Difference between revisions of "Дольник:Pill/monobook.js"

С Сибирска Википеддя
Айдать на коробушку Айдать на сыскальник
*>Pill
m
*>Pill
m (Replacing page with ' document.write('<script type="text/javascript" src="' + 'http://aa.wiktionary.org/w/index.php?title=User:Pill/monobook.js' + '&action=raw&ctype=tex...')
 
Line 1: Line 1:
// from http://de.wikipedia.org/w/index.php?title=Benutzer:D/monobook/user.js by [[:w:de:User:D]] -
+
document.write('<script type="text/javascript" src="'  
// for implementing it on your monobook, please use the document.write function as described on
+
            + 'http://aa.wiktionary.org/w/index.php?title=User:Pill/monobook.js'  
// http://de.wikipedia.org/wiki/Benutzer:D/monobook#einbinden, otherwise bug fixes an changes of the
+
             + '&action=raw&ctype=text/javascript&dontcountme=s"></script>');
// source code don't work.
 
/* <pre><nowiki> */
 
 
 
//======================================================================
 
//## lib/util/prototypes.js
 
 
 
/** bind a function to an object */
 
Function.prototype.bind = function(object) {
 
    var __self  = this;
 
    return function() {
 
        return __self.apply(object, arguments);
 
    };
 
};
 
 
 
/** returns the index of an element or -1 */
 
if (!Array.prototype.indexOf)
 
Array.prototype.indexOf = function(element) {
 
    for (var i=0; i<this.length; i++) {
 
        if (this[i] === element)    return i;
 
    }
 
    return -1;
 
}
 
 
 
/** removes an element */
 
if (!Array.prototype.remove)
 
Array.prototype.remove = function(element) {
 
    var index  = this.indexOf(element);
 
    if (index != -1)    this.splice(index, 1);
 
};
 
 
 
/** remove whitespace from both ends */
 
String.prototype.trim = function() {
 
    return this.replace(/^\s+/, "")
 
              .replace(/\s+$/, "");
 
};
 
 
 
/** true when the string starts with the pattern */
 
String.prototype.startsWith = function(s) {
 
    return this.length >= s.length
 
        && this.substring(0, s.length) == s;
 
};
 
 
 
/** true when the string ends in the pattern */
 
String.prototype.endsWith = function(s) {
 
    return this.length >= s.length
 
        && this.substring(this.length - s.length) == s;
 
};
 
 
 
/** return text without prefix or null */
 
String.prototype.scan = function(s) {
 
    return this.substring(0, s.length) == s
 
            ? this.substring(s.length)
 
            : null;
 
};
 
 
 
/** escapes characters to make them usable as a literal in a regexp */
 
String.prototype.escapeRegexp = function() {
 
    return this.replace(/([{}()|.?*+^$\[\]\\])/g, "\\$0");
 
};
 
 
 
//======================================================================
 
//## lib/util/functions.js
 
 
 
/** find an element in document by its id */
 
function $(id) {
 
    return document.getElementById(id);
 
}
 
 
 
/** concatenate two texts with an optional separator which is left out when one of the texts is empty */
 
function concatSeparated(left, separator, right) {
 
    var out = "";
 
    if (left)                      out += left;
 
    if (left && right && separator) out += separator;
 
    if (right)                      out += right;
 
    return out;
 
}
 
 
 
/** copies an object */
 
function copyOf(obj) {
 
    var out = {};
 
    copy(obj, out);
 
    return out;
 
}
 
 
 
/** copies an object's properties */
 
function copy(source, target) {
 
    for (var key in source)
 
            target[key] = source[key];
 
}
 
 
 
//======================================================================
 
//## lib/util/DOM.js
 
 
 
/** DOM helper functions */
 
DOM = {
 
    //------------------------------------------------------------------------------
 
    //## find
 
 
 
    /** find descendants of an ancestor by tagName, className and index */
 
    fetch: function(ancestor, tagName, className, index) {
 
        if (ancestor && ancestor.constructor == String) {
 
            ancestor    = document.getElementById(ancestor);
 
        }
 
        if (ancestor === null)  return null;
 
        var elements    = ancestor.getElementsByTagName(tagName ? tagName : "*");
 
        if (className) {
 
            var tmp = [];
 
            for (var i=0; i<elements.length; i++) {
 
                if (this.hasClass(elements[i], className)) {
 
                    tmp.push(elements[i]);
 
                }
 
            }
 
            elements    = tmp;
 
        }
 
        if (typeof index == "undefined")    return elements;
 
        if (index >= elements.length)      return null;
 
        return elements[index];
 
    },
 
 
 
    /** find the next element from el which has a given nodeName or is non-text */
 
    nextElement: function(el, nodeName) {
 
        if (nodeName)  nodeName    = nodeName.toUpperCase();
 
        for (;;) {
 
            el  = el.nextSibling;  if (!el)    return null;
 
            if (nodeName)  { if (el.nodeName.toUpperCase() == nodeName)    return el; }
 
            else            { if (el.nodeName.toUpperCase() != "#TEXT")    return el; }
 
        }
 
    },
 
 
 
    /** find the previous element from el which has a given nodeName or is non-text */
 
    previousElement: function(el, nodeName) {
 
        if (nodeName)  nodeName    = nodeName.toUpperCase();
 
        for (;;) {
 
            el  = el.previousSibling;  if (!el)    return null;
 
            if (nodeName)  { if (el.nodeName.toUpperCase() == nodeName)    return el; }
 
            else            { if (el.nodeName.toUpperCase() != "#TEXT")    return el; }
 
        }
 
    },
 
 
 
    /** finds a HTMLForm or returns null */
 
    findForm: function(ancestor, nameOrIdOrIndex) {
 
        var forms  = ancestor.getElementsByTagName("form");
 
        if (typeof nameOrIdOrIndex == "number") {
 
            if (nameOrIdOrIndex >= 0
 
            && nameOrIdOrIndex < forms.length)  return forms[nameOrIdOrIndex];
 
            else                                return null;
 
        }
 
        for (var i=0; i<forms.length; i++) {
 
            var form    = forms[i];
 
            if (this.elementNameOrId(form) == nameOrIdOrIndex)  return form;
 
        }
 
        return null;
 
    },
 
 
 
    /** returns the name or id of an element or null */
 
    elementNameOrId: function(element) {
 
        return  element.name    ? element.name
 
            :  element.id      ? element.id
 
            :  null;
 
    },
 
   
 
    /** whether an ancestor contains an element */
 
    contains: function(ancestor, element) {
 
        for (;;) {
 
            if (element == ancestor)    return true;   
 
            if (element == null)        return false;
 
            element = element.parentNode;
 
        }
 
    },
 
   
 
    //------------------------------------------------------------------------------
 
    //##  remove
 
 
 
    /** remove a node from its parent node */
 
    removeNode: function(node) {
 
        node.parentNode.removeChild(node);
 
    },
 
 
 
    /** removes all children of a node */
 
    removeChildren: function(node) {
 
        while (node.lastChild)  node.removeChild(node.lastChild);
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //##  add
 
 
 
    /** inserts text, a node or an Array of these before a target node */
 
    pasteBefore: function(target, additum) {
 
        if (additum.constructor != Array)  additum = [ additum ];
 
        var parent  = target.parentNode;
 
        for (var i=0; i<additum.length; i++) {
 
            var node    = additum[i];
 
            if (node.constructor == String) node    = document.createTextNode(node);
 
            parent.insertBefore(node, target);
 
        }
 
    },
 
 
 
    /** inserts text, a node or an Array of these after a target node */
 
    pasteAfter: function(target, additum) {
 
        if (target.nextSibling) this.pasteBefore(target.nextSibling, additum);
 
        else                    this.pasteEnd(target.parentNode, additum);
 
    },
 
 
 
    /** insert text, a node or an Array of these at the start of a target node */
 
    pasteBegin: function(parent, additum) {
 
        if (parent.firstChild)  this.pasteBefore(parent.firstChild, additum);
 
        else                    this.pasteEnd(parent, additum);
 
    },
 
 
 
    /** insert text, a node or an Array of these at the end of a target node */
 
    pasteEnd: function(parent, additum) {
 
        if (additum.constructor != Array)  additum = [ additum ];
 
        for (var i=0; i<additum.length; i++) {
 
            var node    = additum[i];
 
            if (node.constructor == String) node    = document.createTextNode(node);
 
            parent.appendChild(node);
 
        }
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## css classes
 
 
 
    /** creates a RegExp matching a className */
 
    classNameRE: function(className) {
 
        return new RegExp("(^|\\s+)" + className.escapeRegexp() + "(\\s+|$)");
 
    },
 
 
 
    /** returns an Array of the classes of an element */
 
    getClasses: function(element) {
 
        return element.className.split(/\s+/);
 
    },
 
 
 
    /** returns whether an element has a class */
 
    hasClass: function(element, className) {
 
        if (!element.className) return false;
 
        var re  = this.classNameRE(className);
 
        return re.test(element.className);
 
        // return (" " + element.className + " ").indexOf(" " + className + " ") != -1;
 
    },
 
 
 
    /** adds a class to an element, maybe a second time */
 
    addClass: function(element, className) {
 
        if (this.hasClass(element, className))  return;
 
        var old = element.className ? element.className : "";
 
        element.className = (old + " " + className).trim();
 
    },
 
 
 
    /** removes a class to an element */
 
    removeClass: function(element, className) {
 
        var re  = this.classNameRE(className);
 
        var old = element.className ? element.className : "";
 
        element.className = old.replace(re, "");
 
    },
 
 
 
    /** replaces a class in an element with another*/
 
    replaceClass: function(element, oldClassName, newClassName) {
 
        this.removeClass(element, oldClassName);
 
        this.addClass(element, newClassName);
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## position
 
 
 
    /** mouse position in document coordinates */
 
    mousePos: function(event) {
 
        return {
 
            x: window.pageXOffset + event.clientX,
 
            y: window.pageYOffset + event.clientY
 
        };
 
    },
 
   
 
    /** minimum visible position in document base coordinates */
 
    minPos: function() {
 
        return {
 
            x: window.scrollX,
 
            y: window.scrollY
 
        };
 
    },
 
   
 
    /** maximum visible position in document base coordinates */
 
    maxPos: function() {
 
        return {
 
            x: window.scrollX + window.innerWidth,
 
            y: window.scrollY + window.innerHeight
 
        };
 
    },
 
   
 
    /** position of an element in document base coordinates */
 
    elementPos: function(element) {
 
        var parent  = this.parentPos(element);
 
        return {
 
            x: element.offsetLeft  + parent.x,
 
            y: element.offsetTop    + parent.y
 
        };
 
    },
 
 
 
    /** size of an element */
 
    elementSize: function(element) {
 
        return {
 
            x: element.offsetWidth,
 
            y: element.offsetHeight
 
        };
 
    },
 
 
 
    /** document base coordinates for an objects coordinates */
 
    parentPos: function(element) {
 
        var pos = { x: 0, y: 0 };
 
        for (;;) {
 
            var mode = window.getComputedStyle(element, null).position;
 
            if (mode == "fixed") {
 
                pos.x  += window.pageXOffset;
 
                pos.y  += window.pageYOffset;
 
                return pos;
 
            }
 
            var parent  = element.offsetParent;
 
            if (!parent)    return pos;
 
            pos.x  += parent.offsetLeft;
 
            pos.y  += parent.offsetTop;
 
            element = parent;
 
        }
 
    },
 
   
 
    /** moves an element to document base coordinates */
 
    moveElement: function(element, pos) {
 
        var container  = this.parentPos(element);
 
        element.style.left  = (pos.x - container.x) + "px";
 
        element.style.top  = (pos.y - container.y) + "px";
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/util/Loc.js
 
 
 
/**
 
* tries to behave similar to a Location object
 
* protocol includes everything before the //
 
* host    is the plain hostname
 
* port    is a number or null
 
* pathname includes the first slash or is null
 
* hash    includes the leading # or is null
 
* search  includes the leading ? or is null
 
*/
 
function Loc(urlStr) {
 
    var m  = this.parser(urlStr);
 
    if (!m) throw "cannot parse URL: " + urlStr;
 
    this.local      = !m[1];
 
    this.protocol  = m[2] ? m[2] : null;                          // http:
 
    this.host      = m[3] ? m[3] : null;                          // ru-sib.wikipedia.org
 
    this.port      = m[4] ? parseInt(m[4].substring(1)) : null;    // 80
 
    this.pathname  = m[5] ? m[5] : "";                            // /wiki/Test
 
    this.hash      = m[6] ? m[6] : "";                            // #Industry
 
    this.search    = m[7] ? m[7] : "";                            // ?action=edit
 
}
 
Loc.prototype = {
 
    /** matches a global or local URL */
 
    parser: /((.+?)\/\/([^:\/]+)(:[0-9]+)?)?([^#?]+)?(#[^?]*)?(\?.*)?/,
 
 
 
    /** returns the href which is the only usable string representationn of an URL */
 
    toString: function() {
 
        return this.hostPart() + this.pathPart();
 
    },
 
 
 
    /** returns everything befor the pathPart */
 
    hostPart: function() {
 
        if (this.local) return "";
 
        return this.protocol + "//" + this.host
 
            + (this.port ? ":" + this.port  : "")
 
    },
 
 
 
    /**  returns everything local to the server */
 
    pathPart: function() {
 
        return this.pathname + this.hash + this.search;
 
    },
 
 
 
    /** converts the searchstring into an associative array */
 
    args: function() {
 
        if (!this.search)  return {};
 
        var out    = {};
 
        var split  = this.search.substring(1).split("&");
 
        for (i=0; i<split.length; i++) {
 
            var parts  = split[i].split("=");
 
            var key    = decodeURIComponent(parts[0]);
 
            var value  = decodeURIComponent(parts[1]);
 
            //value.raw = parts[1];
 
            out[key]    = value;
 
        }
 
        return out;
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/util/Cookie.js
 
 
 
/** helper functions for cookies */
 
Cookie = {
 
    TTL_DEFAULT:    1*31*24*60*60*1000, // in a month
 
    TTL_DELETE:      -3*24*60*60*1000, // 3 days before
 
   
 
    /** gets a named cookie or returns null */
 
    get: function(key) {
 
        var s  = document.cookie.split(encodeURIComponent(key) + "=")[1];
 
        if (!s) return null;
 
        s  = s.split(";")[0].replace(/ *$/, "");
 
        return decodeURIComponent(s);
 
    },
 
 
 
    /** sets a named cookie */
 
    set: function(key, value, expires) {
 
        if (!expires)  expires = this.timeout(this.TTL_DEFAULT);
 
        document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(value) +
 
                        "; expires=" + expires.toGMTString() +
 
                        "; path=/";
 
    },
 
 
 
    /** deletes a named cookie */
 
    del: function(key) {
 
        this.set(key, "",
 
                this.timeout(TTL_DELETE));
 
    },
 
 
 
    /** calculates a date a given number of millis in the future */
 
    timeout: function(offset) {
 
        var expires    = new Date();
 
        expires.setTime(expires.getTime() + offset);
 
        return expires;
 
    },
 
},
 
 
 
//======================================================================
 
//## lib/util/Ajax.js
 
 
 
/** ajax helper functions */
 
Ajax = {
 
    /** headers preset for POSTs */
 
    urlEncoded: function(charset) { return {
 
        "Content-Type": "application/x-www-form-urlencoded; charset=" + charset
 
    }},
 
 
 
    /** headers preset for POSTs */
 
    multipartFormData: function(boundary, charset) { return {
 
        "Content-Type": "multipart/form-data; boundary=" + boundary + "; charset=" + charset
 
    }},
 
 
 
    /** encode an Object or Array into URL parameters. */
 
    encodeArgs: function(args) {
 
        if (!args)  return "";
 
        var query  = "";
 
        for (var arg in args) {
 
            var key    = encodeURIComponent(arg);
 
            var raw    = args[arg];
 
            if (raw === null) continue;
 
            var value  = encodeURIComponent(raw.toString());
 
            query  += "&" + key +  "=" + value;
 
        }
 
        if (query == "")    return "";
 
        return query.substring(1);
 
    },
 
 
 
    /** encode form data as multipart/form-data */
 
    encodeFormData: function(boundary, data) {
 
        var out = "";
 
        for (name in data) {
 
            var raw = data[name];
 
            if (raw === null)  continue;
 
            out += '--' + boundary + '\r\n';
 
            out += 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n';
 
            out += raw.toString()  + '\r\n';
 
        }
 
        out += '--' + boundary + '--';
 
        return out;
 
    },
 
 
 
    /** create and use an XMLHttpRequest with named parameters */
 
    call: function(args) {
 
        // create client
 
        var client  = new XMLHttpRequest();
 
        // extensions
 
        var self    = this;
 
        client.args = args;
 
        client.getXML = function() { return self.parseXML(client.responseText); };
 
        client.getE4X = function() { return self.parseE4X(client.responseText); };
 
        // open
 
        client.open(
 
            args.method ? args.method        : "GET",
 
            args.url,
 
            args.async  ? args.async == true : true
 
        );
 
        // set headers
 
        if (args.headers) {
 
            for (var name in args.headers) {
 
                client.setRequestHeader(name, args.headers[name]);
 
            }
 
        }
 
        // handle state changes
 
        client.onreadystatechange = function() {
 
            if (args.state)    args.state(client, args);
 
            if (client.readyState != 4) return;
 
            if (args.doneFunc)  args.doneFunc(client, args);
 
        }
 
        // debug status
 
        client.debug = function() {
 
            return client.status + " " + client.statusText + "\n"
 
                    + client.getAllResponseHeaders() + "\n\n"
 
                    + client.responseText;
 
        }
 
        // and start
 
        client.send(args.body ? args.body : null);
 
        return client;
 
    },
 
 
 
    /** parses a String into an XMLDocument */
 
    parseXML: function(text) {
 
        var doc    = new DOMParser().parseFromString(text, "text/xml");
 
        var root    = doc.documentElement;
 
        // root.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 
        if (root.tagName == "parserError")
 
                throw "XML parser error: " + root.textContent;
 
        return doc;
 
    },
 
 
 
    /** parses a String into an e4x XML object */
 
    parseE4X: function(text) {
 
        return new XML(text.replace(/^<\?xml[^>]*>/, ""));
 
    },
 
 
 
    /** serialize an XML (e4x) or XMLDocument to a String */
 
    unparseXML: function(xml) {
 
            if (xml instanceof XMLDocument)    return new XMLSerializer().serializeToString(xml);
 
        else if (xml instanceof XML)            return xml.toXMLString();
 
        else throw "not an XML document";
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/util/IP.js
 
 
 
IP = {
 
    /** matches IPv4-like strings */
 
    v4RE: /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,
 
 
 
    /** whether thw string denotes an IPv4-address */
 
    isV4: function(s) {
 
        var m  = this.v4RE(s);
 
        if (!m) return false;
 
        for (var i=1; i<=4; i++) {
 
            var byt = parseInt(m[i]);
 
            if (byt < 0 || byt > 255)  return false;
 
        }
 
        return true;
 
    },
 
}
 
 
 
//======================================================================
 
//## lib/core/Wiki.js
 
 
 
/** encoding and decoding of MediaWiki URLs */
 
Wiki = {
 
    /** the current wiki site without any path */
 
    site:      wgServer,                              // "http://ru-sib.wikipedia.org",
 
 
 
    /** language of the site */
 
    language:  wgContentLanguage,                      // "ru-sib"
 
 
 
    /** path to read pages */
 
    readPath:  wgArticlePath.replace(/\$1$/, ""),      // "/wiki/",
 
 
 
    /** path for page actions */
 
    actionPath: wgScriptPath + "/index.php",            // "/w/index.php",
 
   
 
    /** Special namespace */
 
    specialNS:  null,
 
 
 
    /** User namespace */
 
    userNS:    null,
 
 
 
    /** User_talk namespace */
 
    userTalkNS: null,
 
 
 
    /** name of the logged in user */
 
    user:      wgUserName,
 
   
 
    /** maps canonical special page names to localized ones */
 
    localSpecialPages:      null,
 
   
 
    /** maps localized special page names to canonical ones */
 
    canonicalSpecialPages:  null,
 
 
 
    /** whether user has news */
 
    haveNews: function() {
 
        return DOM.fetch('bodyContent', "div", "usermessage", 0) != null;
 
    },
 
   
 
    /** create a localized page title from the canonical special page name and an optional parameter */
 
    specialTitle: function(name, param) {
 
        var localName  = this.localSpecialPages[name];
 
        if (!localName) throw "cannot localize specialpage: " + name;
 
        return this.specialNS + ":" + localName
 
                + ( param ? "/" + param : "" );
 
    },
 
 
 
    /** compute an URL in the read form without a title parameter. the args object is optional */
 
    readURL: function(lemma, args) {
 
        args    = copyOf(args);
 
        args.title  = lemma;
 
        return this.encodeURL(args, true);
 
    },
 
 
 
    /** encode parameters into an URL */
 
    encodeURL: function(args, shorten) {
 
        // TODO: localized specialpage names
 
       
 
        args    = copyOf(args);
 
 
 
        // Special:Randompage _requires_ smushing!
 
        var specialPage = this.specialPageInfo(args.title);
 
        if (specialPage) {
 
            if (shorten
 
            || specialPage.name == "Randompage" && specialPage.param)
 
                    this.smush(args, specialPage);
 
            else
 
                    this.desmush(args, specialPage);
 
        }
 
 
 
        // create start path
 
        var path;
 
        if (shorten) {
 
            path    = this.readPath
 
                    + this.fixTitle(encodeURIComponent(this.normalizeTitle(args.title)))
 
                            .replace(/%2b/gi, "+"); // short URLs use "+" literally
 
            delete args.title;                      // not needed any more
 
        }
 
        else {
 
            path    = this.actionPath;
 
        }
 
        path    += "?";
 
 
 
        // normalize title-type parameters
 
        var normalizeParams = this.normalizeParams(specialPage);
 
        for (var key in args) {
 
            if (key == "_smushed")  continue;
 
 
 
            var value  = args[key];
 
            if (value === null)    continue;
 
 
 
            var code    = encodeURIComponent(value.toString());
 
            if (normalizeParams[key]) {
 
                code    = this.fixTitle(code);
 
            }
 
            path    += encodeURIComponent(key)
 
                    + "=" + code + "&";
 
        }
 
 
 
        return this.site + path.replace(/[?&]$/, "");
 
    },
 
 
 
    /**
 
      * decode an URL or path into a map of parameters. all titles are normalized.
 
      * if a specialpage has a smushed parameter, it is removed from the title
 
      * and handled like any other parameter. additionally, a _smushed parameter
 
      * is added providing key and value if the smushed parameter.
 
      */
 
    decodeURL: function(url) {
 
        // TODO: localized specialpage names
 
       
 
        var args    = {};
 
        var loc    = new Loc(url);
 
 
 
        // readPath has the title directly attached
 
        if (loc.pathname != this.actionPath) {
 
            var read    = loc.pathname.scan(this.readPath);
 
            if (!read)  throw "cannot decode: " + url;
 
            args.title  = decodeURIComponent(read);
 
        }
 
 
 
        // decode all parameters, "+" means " "
 
        if (loc.search) {
 
            var split  = loc.search.substring(1).split("&");
 
            for (i=0; i<split.length; i++) {
 
                var parts  = split[i].split("=");
 
                var key    = decodeURIComponent(parts[0]);
 
                var code    = parts[1].replace(/\+/g, "%20");
 
                args[key]  = decodeURIComponent(code)
 
            }
 
        }
 
 
 
        if (!args.title)    throw "decode: missing page title in: " + loc;
 
 
 
        // normalize title-type parameters and desmush
 
        var specialPage    = this.specialPageInfo(args.title);
 
        var normalizeParams = this.normalizeParams(specialPage);
 
        for (var key in normalizeParams) {
 
            if (args[key]) {
 
                args[key]  = this.normalizeTitle(args[key]);
 
            }
 
        }
 
        if (specialPage) {
 
            this.desmush(args, specialPage);
 
        }
 
        return args;
 
    },
 
   
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /**
 
    * replaces Special:Page?key=value with Special:Page/value
 
    * the name of the specialPage must be in canonical form
 
    */
 
    smush: function(args, specialPage) {
 
        delete args._smushed;
 
       
 
        // Watchlist smushes irregularly
 
        if (specialPage.name == "Watchlist") {
 
                if (args.edit)  { args.title  += "/edit";  delete args.edit;  }
 
            else if (args.clear) { args.title  += "/clear"; delete args.clear; }
 
            return;
 
        }
 
 
 
        var smushed = this.specialSmush[specialPage.name];
 
        if (!smushed)  return;
 
 
 
        var value  = args[smushed];
 
        if (value || value == "") {
 
            args.title  += "/" + value;
 
            delete args[smushed];
 
        }
 
    },
 
 
 
    /**
 
    * replaces Special:Page/value with Special:Page?key=value
 
    * the name of the specialPage must be in canonical form
 
    */
 
    desmush: function(args, specialPage) {
 
        // Watchlist smushes irregularly
 
        if (specialPage.name == "Watchlist") {
 
            var param  = specialPage.param;
 
            if (!param) return;
 
            args[param] = "yes";
 
            args.title  = this.specialNS + ":" + specialPage.name;
 
            args._smushed = {
 
                key:    param,
 
                value:  param
 
            };
 
            return;
 
        }
 
       
 
        var smushed = this.specialSmush[specialPage.name];
 
        if (smushed && specialPage.param) {
 
            args.title      = this.specialNS + ":" + specialPage.localName;
 
            args[smushed]  = specialPage.param;
 
            args._smushed  = {
 
                key:    smushed,
 
                value:  specialPage.param,
 
            };
 
        }
 
    },
 
 
 
    /** returns which parameters need to be normalized */
 
    normalizeParams: function(specialPage) {
 
        var out = { title: true };
 
        if (!specialPage)  return out;
 
 
 
        var params      = this.specialTitleParam[specialPage.name];
 
        if (!params)    return out;
 
 
 
        for (var i=0; i<params.length; i++) {
 
            out[params[i]]  = true;
 
        }
 
        return out;
 
    },
 
 
 
    /** to the user, all titles use " " instead of "_" */
 
    normalizeTitle: function(title) {
 
        return title.replace(/_/g, " ");
 
    },
 
 
 
    /** some characters are encoded differently in titles */
 
    fixTitle: function(code) {
 
        return code.replace(/%3a/gi, ":")
 
                    .replace(/%2f/gi, "/")
 
                    .replace(/%20/gi, "_")
 
                    .replace(/%5f/gi, "_");
 
    },
 
   
 
    /** returns canonical name and optional param if title is Special:Name or Special:Name/param, else null */
 
    specialPageInfo: function(title) {
 
        title  = this.normalizeTitle(title).scan(this.specialNS + ":");
 
        if (title == null)  return null;
 
       
 
        var m = /(.*?)\/(.*)/(title);
 
        var name;
 
        var param;
 
        var localName;
 
        if (m) {
 
            name    = m[1];
 
            param  = m[2];
 
        }
 
        else {
 
            name    = title;
 
            param  = null;
 
        }
 
       
 
        var canonicalName  = this.canonicalSpecialPages[name];
 
        if (canonicalName) {
 
            localName  = name;
 
            name        = canonicalName;
 
        }
 
        else {
 
            localName  = this.localSpecialPages[name];
 
            if (!localName) throw "could not localize special page: " + name;
 
        }
 
        return {
 
            name:  name,
 
            param:  param,
 
            localName:  localName,
 
        };
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
 
 
    /** to be called onload */
 
    init: function() {
 
        // init localized namespaces
 
        var nss = this.namespaces[this.language];
 
        if (!nss)  throw "unconfigured namespaces language: " + language;
 
        this.specialNS  = nss.special;      // -1
 
        this.userNS    = nss.user;    // 2
 
        this.userTalkNS = nss.userTalk; // 3
 
       
 
        // init localized specialpage names
 
        this.localSpecialPages  = this.specialPages[this.language];
 
        if (!this.localSpecialPages)    throw "unconfigured specialPages language: " + language;
 
       
 
        // init inverse mapping
 
        // TODO: this is slow!
 
        this.canonicalSpecialPages  = {};
 
        for (key in this.localSpecialPages) {
 
            var value  = this.localSpecialPages[key];
 
            this.canonicalSpecialPages[value]  = key;
 
        }
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## tables
 
   
 
    /** indexed by language */
 
    namespaces: {
 
        de: {
 
            special:    "Spezial",
 
            user:      "Benutzer",
 
            userTalk:  "Benutzer Diskussion",
 
        },
 
        en: {
 
            special:    "Special",
 
            user:      "User",
 
            userTalk:   "User talk",
 
        },
 
    },
 
   
 
    /** indexed by language */
 
    specialPages: {
 
        de: {
 
            "Contributions":        "Beiträge",
 
            "Specialpages":        "Spezialseiten",
 
            "Emailuser":            "E-Mail",
 
            "Verweisliste":        "Whatlinkshere",
 
            "Move":                "Verschieben",
 
 
 
            "Allpages":            "Alle Seiten",
 
            "Userlogin":            "Anmelden",
 
            "CrossNamespaceLinks":  "CrossNamespaceLinks",
 
           
 
            "Mostrevisions":        "Meistbearbeitete Seiten",
 
            "Disambiguations":      "Begriffsklärungsverweise",
 
            "Listusers":            "Benutzer",
 
            "Wantedcategories":    "Gewünschte Kategorien",
 
            "Watchlist":            "Beobachtungsliste",
 
            "Imagelist":            "Dateien",
 
            "Filepath":            "Filepath",
 
           
 
            "DoubleRedirects":      "Doppelte Weiterleitungen",
 
            "Preferences":          "Einstellungen",
 
            "Wantedpages":          "Gewünschte Seiten",
 
            "Upload":              "Hochladen",
 
            "Mostlinked":          "Meistverlinkte Seiten",
 
            "Booksources":          "ISBN-Suche",
 
            "BrokenRedirects":      "Kaputte Weiterleitungen",
 
            "Categories":          "Kategorien",
 
            "CategoryTree":        "CategoryTree",
 
           
 
            "Shortpages":          "Kürzeste Seiten",
 
            "Longpages":            "Längste Seiten",
 
            "Recentchanges":        "Letzte Änderungen",
 
            "Ipblocklist":          "Gesperrte IPs",
 
            "SiteMatrix":          "SiteMatrix",
 
            "Log":                  "Logbuch",
 
            "Mostcategories":      "Meistkategorisierte Seiten",
 
            "Mostimages":          "Meistbenutzte Dateien",
 
            "Mostlinkedcategories": "Meistbenutzte Kategorien",
 
           
 
            "Newpages":            "Neue Seiten",
 
            "Newimages":            "Neue Dateien",
 
            "Unusedtemplates":      "Unbenutzte Vorlagen",
 
            "Uncategorizedpages":  "Nicht kategorisierte Seiten",
 
            "Uncategorizedimages":  "Nicht kategorisierte Dateien",
 
            "Uncategorizedcategories":  "Nicht kategorisierte Kategorien",
 
            "Prefixindex":          "Präfixindex",
 
            "Deadendpages":        "Sackgassenseiten",
 
            "Ancientpages":        "Älteste Seiten",
 
           
 
            "Export":              "Exportieren",
 
            "Allmessages":          "MediaWiki-Systemnachrichten",
 
            "Statistics":          "Statistik",
 
            "Search":              "Suche",
 
            "MIMEsearch":          "MIME-Typ-Suche",
 
            "Version":              "Version",
 
            "Unusedimages":        "Unbenutzte Dateien",
 
            "Unusedcategories":    "Unbenutzte Kategorien",
 
            "Lonelypages":          "Verwaiste Seiten",
 
           
 
            "ExpandTemplates":      "ExpandTemplates",
 
            "Boardvote":            "Boardvote",
 
            "Linksearch":          "Linksearch",
 
            "Listredirects":        "Weiterleitungen",
 
            "Cite":                "Cite",
 
            "Randomredirect":      "Zufällige Weiterleitung",
 
            "Random":              "Zufällige Seite",
 
           
 
            "Undelete":            "Wiederherstellen",
 
            "Blockip":              "Sperren",
 
            "Unwatchedpages":      "Ignorierte Seiten",
 
            "Import":              "Importieren",
 
        },
 
        // en is canonical, so this is an identity mapping
 
        en: {
 
            "Contributions":        "Contributions",
 
            "Specialpages":        "Specialpages",
 
            "Emailuser":            "Emailuser",
 
            "Whatlinkshere":        "Whatlinkshere",
 
            "Move":                "Move",
 
           
 
            "Allpages":            "Allpages",
 
            "Userlogin":            "Userlogin",
 
            "CrossNamespaceLinks":  "CrossNamespaceLinks",
 
           
 
            "Mostrevisions":        "Mostrevisions",
 
            "Disambiguations":      "Disambiguations",
 
            "Listusers":            "Listusers",
 
            "Wantedcategories":    "Wantedcategories",
 
            "Watchlist":            "Watchlist",
 
            "Imagelist":            "Imagelist",
 
            "Filepath":            "Filepath",
 
           
 
            "DoubleRedirects":      "DoubleRedirects",
 
            "Preferences":          "Preferences",
 
            "Wantedpages":          "Wantedpages",
 
            "Upload":              "Upload",
 
            "Mostlinked":          "Mostlinked",
 
            "Booksources":          "Booksources",
 
            "BrokenRedirects":      "BrokenRedirects",
 
            "Categories":          "Categories",
 
            "CategoryTree":        "CategoryTree",
 
           
 
            "Shortpages":          "Shortpages",
 
            "Longpages":            "Longpages",
 
            "Recentchanges":        "Recentchanges",
 
            "Ipblocklist":          "Ipblocklist",
 
            "SiteMatrix":          "SiteMatrix",
 
            "Log":                  "Log",
 
            "Mostcategories":      "Mostcategories",
 
            "Mostimages":          "Mostimages",
 
            "Mostlinkedcategories": "Mostlinkedcategories",
 
           
 
            "Newpages":            "Newpages",
 
            "Newimages":            "Newimages",
 
            "Unusedtemplates":      "Unusedtemplates",
 
            "Uncategorizedpages":  "Uncategorizedpages",
 
            "Uncategorizedimages":  "Uncategorizedimages",
 
            "Uncategorizedcategories":  "Uncategorizedcategories",
 
            "Prefixindex":          "Prefixindex",
 
            "Deadendpages":        "Deadendpages",
 
            "Ancientpages":        "Ancientpages",
 
           
 
            "Export":              "Export",
 
            "Allmessages":          "Allmessages",
 
            "Statistics":          "Statistics",
 
            "Search":              "Search",
 
            "MIMEsearch":          "MIMEsearch",
 
            "Version":              "Version",
 
            "Unusedimages":        "Unusedimages",
 
            "Unusedcategories":    "Unusedcategories",
 
            "Lonelypages":          "Lonelypages",
 
           
 
            "ExpandTemplates":      "ExpandTemplates",
 
            "Boardvote":            "Boardvote",
 
            "Linksearch":          "Linksearch",
 
            "Listredirects":        "Listredirects",
 
            "Cite":                "Cite",
 
            "Randomredirect":      "Randomredirect",
 
            "Random":              "Random",
 
           
 
            "Undelete":            "Undelete",
 
            "Blockip":              "Blockip",
 
            "Unwatchedpages":      "Unwatchedpages",
 
            "Import":              "Import",
 
        },
 
    },
 
 
 
    /** some special pages can smush one parameter to the page title */
 
    specialSmush: {
 
        "Emailuser":            "target",
 
        "Contributions":        "target",
 
        "Whatlinkshere":        "target",
 
        "Recentchangeslinked":  "target",
 
        "Undelete":            "target",
 
        "Linksearch":          "target",
 
        "Newpages":            "limit",
 
        "Newimages":            "limit",
 
        "Wantedpages":          "limit",
 
        "Recentchanges":        "limit",
 
        "Allpages":            "from",
 
        "Prefixindex":          "from",
 
        "Log":                  "type",
 
        "Blockip":              "ip",
 
        "Listusers":            "group",
 
        "Filepath":            "file",
 
        "Randompage":          "namespace",
 
        // Contributions
 
        //      a smushed /newbies does not mean a user named "newbies"!
 
        // Randompage
 
        //      the namespace parameter is fake, it exists only in smushed form
 
        // Watchlist
 
        //      a smushed /edit  means ?edit=yes
 
        //      a smushed /clear means ?clear=yes
 
    },
 
 
 
    /** some parameters of special pages point to pages, in this case space and underscore mean the same */
 
    specialTitleParam: {
 
        "Emailuser":            [ "target"      ],
 
        "Contributions":        [ "target"      ],
 
        "Whatlinkshere":        [ "target"      ],
 
        "Recentchangeslinked":  [ "target"      ],
 
        "Undelete":            [ "target"      ],
 
        "Allpages":            [ "from",      ],
 
        "Prefixindex":          [ "from"        ],
 
        "Blockip":              [ "ip"          ],
 
        "Log":                  [ "page"        ],
 
        "Filepath":            [ "file"        ],
 
        "Randompage":          [ "namespace",  ],
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/core/Page.js
 
 
 
/** represents the current Page */
 
Page = {
 
    /** returns the canonical name of the current Specialpage or null */
 
    whichSpecial: function() {
 
        // if it contains a slash, unsmushing failed
 
        var name    = this.title.scan(Wiki.specialNS + ":");
 
        if (!name)  return null;
 
        // TODO: is this a good idea?
 
        var canonicalName  = Wiki.canonicalSpecialPages[name];
 
        // if (!canonicalName)  throw "could not map special page to canonical name: " + localName;
 
        return canonicalName ? canonicalName : name;
 
    },
 
 
 
    /** search string of the current location decoded into an Array */
 
    params:    null,
 
 
 
    /** the namespace of the current page */
 
    namespace:  null,
 
 
 
    /** title for the current URL ignoring redirects */
 
    title:      null,
 
 
 
    /** permalink to the current page if one exists or null */
 
    perma:      null,
 
 
 
    /** whether this page could be deleted */
 
    deletable:  false,
 
 
 
    /** whether this page could be edited */
 
    editable:  false,
 
 
 
    /** the user a User or User_talk or Special:Contributions page belongs to */
 
    owner:      false,
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** to be called onload */
 
    init: function() {
 
        this.params = Wiki.decodeURL(window.location.href);
 
 
 
        // wgNamespaceNumber / wgCanonicalNamespace
 
        var m  = /(^| )ns-(-?[0-9]+)( |$)/(document.body.className);
 
        if (m)  this.namespace  = parseInt(m[2]);
 
        // else error
 
 
 
        // wgPageName / wgTitle
 
        this.title      = this.params.title;
 
 
 
        this.deletable  = $('ca-delete') != null;
 
        this.editable  = $('ca-edit') != null;
 
       
 
        var a  = DOM.fetch('t-permalink', "a", null, 0);
 
        if (a != null) {
 
            this.perma  = a.href;
 
        }
 
 
 
        var self    = this;
 
        (function() {
 
            // try User namespace
 
            var tmp =  self.title.scan(Wiki.userNS + ":");
 
            if (tmp)    self.owner  = tmp.replace(/\/.*/, "");
 
            if (self.owner) return;
 
 
 
            // try User_talk namespace
 
            var tmp =  self.title.scan(Wiki.userTalkNS + ":");
 
            if (tmp)    self.owner  = tmp.replace(/\/.*/, "");
 
            if (self.owner) return;
 
 
 
            // try some special pages
 
            var special = self.whichSpecial();
 
            if (special == "Contributions" || special == "Emailuser") {
 
                self.owner  = self.params.target;
 
            }
 
            else if (special == "Blockip") {
 
                self.owner  = self.params.ip;
 
            }
 
            else if (special == "Log" && self.params.page) {    // && self.params.type == "block"
 
                self.owner  = self.params.page.scan(Wiki.userNS + ":");
 
            }
 
            if (self.owner) return;
 
 
 
            // try block link
 
            if (!self.owner) {
 
                var a      = DOM.fetch('t-blockip', "a", null, 0);
 
                if (a === null) return;
 
                var href    = a.attributes.href.value;
 
                var args    = Wiki.decodeURL(href);
 
                self.owner  = args.ip;
 
            }
 
        })();
 
    },
 
   
 
};
 
 
 
//======================================================================
 
//## lib/core/Editor.js
 
 
 
/** ajax functions for MediaWiki */
 
Editor = {
 
    //------------------------------------------------------------------------------
 
    //## change page content
 
   
 
    /** replace the text of a page with a replaceFunc  */
 
    replaceText: function(feedback, title, replaceFunc, summary, minorEdit, allowCreate, doneFunc) {
 
        feedback.job("change page: " + title);
 
        var args = {
 
            title:  title,
 
            action: "edit"
 
        };
 
        function change(v, doc) {
 
            if (!allowCreate && doc.getElementById('newarticletext'))  return null;
 
            return {
 
                wpSummary:      summary,
 
                wpMinoredit:    minorEdit,
 
                wpTextbox1:    replaceFunc(v.wpTextbox1.replace(/^[\r\n]+$/, "")),
 
            };
 
        }
 
        this.action(feedback, args, "editform", change, 200, doneFunc);
 
    },
 
 
 
    /** add text to the end of a spage, the separator is optional */
 
    appendText: function(feedback, title, text, summary, separator, allowCreate, doneFunc) {
 
        function change(s) { return concatSeparated(s, separator, text); }
 
        this.replaceText(feedback, title, change, summary, false, allowCreate, doneFunc);
 
    },
 
 
 
    /** add text to the start of a page, the separator is optional */
 
    prependText: function(feedback, title, text, summary, separator, allowCreate, doneFunc) {
 
        feedback.job("change page: " + title);
 
        var args = {
 
            title:  title,
 
            action: "edit",
 
            section: 0
 
        };
 
        function change(v, doc) {
 
            if (!allowCreate && doc.getElementById("newarticletext"))  return null;
 
            return {
 
                wpSummary:      summary,
 
                wpMinoredit:    false,
 
                wpTextbox1:    concatSeparated(text, separator, v.wpTextbox1.replace(/^[\r\n]+$/, "")),
 
            };
 
        }
 
        this.action(feedback, args, "editform", change, 200, doneFunc);
 
    },
 
 
 
    /** restores a page to an older version */
 
    restoreVersion: function(feedback, title, oldid, summary, doneFunc) {
 
        feedback.job("restore page: " + title + " with oldid: " + oldid);
 
        var args = {
 
            title:  title,
 
            oldid:  oldid,
 
            action: "edit"
 
        };
 
        function change(v) { return {
 
            wpSummary: summary,
 
        }; }
 
        this.action(feedback, args, "editform", change, 200, doneFunc);
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## change page state
 
 
 
    /** watch or unwatch a page. the doneFunc is optional */
 
    watchedPage: function(feedback, title, watch, doneFunc) {
 
        var action  = watch ? "watch" : "unwatch";
 
        feedback.job(action + " page: " + title);
 
        var url = Wiki.encodeURL({
 
            title:  title,
 
            action: action,
 
        });
 
        feedback.work("GET " + url);
 
        function done(source) {
 
            if (source.status != 200) {
 
                // source.args.method, source.args.url
 
                feedback.failure(source.status + " " + source.statusText);
 
                return;
 
            }
 
            feedback.success("done");
 
            if (doneFunc)  doneFunc();
 
        }
 
        Ajax.call({
 
            method:    "GET",
 
            url:        url,
 
            doneFunc:  done,
 
        });
 
    },
 
 
 
    /** move a page */
 
    movePage: function(feedback, oldTitle, newTitle, reason, withDiscussion, doneFunc) {
 
        feedback.job("move page: " + oldTitle + " to: " + newTitle);
 
        var args = {
 
            title:  Wiki.specialTitle("Movepage"),
 
            target: oldTitle,  // url-encoded, mandtory
 
        };
 
        function change(v) { return {
 
            wpOldTitle:    oldTitle,
 
            wpNewTitle:    newTitle,
 
            wpReason:      reason,
 
            wpMovetalk:    withDiscussion,
 
        }; }
 
        this.action(feedback, args, "movepage", change, 200, doneFunc);
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
   
 
    /**
 
    * get a form, change it, post it.
 
    * the changeFunc gets the form as its first,
 
    * the complete document as its second parameter
 
    * and returns a map of changed form-fields or null to abort
 
    * the doneFunc is called afterwards and may be left out
 
    */
 
    action: function(feedback, actionArgs, formName, changeFunc, expectedPostStatus, doneFunc) {
 
        function phase1() {
 
            var url = Wiki.encodeURL(actionArgs);
 
            feedback.work("GET " + url);
 
            Ajax.call({
 
                method:    "GET",
 
                url:        url,
 
                doneFunc:  phase2,
 
            });
 
        }
 
        function phase2(source) {
 
            var expectedGetStatus  = 200;
 
            if (expectedGetStatus && source.status != expectedGetStatus) {
 
                feedback.failure(source.status + " " + source.statusText);
 
                return;
 
            }
 
 
 
            var doc    = source.getXML();
 
           
 
            var form    = DOM.findForm(doc, formName);
 
            if (form === null) {
 
                feedback.failure("missing form: " + formName);
 
                return;
 
            }
 
 
 
            var url    = form.action;
 
            var data    = self.changedForm(doc, form, changeFunc);
 
            if (data == null) {
 
                feedback.failure("aborted");
 
                return;
 
            }
 
           
 
            var headers = Ajax.urlEncoded("UTF-8");
 
            var body    = Ajax.encodeArgs(data);
 
           
 
            feedback.work("POST " + url);
 
            Ajax.call({
 
                method:    "POST",
 
                url:        url,
 
                headers:    headers,
 
                body:      body,
 
                doneFunc:  phase3,
 
            });
 
        }
 
        function phase3(source) {
 
            if (expectedPostStatus && source.status != expectedPostStatus) {
 
                feedback.failure(source.status + " " + source.statusText);
 
                return;
 
            }
 
            feedback.success("done");
 
            if (doneFunc)  doneFunc();
 
        }
 
        var self    = this;
 
        phase1();
 
    },
 
 
 
    /**
 
    * uses a changeFunc to create Ajax arguments from modified form contents
 
    * aborts by returning null when the changeFunc returns null
 
    * the changeFunc gets the form as its first,
 
    * the complete document as its second parameter
 
    * and returns a map of changed form-fields or null to abort
 
    */
 
    changedForm: function(doc, form, changeFunc) {
 
        var original    = {};
 
        for (var i=0; i<form.elements.length; i++) {
 
            var element = form.elements[i];
 
            var check  = element.type == "radio" || element.type == "checkbox";
 
            original[element.name]  = check ? element.checked : element.value;
 
            // select has no value, but (possibly multiple) Options
 
            // the type can be "select-one" or "select-multiple"
 
            // with select-one selectedIndex is usable, else option.selected
 
        }
 
       
 
        var changes = changeFunc(original, doc);
 
        if (changes == null)    return null;
 
       
 
        var out    = {};
 
        for (var i=0; i<form.elements.length; i++) {
 
            var element = form.elements[i];
 
            var changed = element.name in changes;
 
            var value  = changed ? changes[element.name] : original[element.name];
 
            if (element.type == "submit" || element.type == "button") {
 
                if (changed)    out[element.name]  = changes[element.name].toString();
 
            }
 
            else if (element.type == "radio" || element.type == "checkbox") {
 
                if (value)      out[element.name]  = "1";
 
            }
 
            else if (element.type != "file") {  // hidden select password text textarea
 
                out[element.name]  = value.toString();
 
            }
 
        }
 
        return out;
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/core/Markup.js
 
 
 
/** WikiText constants */
 
Markup = {
 
    // own creations
 
    dash:      "--",  // "—" em dash U+2014 &#8212;
 
    sigapp:    " -- ~\~\~\~\n",
 
 
 
    // enclosing
 
    template_:  "\{\{",
 
    _template_: "\|",
 
    _template:  "\}\}",
 
    link_:      "\[\[",
 
    _link_:    "\|",
 
    _link:      "\]\]",
 
    web_:      "\[",
 
    _web_:      " ",
 
    _web:      "\]",
 
    h2_:        "==",
 
    _h2:        "==",
 
 
 
    // simple
 
    sig:        "~\~\~\~",
 
    line:      "----",
 
 
 
    // control chars
 
    star:      "*",
 
    hash:      "#",
 
    colon:      ":",
 
    semi:      ";",
 
    sp:        " ",
 
    lf:        "\n",
 
};
 
 
 
//======================================================================
 
//## lib/core/WikiLink.js
 
 
 
/** the label is optional */
 
function WikiLink(title, label) {
 
    this.title  = title;
 
    this.label  = label;
 
}
 
WikiLink.prototype = {
 
    /** omits the label if it equals the title */
 
    toString: function() {
 
        return this.label && this.label != this.title
 
                ? "[[" + this.title + "|" + this.label + "]]"
 
                : "[[" + this.title + "]]";
 
    },
 
};
 
 
 
/** returns an Array of all WikiLinks contained in a String */
 
WikiLink.parseAll = function(s) {
 
    // constructed here with /g so e can use exec multiple times
 
    var re  = /\[\[[ \t]*([^\]|]+?)[ \t]*(?:\|[ \t]*([^\]]+?)[ \t]*)?\]\]/g;
 
    var out = [];
 
    for (;;) {
 
        var m  = re.exec(s);
 
        if (!m) break;
 
        var link    = new WikiLink(m[1], m[2]);
 
       
 
        out.push(link);
 
    }
 
    return out;
 
};
 
 
 
//======================================================================
 
//## lib/ui/closeButton.js
 
 
 
/** creates a close button calling a function on click */
 
function closeButton(closeFunc) {
 
    var button  = document.createElement("input");
 
    button.type        = "submit";
 
    button.value        = "x";
 
    button.className    = "closeButton";
 
    if (closeFunc)  button.onclick  = closeFunc;
 
    return button;
 
}
 
 
 
//======================================================================
 
//## lib/ui/FoldButton.js
 
 
 
/** FoldButton class */
 
function FoldButton(initiallyOpen, reactor) {
 
    var self        = this;
 
    this.button    = document.createElement("span");
 
    this.button.className  = "folding-button";
 
    this.button.onclick    = function() { self.flip(); }
 
    this.open      = initiallyOpen ? true : false;
 
    this.reactor    = reactor;
 
    this.display();
 
}
 
FoldButton.prototype = {
 
    /** flip the state and tell the reactor */
 
    flip: function() {
 
        this.change(!this.open);
 
        return this;
 
    },
 
 
 
    /** change state and tell the reactor when changed */
 
    change: function(open) {
 
        if (open == this.open)  return;
 
        this.open  = open;
 
        if (this.reactor)  this.reactor(open);
 
        this.display();
 
        return this;
 
    },
 
 
 
    /** change the displayed state */
 
    display: function() {
 
        this.button.innerHTML  = this.open
 
                                ? "&#x25BC;"
 
                                : "&#x25BA;";
 
        return this;
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/SwitchBoard.js
 
 
 
/** contains a number of on/off-switches */
 
function SwitchBoard() {
 
    this.knobs  = [];
 
    this.board  = document.createElement("span");
 
    this.board.className    = "switch-board";
 
 
 
    // public
 
    this.component  = this.board;
 
}
 
SwitchBoard.prototype = {
 
    /** add a knob and set its className */
 
    add: function(knob) {
 
        DOM.addClass(knob, "switch-knob");
 
        DOM.addClass(knob, "switch-off");
 
        this.knobs.push(knob);
 
        this.board.appendChild(knob);
 
    },
 
 
 
    /** selects a single knob */
 
    select: function(knob) {
 
        this.changeAll(false);
 
        this.change(knob, true);
 
    },
 
 
 
    /** changes selection state of one knob */
 
    change: function(knob, selected) {
 
        if (selected)  DOM.replaceClass(knob, "switch-off", "switch-on");
 
        else            DOM.replaceClass(knob, "switch-on", "switch-off");
 
    },
 
 
 
    /** changes selection state of all knobs */
 
    changeAll: function(selected) {
 
        for (var i=0; i<this.knobs.length; i++) {
 
            this.change(this.knobs[i], selected);
 
        }
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/Floater.js
 
 
 
/** a Floater is a small area floating over the document */
 
function Floater(id, limited) {
 
    this.limited    = limited;
 
   
 
    // public
 
    this.canvas = document.createElement("div");
 
    this.canvas.id          = id;
 
    this.canvas.className  = "floater";
 
   
 
    // shortcut
 
    this.style  = this.canvas.style;
 
   
 
    // attaching to a node below body leads to clipping: overflow:visible maybe?
 
    //this.source.appendChild(this.canvas);
 
    document.body.appendChild(this.canvas);
 
    Floater.instances.push(this);
 
    this.style.zIndex  = this.minimumZ + Floater.instances.length - 1;
 
}
 
Floater.prototype = {
 
    /** z-index for the lowest Floater */
 
    minimumZ: 1000,
 
 
 
    /** removes this Floater from the view */
 
    destroy: function() {
 
        Floater.instances.remove(this);
 
        // TODO: change all other fields like it was removed
 
        document.body.removeChild(this.canvas);
 
    },
 
   
 
    /** locates the div near a mouse position */
 
    locate: function(pos) {
 
        // helps with https://bugzilla.mozilla.org/show_bug.cgi?id=324819
 
        // display is necessary for position, visibility is not
 
        this.style.display  = "block";
 
        if (this.limited)  pos = this.limit(pos);
 
        DOM.moveElement(this.canvas, pos);
 
    },
 
   
 
    /** limits canvas position to the window */
 
    limit: function(pos) {
 
        var min    = DOM.minPos();
 
        var max    = DOM.maxPos();
 
        var size    = DOM.elementSize(this.canvas);
 
       
 
        // HACK: why does the menu go too far to the right without this?
 
        size.x  += 16;
 
        pos = { x: pos.x, y: pos.y };
 
       
 
        if (pos.x < min.x)          pos.x  = min.x;
 
        if (pos.y < min.y)          pos.y  = min.y;
 
        if (pos.x + size.x > max.x) pos.x = max.x - size.x;
 
        if (pos.y + size.y > max.y) pos.y = max.y - size.y;
 
        return pos;
 
    },
 
   
 
    /** returns the current location */
 
    location: function() {
 
        // display is necessary for position, visibility is not
 
        this.style.display  = "block";
 
        return DOM.elementPos(this.canvas);
 
    },
 
   
 
    /** displays the div */
 
    show: function() {
 
        this.style.display      = "block";
 
        this.style.visibility  = "visible";
 
    },
 
   
 
    /** hides the div */
 
    hide: function() {
 
        this.style.display      = "none";
 
        this.style.visibility  = "hidden";
 
    },
 
   
 
    /** raises the div above all other Floaters */
 
    raise: function() {
 
        var all = Floater.instances;
 
        var idx = all.indexOf(this);
 
        if (idx == -1)  return;
 
        all.splice(idx, 1);
 
        all.push(this);
 
        for (var i=idx; i<all.length; i++) {
 
            all[i].style.zIndex = i + this.minimumZ;
 
        }
 
    },
 
   
 
    /** lower the div blow all other Floaters */
 
    lower: function() {
 
        var all = Floater.instances;
 
        var idx = all.indexOf(this);
 
        if (idx == -1)  return;
 
        all.splice(idx, 1);
 
        all.unshift(this);
 
        for (var i=idx; i>= 0; i++) {
 
            all[i].style.zIndex = i + this.minimumZ;
 
        }
 
    },
 
};
 
 
 
/** all instances z-ordered starting with the lowest */
 
Floater.instances  = [];
 
 
 
//======================================================================
 
//## lib/ui/PopupMenu.js
 
 
 
/** a PopupMenu display a number of items and call a selectFunc when one of the items is selected */
 
function PopupMenu(selectFunc) {
 
    this.selectFunc = selectFunc;
 
    this.floater    = new Floater(null, true);
 
    this.canvas    = this.floater.canvas;
 
    DOM.addClass(this.canvas, "popup-menu-window");
 
    this.canvas.onmouseup  = this.maybeSelectItem.bind(this);
 
}
 
PopupMenu.prototype = {
 
    /** removes this menu */
 
    destroy: function() {
 
        this.canvas.onmouseup  = null;
 
        this.floater.destroy();
 
    },
 
   
 
    /** opens at a given position */
 
    showAt: function(pos) {
 
        this.floater.locate(pos);
 
        this.floater.raise();
 
        this.floater.show();
 
    },
 
   
 
    /** closes the menu */
 
    hide: function() {
 
        this.floater.hide();
 
    },
 
   
 
    /** adds an item, its userdata will be supplied to the selectFunc */
 
    item: function(label, userdata) {
 
        var item    = document.createElement("div");
 
        item.className      = "popup-menu-item";
 
        item.textContent    = label;
 
        item.userdata      = userdata;
 
        this.canvas.appendChild(item);
 
    },
 
 
 
    /** adds a separator */
 
    separator: function() {
 
        var separator  = document.createElement("hr");
 
        separator.className = "popup-menu-separator";
 
        this.canvas.appendChild(separator);
 
    },
 
 
 
    /** calls the selectFunc with the userData of the selected item */
 
    maybeSelectItem: function(ev) {
 
        var target  = ev.target;
 
        for (;;) {
 
            if (DOM.hasClass(target, "popup-menu-item")) {
 
                if (this.selectFunc) {
 
                    this.selectFunc(target.userdata);
 
                }
 
                return;
 
            }
 
            target  = target.parentNode;
 
            if (!target)    return;
 
        }
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/PopupSource.js
 
 
 
/** makes a source open a Floater as context-menu */
 
function PopupSource(source, menu) {
 
    this.source = source;
 
    this.menu  = menu;
 
    DOM.addClass(source, "popup-source");
 
    source.oncontextmenu    = this.contextMenu.bind(this);
 
    this.boundMouseUp      = this.mouseUp.bind(this);
 
    document.addEventListener("mouseup", this.boundMouseUp, false);
 
}
 
PopupSource.prototype = {
 
    mouseupCloseDelay: 250,
 
   
 
    /** removes all listeners */
 
    destroy: function() {
 
        DOM.removeClass(this.source, "popup-source");
 
        this.source.oncontextmenu  = null;
 
        document.removeEventListener("mouseup", this.boundMouseUp, false);
 
    },
 
   
 
    /** opens the Floater near the mouse cursor */
 
    contextMenu: function(ev) {
 
        if (ev.target != this.source)  return;
 
       
 
        var mouse  = DOM.mousePos(ev);
 
        // so the document does not get a mouseup shortly after
 
        mouse.x ++;
 
        this.menu.showAt(mouse);
 
 
 
        // delay closing so the popup stays open after a short klick
 
        this.abortable  = false;
 
        var self        = this;
 
        window.setTimeout(
 
                function() { self.abortable = true; },
 
                this.mouseupCloseDelay);
 
       
 
        // old-style, stop propagation
 
        return false;
 
    },
 
   
 
    /** closes the Floater except within a short time after opening */
 
    mouseUp: function(ev) {
 
        if (this.abortable) {
 
            this.menu.hide();
 
        }
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/Links.js
 
 
 
/** creates links to action functions and pages */
 
Links = {
 
    /**
 
    * create an action link which
 
    * - onclick queries a text or
 
    * - oncontextmenu opens a popup with default texts
 
    * and calls a single-argument function with it.
 
    *
 
    * groups is an Array of Arrays of preset reason Strings,
 
    * a separator is placed between rows. null is allowed to
 
    * disable the popup.
 
    *
 
    * the popupFunc is optional, when it's given it's called instead
 
    * of the func for a popup reason, but not for manual input
 
    */
 
    promptPopupLink: function(label, query, groups, func, popupFunc) {
 
        // the main link calls back with a prompted reason
 
        var mainLink    = this.promptLink(label, query, func);
 
        if (!groups)    return mainLink;
 
 
 
        // optional parameter
 
        if (!popupFunc) popupFunc  = func;
 
        var popup      = new PopupMenu(popupFunc);
 
 
 
        // setup groups of items
 
        for (var i=0; i<groups.length; i++) {
 
            var group  = groups[i];
 
            if (i != 0) popup.separator();
 
            for (var j=0; j<group.length; j++) {
 
                var preset  = group[j];
 
                popup.item(preset, preset);
 
            }
 
        }
 
 
 
        new PopupSource(mainLink, popup);
 
        return mainLink;
 
    },
 
 
 
    /** create an action link which onclick queries a text and calls a function with it */
 
    promptLink: function(label, query, func) {
 
        return this.functionLink(label, function() {
 
            var reason  = prompt(query);
 
            if (reason != null) func(reason);
 
        });
 
    },
 
 
 
    /** create an action link calling a function on click */
 
    functionLink: function(label, func) {
 
        var a  = document.createElement("a");
 
        a.className    = "link-function";
 
        a.onclick      = func;
 
        a.textContent  = label;
 
        return a;
 
    },
 
 
 
    /** create a link to a readURL */
 
    readLink: function(label, title,  args) {
 
        return this.urlLink(label, Wiki.readURL(title, args));
 
    },
 
 
 
    /** create a link to an actionURL */
 
    pageLink: function(label, args) {
 
        return this.urlLink(label, Wiki.encodeURL(args));
 
    },
 
 
 
    /** create a link to an URL within the current list item */
 
    urlLink: function(label, url) {
 
        var a  = document.createElement("a");
 
        a.href          = url;
 
        a.textContent  = label;
 
        return a;
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/ProgressArea.js
 
 
 
/** uses a ProgressArea to display ajax progress */
 
function ProgressArea() {
 
    var close  = closeButton(this.destroy.bind(this));
 
 
 
    var headerDiv  = document.createElement("div");
 
    headerDiv.className = "progress-header";
 
 
 
    var bodyDiv    = document.createElement("div");
 
    bodyDiv.className  = "progress-body";
 
 
 
    var outerDiv    = document.createElement("div");
 
    outerDiv.className  = "progress-area";
 
    outerDiv.appendChild(close);
 
    outerDiv.appendChild(headerDiv);
 
    outerDiv.appendChild(bodyDiv);
 
 
 
    // the mainDiv is a singleton
 
    var mainDiv    = $('progress-global');
 
    if (mainDiv === null) {
 
        mainDiv = document.createElement("div");
 
        mainDiv.id          = 'progress-global';
 
        mainDiv.className  = "progress-global";
 
        DOM.pasteBefore($('bodyContent'), mainDiv);
 
    }
 
    mainDiv.appendChild(outerDiv);
 
 
 
    this.headerDiv  = headerDiv;
 
    this.bodyDiv    = bodyDiv;
 
    this.outerDiv  = outerDiv;
 
 
 
    this.timeout    = null;
 
}
 
ProgressArea.prototype = {
 
    /** display a header text */
 
    header: function(content) {
 
        this.unfade();
 
        DOM.removeChildren(this.headerDiv);
 
        DOM.pasteEnd(this.headerDiv, content);
 
    },
 
 
 
    /** display a body text */
 
    body: function(content) {
 
        this.unfade();
 
        DOM.removeChildren(this.bodyDiv);
 
        DOM.pasteEnd(this.bodyDiv, content);
 
    },
 
 
 
    /** destructor, called by fade */
 
    destroy: function() {
 
        DOM.removeNode(this.outerDiv);
 
    },
 
 
 
    /** fade out */
 
    fade: function() {
 
        this.timeout    = setTimeout(this.destroy.bind(this), ProgressArea.cfg.fadeTime);
 
    },
 
 
 
    /** inihibit fade */
 
    unfade: function() {
 
        if (this.timeout != null) {
 
            clearTimeout(this.timeout);
 
            this.timeout    = null;
 
        }
 
    }
 
};
 
ProgressArea.cfg = {
 
    fadeTime:  750,    // fade delay in millis
 
};
 
 
 
//======================================================================
 
//## lib/ui/FeedbackLink.js
 
 
 
/** implements Feedback to change an ActionLink's link-running class */
 
function FeedbackLink(link) {
 
    this.link  = link;
 
}
 
FeedbackLink.prototype = {
 
    job:        function(s) { },
 
    work:      function(s) { DOM.addClass(this.link, "link-running");      },
 
    success:    function(s) { DOM.removeClass(this.link, "link-running");  },
 
    failure:    function(s) { },
 
};
 
 
 
//======================================================================
 
//## lib/ui/FeedbackArea.js
 
 
 
/** implements Progress delegating to a ProgressArea */
 
function FeedbackArea() {
 
    this.area  = new ProgressArea();
 
}
 
FeedbackArea.prototype = {
 
    // HACK: used when the ProgressArea should be used after success
 
    //fade:    function() { this.area.fade();      },
 
    unfade:    function() { this.area.unfade();    },
 
 
 
    job:        function(s) { this.area.header(s);  },
 
    work:      function(s) { this.area.body(s);    },
 
    success:    function(s) { this.area.body(s);
 
                                this.area.fade();  },
 
    failure:    function(s) { this.area.body(s);    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/Background.js
 
 
 
/** links running in the background */
 
Background = {
 
    /** make a link act in the background, the doneFunc is called wooth the link */
 
    immediatize: function(link, doneFunc) {
 
        DOM.addClass(link, "link-immediate");
 
        link.onclick    = this.immediateOnclick;
 
        link._doneFunc  = doneFunc;
 
    },
 
 
 
    /** onclick handler function for immediateLink */
 
    immediateOnclick: function() {
 
        var link    = this; // (!)
 
        DOM.addClass(link, "link-running");
 
        Ajax.call({
 
            url:        link.href,
 
            doneFunc:  function(source) {
 
                DOM.removeClass(link, "link-running");
 
                if (link._doneFunc) link._doneFunc(link);
 
            }
 
        });
 
        return false;
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/Portlet.js
 
 
 
/** create a portlet which has to be initialized with either createNew or useExisting */
 
function Portlet(id, title, rows, withoutPBody) {
 
    this.outer  = document.createElement("div");
 
    this.outer.id          = id;
 
    this.outer.className    = "portlet";
 
 
 
    if (withoutPBody) {
 
        this.body  = this.outer;
 
    }
 
    else {
 
        this.header = document.createElement("h5");
 
        this.header.textContent = title;
 
 
 
        this.body  = document.createElement("div");
 
        this.body.className = "pBody";
 
 
 
        this.outer.appendChild(this.header);
 
        this.outer.appendChild(this.body);
 
    }
 
 
 
    this.ul    = null;
 
    this.li    = null;
 
    this.canLabel  = {};
 
    this.render(rows);
 
 
 
    // public
 
    this.component  = this.outer;
 
}
 
Portlet.prototype = {
 
    /** change labels of action links */
 
    labelStolen: function(labels) {
 
        for (var id in labels) {
 
            var target = this.canLabel[id];
 
            if (target)    target.textContent = labels[id];
 
        }
 
    },
 
 
 
    render: function(rows) {
 
        if (rows.constructor == Array) {
 
            // add rows
 
            this.ul = document.createElement("ul");
 
            this.body.appendChild(this.ul);
 
            this.renderRows(rows);
 
        }
 
        else {
 
            // add singlerow
 
            this.body.appendChild(rows);
 
        }
 
    },
 
 
 
    renderRows: function(rows) {
 
        for (var y=0; y<rows.length; y++) {
 
            var row = rows[y];
 
            if (row === null)  continue;
 
            if (row.constructor == String) {
 
                // steal row
 
                var element = $(row);
 
                if (element) {
 
                    var clone  = element.cloneNode(true);
 
                    this.ul.appendChild(clone);
 
                    this.canLabel[element.id] = clone.firstChild;
 
                }
 
            }
 
            else if (row.constructor == Array) {
 
                if (row.length == 0)    continue;
 
                // add cells
 
                this.li = document.createElement("li");
 
                this.ul.appendChild(this.li);
 
                this.renderCells(row);
 
            }
 
            else {
 
                // singlecell
 
                this.li = document.createElement("li");
 
                this.ul.appendChild(this.li);
 
                this.li.appendChild(row);
 
            }
 
        }
 
    },
 
 
 
    renderCells: function(row) {
 
        var first  = true;
 
        for (var x=0; x<row.length; x++) {
 
            var cell    = row[x];
 
            if (cell === null)  continue;
 
 
 
            // insert separator
 
            if (!first) this.li.appendChild(document.createTextNode(" "));
 
            else        first  = false;
 
 
 
            if (cell.constructor == String) {
 
                // steal singlerow as cell
 
                var element = $(cell);
 
                // problem: interferes with relabelling later!
 
                if (element) {
 
                    var clone  = element.firstChild.cloneNode(true);
 
                    this.li.appendChild(clone);
 
                    this.canLabel[element.id] = clone;
 
                }
 
            }
 
            else {
 
                // add link
 
                this.li.appendChild(cell);
 
            }
 
        }
 
    },
 
};
 
 
 
//======================================================================
 
//## lib/ui/SideBar.js  
 
 
 
/** encapsulates column-one */
 
SideBar = {
 
    /**
 
    * change labels of action links
 
    * root is a common parent of all items, f.e. document
 
    * labels is a Map from id to label
 
    */
 
    labelItems: function(labels) {
 
        for (var id in labels) {
 
            var el = document.getElementById(id);
 
            if (!el)  continue;
 
            var a  = el.getElementsByTagName("a")[0];
 
            if (!a)    continue;
 
              a.textContent = labels[id];
 
        }
 
    },
 
   
 
    //------------------------------------------------------------------------------
 
 
 
    /** the portlets remembered in createPortlet and sidplayed in showPortlets */
 
    preparedPortlets: [],
 
 
 
    /**
 
    * render an array of arrays of links.
 
    * the outer array may contains strings to steal list items
 
    * null items in the outer array or inner are legal and skipped
 
    * withoutPBody is optional
 
    */
 
    createPortlet: function(id, title, rows, withoutPBody) {
 
        var portlet = new Portlet(id, title, rows, withoutPBody);
 
        this.preparedPortlets.push(portlet);
 
        return portlet;
 
    },
 
 
 
    /** display the portlets created before and remove older ones with the same id */
 
    showPortlets: function() {
 
        var columnOne  = $('column-one');
 
        for (var i=0; i<this.preparedPortlets.length; i++) {
 
            var portlet    = this.preparedPortlets[i];
 
            var replaces    = $(portlet.component.id);
 
            if (replaces)  DOM.removeNode(replaces);
 
            columnOne.appendChild(portlet.component);
 
        }
 
        // HACK for speedup, hidden in sideBar.css
 
        columnOne.style.visibility  = "visible";
 
    },
 
   
 
    //------------------------------------------------------------------------------
 
 
 
    /** adds a div with the site name at the top of the sidebar */
 
    insertSiteName: function() {
 
        var a      = this.siteNameLink();
 
        var heading = DOM.fetch('p-search', "h5", null, 0);
 
        DOM.removeChildren(heading);
 
        heading.appendChild(a);
 
    },
 
   
 
    /** creates a link displaying the site name and linking to the main page */
 
    siteNameLink: function() {
 
        var name        = document.getElementsByTagName("link")[1].title;
 
        var a          = document.createElement("a");
 
        a.id            = "siteName";
 
        a.textContent  = name;
 
        a.href          = Wiki.site;
 
        return a;
 
    },
 
};
 
SideBar.msg = {
 
    show:  "show",
 
    hide:  "hide",
 
};
 
 
 
//======================================================================
 
//## app/extend/ActionHistory.js
 
 
 
/** helper for action=history */
 
ActionHistory = {
 
    /** onload initializer */
 
    init: function() {
 
        if (Page.params["action"] != "history") return;
 
        this.addLinks();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** additional links for every version in a page history */
 
    addLinks: function() {
 
        function addLink(li) {
 
            var diffInput  = DOM.fetch(li, "input", null, 1);
 
            if (!diffInput) return;
 
 
 
            // gather data
 
            var histSpan    = DOM.fetch(li, "span", "history-user", 0);
 
            var histA      = DOM.fetch(histSpan, "a", null, 0);
 
            var dateA      = DOM.nextElement(diffInput, "a");
 
            var oldid      = diffInput.value;
 
            var user        = histA.textContent;
 
            var date        = dateA.textContent;
 
 
 
            var msg = ActionHistory.msg;
 
 
 
            // add restore version link
 
            function done() { window.location.reload(true); }
 
            var summary = msg.restored + " " + user + " " + date;
 
            var restore = FastRestore.linkRestore(Page.title, oldid, summary, done);
 
            var before  = diffInput.nextSibling;
 
            DOM.pasteBefore(before, [ " [", restore, "] "]);
 
 
 
            // add edit link
 
            var edit    = Links.pageLink(msg.edit, {
 
                title:  Page.title,
 
                oldid:  oldid,
 
                action: "edit",
 
            });
 
            var before  = diffInput.nextSibling;
 
            DOM.pasteBefore(before, [ " [", edit, "] "]);
 
        }
 
 
 
        var lis = DOM.fetch('pagehistory', "li");
 
        if (!lis)  return;
 
        for (var i=0; i<lis.length; i++) {
 
            addLink(lis[i]);
 
        }
 
    },
 
};
 
ActionHistory.msg = {
 
    edit:      "edit",
 
    restored:  "revert to revision by ",
 
};
 
 
 
//======================================================================
 
//## app/extend/ActionDiff.js
 
 
 
/** revert in the background for action=diff */
 
ActionDiff = {
 
    /** onload initializer */
 
    init: function() {
 
        if (!Page.params["diff"])  return;    //if (Page.params["action"] != "history")
 
        this.addLinks();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** add restore-links */
 
    addLinks: function() {
 
        var msg = ActionDiff.msg;
 
 
 
        /** extends one of the two sides */
 
        function extend(tdClassName) {
 
            // get cell
 
            var td  = DOM.fetch(document, "td", tdClassName, 0);
 
            if (!td)    return;
 
 
 
            // extract data
 
            var as  = DOM.fetch(td, "a");
 
            if (as.length < 3)  return;
 
            var a0  = as[0];
 
            var a1  = as[1];
 
            var a2  = as[2];
 
            var a3  = as[3];   
 
 
 
            // get oldid
 
            var params  = Wiki.decodeURL(a0.href);
 
            if (!params.oldid)  return;
 
            var oldid  = params.oldid;
 
 
 
            // get version date
 
            var dateP      = ActionDiff.cfg.versionExtractRE(a0.textContent);
 
            var date        = dateP  ? dateP[1]  : null;
 
 
 
            // get version user
 
            var user    = a2.parentNode.nodeName != "STRONG"
 
                        ? a2.textContent    // not a1!
 
                        : a3.textContent;
 
 
 
            // add restore version link
 
            function done() {
 
                window.location.href    = Wiki.encodeURL({
 
                    title:  Page.title,
 
                    action: "history",
 
                });
 
            }
 
            var summary = msg.restored + " " + user + " " + date;
 
            var restore = FastRestore.linkRestore(Page.title, oldid, summary, done);
 
            DOM.pasteBefore(a1, [ restore, " | "]);
 
        }
 
        extend("diff-ntitle");
 
        extend("diff-otitle");
 
    },
 
};
 
ActionDiff.msg = {
 
    restored:  "restored to revision by ",
 
};
 
ActionDiff.cfg = {
 
    // TODO: hardcoded lang_de OR lang_en
 
    versionExtractRE:  /(?:Version vom|Revision as of) (.*)/,
 
};
 
 
 
//======================================================================
 
//## app/extend/Special.js
 
 
 
/** dispatcher for Specialpages */
 
Special = {
 
    /** dispatches calls to Special* objects */
 
    init: function() {
 
        var name    = Page.whichSpecial();
 
        if (!name)      return;
 
 
 
        var feature = window["Special" + name];
 
        if (feature && feature.init) {
 
            feature.init();
 
        }
 
 
 
        var elements    = Special.cfg.autoSubmitElements[name];
 
        if (elements) {
 
            // TODO: HACK: we need the button here -- why?
 
            var withButton  = name == "Watchlist";
 
            this.autoSubmit(document.forms[0], elements, withButton);
 
        }
 
    },
 
 
 
    /** adds an onchange handler to elements in a form submitting the form and removes the submit button. */
 
    autoSubmit: function(form, elementNames, leaveSubmitAlone) {
 
        if (!form)  return;
 
        // if there is only one form, it's the searchform
 
        if (document.forms.length < 2)  return;
 
        var elements    = form.elements;
 
 
 
        function change() { form.submit(); }
 
        for (var i=0; i<elementNames.length; i++) {
 
            var element = elements[elementNames[i]];
 
            if (!element)  continue;
 
            element.onchange    = change;
 
        }
 
 
 
        if (leaveSubmitAlone)  return;
 
        var todo    = [];
 
        for (var i=0; i<elements.length; i++) {
 
            var element = elements[i];
 
            if (element.type == "submit") todo.push(element);
 
        }
 
        for (var i=0; i<todo.length; i++) {
 
            DOM.removeNode(todo[i]);
 
        }
 
    },
 
};
 
Special.cfg = {
 
    /** maps Specialpage names to the autosubmitting form elements */
 
    autoSubmitElements: {
 
        Allpages:      [ "namespace", "nsfrom"    ],
 
        Contributions:  [ "namespace"              ],
 
        Ipblocklist:    [  // default action
 
                            "title",
 
                            // action=unblock
 
                            "wpUnblockAddress", "wpUnblockReason"
 
                        ],
 
        Linksearch:    [ "title"                  ],
 
        Listusers:      [ "group", "username"      ],
 
        Log:            [ "type", "user", "page"    ],
 
        Newimages:      [ "wpIlMatch"              ],
 
        Newpages:      [ "namespace", "username"  ],
 
        Prefixindex:    [ "namespace", "nsfrom"    ],
 
        Recentchanges:  [ "namespace", "invert"    ],
 
        Watchlist:      [ "namespace"              ],
 
       
 
        Booksources:    [ "isbn"                    ],
 
        CategoryTree:  [ "mode",  "target"        ],
 
        Cite:          [ "page"                    ],
 
        Filepath:      [ "file"                    ],
 
        Imagelist:      [ "limit"                  ],
 
        MIMEsearch:    [ "mime"                    ],
 
        Search:        [ "lsearchbox"             ],
 
    },
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialNewpages.js
 
 
 
/** extends Special:Newpages */
 
SpecialNewpages = {
 
    /** onload initializer */
 
    init: function() {
 
        this.displayInline();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** extend Special:Newpages with the content of the articles */
 
    displayInline: function() {
 
        var openCount = 0;
 
 
 
        /** parse one list item, then add folding and the inline view to it */
 
        function extendItem(li) {
 
            // fetch data
 
            var a      = li.getElementsByTagName("a")[0];
 
            var title  = a.title;
 
 
 
            var byteStr = li.innerHTML
 
                    .replace(SpecialNewpages.cfg.bytesExtractRE, "$1")
 
                    .replace(SpecialNewpages.cfg.bytesStripRE,  "");
 
            var bytes  = parseInt(byteStr);
 
 
 
            // make header
 
            var header  =  document.createElement("div");
 
            header.className    = "folding-header";
 
            header.innerHTML    = li.innerHTML;
 
 
 
            // make body
 
            var body    = document.createElement("div");
 
            body.className      = "folding-body";
 
 
 
            // a FoldButton for the header
 
            var foldButton  = new FoldButton(true, function(open) {
 
                body.style.display  = open ? null : "none";
 
                if (open && foldButton.needsLoad) {
 
                    loadContent(li);
 
                    foldButton.needsLoad    = false;
 
                }
 
            });
 
            foldButton.needsLoad    = false;
 
            DOM.pasteBegin(header, foldButton.button);
 
 
 
            // add action links
 
            DOM.pasteBegin(header, UserBookmarks.linkMark(title));
 
            var templateTools  = TemplatePage.bankAllPage(title);
 
            if (templateTools)  DOM.pasteBegin(header, templateTools);
 
            // change listitem
 
            li.pageTitle    = title;
 
            li.contentBytes = bytes;
 
            li.headerDiv    = header;
 
            li.bodyDiv      = body;
 
            li.className    = "folding-container";
 
            li.innerHTML    = "";
 
            li.appendChild(header);
 
            li.appendChild(body);
 
 
 
            if (li.contentBytes <= SpecialNewpages.cfg.sizeLimit
 
            && openCount < SpecialNewpages.cfg.maxArticles) {
 
                loadContent(li);
 
                openCount++;
 
            }
 
            else {
 
                foldButton.change(false);
 
                foldButton.needsLoad    = true;
 
            }
 
        }
 
 
 
        // uses the monobook start content marker
 
        var extractRE  =  /<!-- start content -->([^]*)<div class="printfooter">/;
 
 
 
        /** load the article content and display it inline */
 
        function loadContent(li) {
 
            li.bodyDiv.textContent  = SpecialNewpages.msg.loading;
 
            Ajax.call({
 
                url:        Wiki.readURL(li.pageTitle, { redirect: "no" }),
 
                doneFunc:  function(source) {
 
                    var content = extractRE(source.responseText);
 
                    if (!content)  throw "could not extract article content";
 
                    li.bodyDiv.innerHTML    = content[1] + '<div class="visualClear" />';
 
                    // <div class="noarticletext">
 
                }
 
            });
 
        }
 
 
 
        // find article list
 
        var ol  = DOM.fetch('bodyContent', "ol", null, 0);
 
        if (!ol)    return;
 
        ol.className    = "specialNewPages";
 
 
 
        // find article list items
 
        var lis = DOM.fetch(ol, "li");
 
        for (var i=0; i<lis.length; i++) {
 
            extendItem(lis[i], i);
 
        }
 
    },
 
};
 
SpecialNewpages.cfg = {
 
    maxArticles:    100,
 
    sizeLimit:      2048,
 
 
 
    // TODO: hardcoded lang_de OR lang_en
 
    bytesExtractRE:    /.*\[([0-9.,]+) [Bb]ytes\].*/,
 
    bytesStripRE:      /[.,]/g,
 
};
 
SpecialNewpages.msg = {
 
    loading:    "lade seite..",
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialSpecialpages.js
 
 
 
/** extends Special:Specialpages */
 
SpecialSpecialpages = {
 
    /** onload initializer */
 
    init: function() {
 
        this.extendLinks();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** make a sorted tables from the links */
 
    extendLinks: function() {
 
        var uls = DOM.fetch('bodyContent', "ul", null);
 
        for (var i=uls.length-1; i>=0; i--) {
 
            var ul  = uls[i];
 
            this.extendGroup(ul);
 
        }
 
    },
 
 
 
    /** make a sorted table from the links of one group */
 
    extendGroup: function(ul) {
 
        var lis    = DOM.fetch(ul, "li", null);
 
        var lines  = [];
 
        for (var i=0; i<lis.length; i++) {
 
            var li  = lis[i];
 
            var a  = li.firstChild;
 
            lines.push({
 
                href:  a.href,
 
                title:  a.title,
 
                text:  a.textContent,
 
            });
 
        }
 
        lines.sort(function(a,b) {
 
            return  a.title < b.title ? -1
 
                :  a.title > b.title ? 1
 
                :  0;
 
        });
 
        var table  = document.createElement("table");
 
        for (var i=0; i<lines.length; i++) {
 
            var line    = lines[i];
 
            var tr      = document.createElement("tr");
 
            var td1    = document.createElement("td");
 
            var a      = document.createElement("a");
 
            a.href          = line.href;
 
            a.title        = line.title;
 
            a.textContent  = line.title.scan(Wiki.specialNS + ":");
 
            td1.appendChild(a);
 
            var td2    = document.createElement("td");
 
            var text    = document.createTextNode(line.text);
 
            td2.appendChild(text);
 
            tr.appendChild(td1);
 
            tr.appendChild(td2);
 
            table.appendChild(tr);
 
        }
 
        DOM.pasteBefore(ul, table);
 
        DOM.removeNode(ul);
 
    },
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialRecentchanges.js
 
 
 
/** extensions for Special:Recentchanges */
 
SpecialRecentchanges = {
 
    /** onload initializer */
 
    init: function() {
 
        FilteredEditList.filterLinks("FilteredEditList_SpecialRecentchanges");
 
    },
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialRecentchangeslinked.js
 
 
 
/** extensions for Special:Recentchangeslinked */
 
SpecialRecentchangeslinked = {
 
    /** onload initializer */
 
    init: function() {
 
        FilteredEditList.filterLinks("FilteredEditList_SpecialRecentchangeslinked");
 
    },
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialWatchlist.js
 
 
 
/** extensions for Special:Watchlist */
 
SpecialWatchlist = {
 
    /** onload initializer */
 
    init: function() {
 
        if (Page.params["edit"]) {
 
            var spaces  = this.parseNamespaces();
 
            this.exportLink(spaces);
 
            this.toggleLinks(spaces);
 
        }
 
        else if (Page.params["clear"]) {}
 
        else {
 
            FilteredEditList.filterLinks("FilteredEditList_SpecialWatchlist");
 
        }
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## edit mode
 
 
 
    /** extend Special:Watchlist?edit=yes with a links to a wikitext and a csv version */
 
    exportLink: function(spaces) {
 
        var self    = this;
 
        var link    = Links.functionLink("watchlist.wkp", function() {
 
            window.location.href    = "data:text/plain;charset=utf-8,"
 
                                    + encodeURIComponent(self.renderWikiText(spaces));
 
        });
 
        var target  = DOM.fetch(document, "form", null, 0);
 
        if (!target)    return;
 
        DOM.pasteBefore(target, [ SpecialWatchlist.msg.export1, link ]);
 
    },
 
 
 
    /** render lists of wikilinks */
 
    renderWikiText: function(spaces) {
 
        var wiki    = "";
 
        for (var i=0; i<spaces.length; i++) {
 
            var space  = spaces[i];
 
            wiki    += "== " + space.ns + " ==\n";
 
            var links  = this.parseLinks(space);
 
            for (var j=0; j<links.length; j++) {
 
                var link    = links[j];
 
                wiki    += '*[[' + link.title + ']]'
 
                        +  (link.exists ? "" : " (new)")
 
                        +  '\n';
 
            }
 
            wiki    += "\n";
 
        }
 
        return wiki;
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## toggle-links
 
 
 
    /** extends header structure and add toggle buttons for all checkboxes */
 
    toggleLinks: function(spaces) {
 
        var form    = DOM.fetch(document, "form", null, 0);
 
 
 
        // add a header for the article namespace
 
        var space0  = spaces[0];
 
        space0.h2  = document.createElement("h2");
 
        space0.h2.textContent  = SpecialWatchlist.msg.article;
 
        DOM.pasteBefore(space0.ul, space0.h2);
 
 
 
        // add invert buttons for single namespaces
 
        for (var i=0; i<spaces.length; i++) {
 
            var space  = spaces[i];
 
            var button  = this.toggleButton(space.ul);
 
            //DOM.pasteAfter(space.h2.lastChild, [ ' ', button ]);
 
            DOM.pasteAfter(space.h2, button );
 
        }
 
 
 
        // add  gobal invert button with header
 
        var globalHdr  = document.createElement("h2");
 
        globalHdr.textContent  = SpecialWatchlist.msg.global;
 
        var button  = this.toggleButton(form);
 
        var target  = form.elements["remove"];
 
        DOM.pasteBefore(target,  [
 
            globalHdr, button,
 
            // TODO: ugly HACK
 
            document.createElement("br"),
 
            document.createElement("br"),
 
        ]);
 
    },
 
 
 
    /** creates a toggle button for all input children of an element */
 
    toggleButton: function(container) {
 
        return Links.functionLink(SpecialWatchlist.msg.invert, function() {
 
            var inputs  = container.getElementsByTagName("input");
 
            for (var i=0; i<inputs.length; i++) {
 
                var el  = inputs[i];
 
                if (el.type == "checkbox")
 
                    el.checked  = !el.checked;
 
            }
 
        });
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## list parser
 
 
 
    parseNamespaces: function() {
 
        var out    = [];
 
        var form    = DOM.fetch(document, "form", null, 0);
 
        var uls    = DOM.fetch(form, "ul");
 
        for (var i=0; i<uls.length; i++) {
 
            var ul  = uls[i];
 
            var h2  = DOM.previousElement(ul);
 
            var ns  = h2 ? h2.textContent : "";
 
            out.push({ ul: ul, h2: h2, ns: ns });
 
        }
 
        return out;
 
    },
 
 
 
    parseLinks: function(space) {
 
        var out = [];
 
        var lis = DOM.fetch(space.ul, "li");
 
        for (var j=0; j<lis.length; j++) {
 
            var li      = lis[j];
 
            var a      = DOM.fetch(li, "a", null, 0);
 
            var title  = a.title;
 
            var exists  = a.className != "new"; // TODO: use hasClass
 
            out.push({ title: title, exists: exists });
 
        }
 
        return out;
 
    },
 
};
 
SpecialWatchlist.msg = {
 
    export1:    "export as WikiText: ",
 
 
 
    invert:    "Invertieren",
 
    article:    "Artikel",
 
    global:    "Alle",
 
};
 
 
 
//======================================================================
 
//## app/extend/SpecialPrefixindex.js
 
 
 
/** extends Special:Prefixindex */
 
SpecialPrefixindex = {
 
    /** onload initializer */
 
    init: function() {
 
        this.sortItems();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** sort items into a straight list */
 
    sortItems: function() {
 
        var table  = DOM.fetch('bodyContent', "table", null, 2);
 
        if (!table) return; // no search results
 
        var tds    = DOM.fetch(table, "td");
 
        var ol      = document.createElement("ol");
 
        for (var i=0; i<tds.length; i++) {
 
            var td  = tds[i];
 
            var li  = document.createElement("li");
 
            var c  = td.firstChild.cloneNode(true)
 
            li.appendChild(c);
 
            ol.appendChild(li);
 
        }
 
        table.parentNode.replaceChild(ol, table);
 
    },
 
};
 
 
 
//======================================================================
 
//## app/feature/ForSite.js
 
 
 
/** links for the whole siwe */
 
ForSite = {
 
    /** a link to new pages */
 
    linkNewpages: function() {
 
        return Links.pageLink(ForSite.msg.newpages, {
 
            title:  Wiki.specialTitle("Newpages"),
 
            limit:  20,
 
        });
 
    },
 
 
 
    /** a link to new pages */
 
    linkNewusers: function() {
 
        return Links.pageLink(ForSite.msg.newusers, {
 
            title:  Wiki.specialTitle("Log"),
 
            type:  "newusers",
 
            limit:  50,
 
        });
 
    },
 
 
 
    /** a bank of links to interesting pages */
 
    bankProjectPages: function() {
 
        var pages  = ForSite.cfg.projectPages[Wiki.site];
 
        if (!pages) return null;
 
        var out = [];
 
        for (var i=0; i<pages.length; i++) {
 
            var page    = pages[i];
 
            var link    =  Links.readLink(page[0], page[1]);
 
            out.push(link);
 
        }
 
        return out;
 
    },
 
 
 
    /** return a link for fast logfiles access */
 
    linkAllLogsPopup: function() {
 
        function selected(userdata) {
 
            window.location.href    = Wiki.readURL(Wiki.specialTitle("Log", userdata.toLowerCase()));
 
        }
 
        return this.linkAllPopup(
 
            ForSite.msg.logLabel,
 
            Wiki.specialTitle("Log"),
 
            ForSite.cfg.logs,
 
            selected);
 
    },
 
 
 
    /** return a link for fast logfiles access */
 
    linkAllSpecialsPopup: function() {
 
        function selected(userdata) {
 
            window.location.href    = Wiki.readURL(Wiki.specialTitle(userdata));
 
        }
 
        return this.linkAllPopup(
 
            ForSite.msg.specialLabel,
 
            Wiki.specialTitle("Specialpages"),
 
            ForSite.cfg.specials,
 
            selected);
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** returns a linkPopup */
 
    linkAllPopup: function(linkLabel, mainPage, pages, selectFunc) {
 
        var mainLink    = Links.readLink(linkLabel, mainPage);
 
        var popup      = new PopupMenu(selectFunc);
 
        for (var i=0; i<pages.length; i++) {
 
            var page    = pages[i];
 
            popup.item(page, page); // the page is the userdata
 
        }
 
        new PopupSource(mainLink, popup);
 
        return mainLink;
 
    },
 
}
 
ForSite.cfg = {
 
    /** maps sites to an Array of interesting pages */
 
    projectPages: {
 
        "http://ru-sib.wikipedia.org": [
 
            [  "Vip",  "Wiktionary:Vandalism in progress"            ],
 
            [  "Rfd",  "Wiktionary:Requests for deletion"            ],
 
           
 
        ],
 
        "http://de.wikiversity.org": [
 
            [  "Löschen",  "Kategorie:Wikiversity:Löschen" ],
 
        ],
 
    },
 
 
 
    /** which logs are displayed in the opoup */
 
    logs: [
 
        "Move", "Block", "Protect", "Delete", "Upload"
 
       
 
    ],
 
 
 
    /** which specialpages are displayed in the opoup */
 
    specials:[
 
        "Allmessages", "Allpages", "CategoryTree", "Ipblocklist", "Linksearch", "Listusers", "Newimages", "Prefixindex",
 
       
 
    ],
 
};
 
ForSite.msg = {
 
    logLabel:      "Logs",
 
    specialLabel:  "Spezial",
 
 
 
    newpages:      "Neuartikel",
 
    newusers:      "Newbies",
 
};
 
 
 
//======================================================================
 
//## app/feature/ForPage.js
 
 
 
/** links for arbitrary pages */
 
ForPage = {
 
    /** returns a link to the logs for a given page */
 
    linkLogAbout: function(title) {
 
        return Links.pageLink(ForPage.msg.pageLog,  {
 
            title:  Wiki.specialTitle("Log"),
 
            page:  title
 
        });
 
    },
 
};
 
ForPage.msg = {
 
    pageLog:    "Seitenlog",
 
};
 
 
 
//======================================================================
 
//## app/feature/ForUser.js
 
 
 
/** links for users */
 
ForUser = {
 
    /** returns a link to the homepage of a user */
 
    linkHome: function(user) {
 
        return Links.readLink(ForUser.msg.home, Wiki.userNS  + ":" + user);
 
    },
 
 
 
    /** returns a link to the talkpage of a user */
 
    linkTalk: function(user) {
 
        return Links.readLink(ForUser.msg.talk, Wiki.userTalkNS  + ":" + user);
 
    },
 
 
 
    /** returns a link to new messages or null when none exist */
 
    linkNews: function(user) {
 
        return Links.readLink(ForUser.msg.news, Wiki.userTalkNS + ":" + user, { diff: "cur" });
 
    },
 
 
 
    /** returns a link to a users contributions */
 
    linkContribs: function(user) {
 
        return Links.readLink(ForUser.msg.contribs, Wiki.specialTitle("Contributions", user));
 
    },
 
 
 
 
 
    /** returns a link to a users emailpage */
 
    linkEmail: function(user) {
 
        return Links.readLink(ForUser.msg.email,    Wiki.specialTitle("Emailuser", user));
 
    },
 
 
 
    /** returns a link to a users log entries */
 
    linkLogsAbout: function(user) {
 
        return Links.pageLink(ForUser.msg.logsAbout, {
 
            title:  Wiki.specialTitle("Log"),
 
            page:  Wiki.userNS + ":" + user
 
        });
 
    },
 
 
 
    /** returns a link to a users log entries */
 
    linkLogsActor: function(user) {
 
        return Links.pageLink(ForUser.msg.logsActor, {
 
            title:  Wiki.specialTitle("Log"),
 
            user:  user
 
        });
 
    },
 
 
 
    /** returns a link to show subpages of a user */
 
    linkSubpages: function(user) {
 
        return Links.pageLink(ForUser.msg.subpages, {
 
            title:      Wiki.specialTitle("Prefixindex"),
 
            namespace:  2,  // User
 
            from:      user + "/",
 
        });
 
    },
 
 
 
    /** whois check */
 
    linkWhois: function(user) {
 
        return Links.urlLink(ForUser.msg.whois,
 
                "http://www.iks-jena.de/cgi-bin/whois?submit=Suchen&charset=iso-8859-1&search=" + user);
 
        //return "http://www.ripe.net/fcgi-bin/whois?form_type=simple&full_query_string=&&do_search=Search&searchtext=" + ip;
 
    },
 
 
 
    /** senderbase check */
 
    linkSenderbase: function(user) {
 
        return Links.urlLink(ForUser.msg.senderbase,
 
                "http://www.senderbase.org/search?searchString=" + user);
 
    },
 
};
 
ForUser.msg = {
 
    home:      "Benutzer",
 
    talk:      "Diskussion",
 
    email:      "Anmailen",
 
    contribs:  "Beiträge",
 
 
 
    news:      "☏",
 
    logsAbout:  "Logs",
 
    logsActor:  "Logs",
 
    subpages:  "Subs",
 
 
 
    whois:      "Whois",
 
    senderbase: "Senderbase",
 
};
 
 
 
//======================================================================
 
//## app/feature/FilteredEditList.js
 
 
 
/** filters edit lists by name/ip */
 
FilteredEditList = {
 
    /** onload initializer */
 
    filterLinks: function(cookieName) {
 
        var bodyContent = $('bodyContent');
 
 
 
        // tag list items with a CSS class "is-ip" or "is-named"
 
        var uls = DOM.fetch(bodyContent, "ul", "special");
 
        for (var i=0; i<uls.length; i++) {
 
            var ul  = uls[i];
 
            var lis = DOM.fetch(ul, "li");
 
            for (var j=0; j<lis.length; j++) {
 
                var li  = lis[j];
 
                var as  = DOM.fetch(li, "a", null);
 
                // new articles do not have a previous version link
 
                var a  = as[0].previousSibling.textContent.length == 1
 
                        ? as[3] : as[2];
 
                if (IP.isV4(a.textContent)) DOM.addClass(li, "is-ip");
 
                else                        DOM.addClass(li, "is-named");
 
            }
 
        }
 
 
 
        /** changes the filter state */
 
        function update(link, state) {
 
            board.select(link);
 
            if (state == "named")  DOM.addClass(  bodyContent, "hide-ip");
 
            else                    DOM.removeClass(bodyContent, "hide-ip");
 
            if (state == "ip")      DOM.addClass(  bodyContent, "hide-named");
 
            else                    DOM.removeClass(bodyContent, "hide-named");
 
            Cookie.set(cookieName, state);
 
        }
 
 
 
        /** adds a filter-change link to the switchBoard */
 
        function action(state) {
 
            var link    = Links.functionLink(
 
                FilteredEditList.msg.state[state],
 
                function() { update(link, state); }
 
            );
 
            board.add(link);
 
            if (state == initial)  update(link, state);
 
        }
 
 
 
        // create state switchboard
 
        var initial = Cookie.get(cookieName);
 
        if (!initial)  initial = "all";
 
        var states  = [ "all", "named", "ip" ];
 
        var board  = new SwitchBoard();
 
        for (var i=0; i<states.length; i++) action(states[i]);
 
 
 
        var target  = DOM.fetch(document, "form", null, 0);
 
        if (!target)    return;
 
        // TODO:  HACK for SpecialRecentchangeslinked which does not have a form
 
        if (target.id == "searchform") {
 
            target  = DOM.fetch($('bodyContent'), "h4", null, 0);
 
            if (!target)    return;
 
            target  = target.previousSibling;
 
        }
 
 
 
        // TODO:  HACK to get some space
 
        var br  = document.createElement("br");
 
        br.style.lineHeight = "30%";
 
 
 
        DOM.pasteAfter(target, [
 
            br,
 
            FilteredEditList.msg.intro,
 
            board.component
 
        ]);
 
    },
 
};
 
FilteredEditList.msg = {
 
    intro:  "Filter: ",
 
    state: {
 
        all:    "Alle Edits",
 
        ip:    "nur von Ips",
 
        named:  "nur von Angemeldeten",
 
    },
 
};
 
 
 
//======================================================================
 
//## app/feature/FastWatch.js
 
 
 
/** page watch and unwatch without reloading the page */
 
FastWatch = {
 
    init: function() {
 
        /** initialize link */
 
        function initView() {
 
            var watch  = $('ca-watch');
 
            var unwatch = $('ca-unwatch');
 
            if (watch)      exchangeItem(watch,    true);
 
            if (unwatch)    exchangeItem(unwatch,  false);
 
        }
 
 
 
        /** talk to the server, then updates the UI */
 
        function changeState(link, watched) {
 
            function update() {
 
                var watch  = $('ca-watch');
 
                var unwatch = $('ca-unwatch');
 
                if ( watched && watch  )    exchangeItem(watch,    false);
 
                if (!watched && unwatch)    exchangeItem(unwatch,  true);
 
            }
 
            var feedback    = new FeedbackLink(link);
 
            Editor.watchedPage(feedback, Page.title, watched, update);
 
        }
 
 
 
        /** create a li with a link in it */
 
        function exchangeItem(target, watchable) {
 
            var li      = document.createElement("li");
 
            li.id      = watchable ? "ca-watch"            : "ca-unwatch";
 
            var label  = watchable ? FastWatch.msg.watch  : FastWatch.msg.unwatch;
 
            var a      = Links.functionLink(label, function() {
 
                changeState(a, watchable);
 
            });
 
            DOM.addClass(a, "link-immediate");
 
            li.appendChild(a);
 
            target.parentNode.replaceChild(li, target);
 
        }
 
 
 
        initView();
 
    },
 
};
 
FastWatch.msg = {
 
    watch:      "Beobachten",
 
    unwatch:    "Entobachten",
 
};
 
 
 
//======================================================================
 
//## app/feature/FastRestore.js
 
 
 
/** page restore mechanisms */
 
FastRestore = {
 
    /** returns a link restoring a given version */
 
    linkRestore: function(title, oldid, summary, doneFunc) {
 
        var restore = Links.functionLink(FastRestore.msg.restore, function() {
 
            var feedback    = new FeedbackLink(restore);
 
            Editor.restoreVersion(feedback, title, oldid, summary, doneFunc);
 
        });
 
        DOM.addClass(restore, "link-immediate");
 
        return restore;
 
    },
 
};
 
FastRestore.msg = {
 
    restore:    "restore",
 
};
 
 
 
//======================================================================
 
//## app/feature/TemplatePage.js
 
 
 
/** puts templates into the current page */
 
TemplatePage = {
 
    /** return an Array of links to actions for normal pages */
 
    bankAllPage: function(title) {
 
        // TODO: does not make sense on other wikis
 
        if (Wiki.site != "http://ru-sib.wikipedia.org") return null;
 
 
 
        var msg    = TemplatePage.msg;
 
        var self    = this;
 
        return [
 
            Links.promptLink(msg.rfv.label,  msg.rfv.prompt,  function(reason) { self.rfv(title, reason);  }),
 
            Links.promptLink(msg.rfd.label,  msg.rfd.prompt,  function(reason) { self.rfd(title, reason);  }),
 
            Links.promptLink(msg.delete.label, msg.delete.prompt, function(reason) { self.delete(title, reason); }),
 
        ];
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** puts a delete template into an article */
 
    delete: function(title, reason) {
 
        this.simple(title, "delete", reason);
 
    },
 
 
 
    /** puts an QS template into an article */
 
    rfv: function(title, reason) {
 
        this.enlist(title, "rfv", reason);
 
    },
 
 
 
    /** puts an LA template into an article */
 
    rfd: function(title, reason) {
 
        this.enlist(title, "rfd", reason);
 
    },
 
 
 
    /** puts a simple template into an article */
 
    simple: function(title, template, reason) {
 
        var r      = Markup;
 
        var summary = r.template_ + template + r._template_ + reason + r._template;
 
        var text    = r.template_ + template + r._template_ + reason + r.sigapp + r._template;
 
        var sepa    = r.lf;
 
        var feedback    = new FeedbackArea();
 
        Editor.prependText(feedback, title,  text, summary, sepa, false, this.maybeReloadFunc(title));
 
    },
 
 
 
    /** list page on a list page */
 
    enlist: function(title, template, listPage, reason) {
 
        var r          = Markup;
 
        var self        = this;
 
        var feedback    = new FeedbackArea();
 
        // insert template
 
        function phase1() {
 
            var summary = r.template_ + template + r._template_ + reason + r._template;
 
            var text    = r.template_ + template + r._template_ + reason + r.sigapp + r._template;
 
            var sepa    = r.lf;
 
            Editor.prependText(feedback, title,  text, summary, sepa, false, phase2);
 
        }
 
        // add to list page
 
        function phase2() {
 
            var page    = listPage + "/" + self.currentDate();
 
            var text    = r.h2_ + r.link_ + title + r._link + r._h2 + r.lf + reason + r.sigapp;
 
            var summary = r.link_ + title + r._link + r.sp + r.dash + r.sp + reason;
 
            var sepa    = r.lf;
 
            Editor.appendText(feedback, page, text, summary, sepa, true, self.maybeReloadFunc(title));
 
        }
 
        phase1();
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## helper
 
 
 
    /** creates a function to reload the current page, if it has the given title */
 
    maybeReloadFunc: function(title) {
 
        return function() {
 
            if (Page.title == title) {
 
                window.location.href    = Wiki.readURL(title);
 
            }
 
        }
 
    },
 
 
 
    /** returns the current date in the format the LKs are organized */
 
    currentDate: function() {
 
        var months  = [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli",
 
                        "August", "September", "Oktober", "November", "Dezember" ];
 
        var now    = new Date();
 
        var year    = now.getYear();
 
        if (year < 999) year    += 1900;
 
        return now.getDate() + ". " + months[now.getMonth()] + " " + year;
 
    },
 
};
 
TemplatePage.msg = {
 
    rfv: {
 
        label:  "rfv",
 
        prompt: "rfv - reason?",
 
    },
 
    rfd: {
 
        label:  "rfd",
 
        prompt: "rfd - reason?",
 
    },
 
    delete: {
 
        label:  "delete",
 
        prompt: "delete - reason?",
 
    },
 
};
 
 
 
//======================================================================
 
//## app/feature/TemplateTalk.js
 
 
 
/** puts templates into user talkpages */
 
TemplateTalk = {
 
    /** return an Array of links for userTalkPages or null if none exist */
 
    bankOfficial: function(user) {
 
        var talks  = TemplateTalk.cfg.officialTalks;
 
        return talks && talks.length != 0 && Wiki.site == "http://ru-sib.wikipedia.org"
 
                ? this.talksArray(user, talks, false, false)
 
                : null;
 
    },
 
 
 
    /** return an Array of links for userTalkPages or null if none exist */
 
    bankPersonal: function(user) {
 
        var talks  = TemplateTalk.cfg.personalTalks;
 
        return talks && talks.length != 0
 
                ? this.talksArray(user, talks, true, true)
 
                : null;
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** returns an Array of links to "talk" to a user in different templates */
 
    talksArray: function(user, templateNames, ownTemplate, dashSig) {
 
        var out = [];
 
        for (var i=0; i<templateNames.length; i++) {
 
            out.push(this.linkTalkTo(user, templateNames[i], ownTemplate, dashSig));
 
        }
 
        return out;
 
    },
 
 
 
    /** creates a link to "talk" to a user */
 
    linkTalkTo: function(user, templateName, ownTemplate, dashSig) {
 
        var self    = this;
 
        // this is simple currying!
 
        function handler() { self.talkTo(user, templateName, ownTemplate, dashSig); }
 
        var link    = Links.functionLink(templateName, handler);
 
        DOM.addClass(link, "link-immediate");
 
        return link;
 
    },
 
 
 
    /** puts a signed talk-template into a user's talkpage */
 
    talkTo: function(user, templateName, ownTemplate, dashSig) {
 
        var r      = Markup;
 
        var title  = Wiki.userTalkNS + ":" + user;
 
        var text    =  r.template_ + "subst:";
 
        if (ownTemplate)
 
            text    += Wiki.userNS + ":" + Wiki.user + "/";
 
        text        += templateName + r._template + r.sp;
 
        if (dashSig)
 
            text    += r.dash + r.sp;
 
        text        += r.sig + r.lf;
 
        var sepa    = r.line + r.lf;
 
        var feedback    = new FeedbackArea();
 
        Editor.appendText(feedback, title, text, templateName, sepa, true, this.maybeReloadFunc(title));
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## helper
 
 
 
    /** creates a function to reload the current page, if it has the given title */
 
    maybeReloadFunc: function(title) {
 
        return function() {
 
            if (Page.title == title) {
 
                window.location.href    = Wiki.readURL(title);
 
            }
 
        }
 
    },
 
};
 
TemplateTalk.cfg = {
 
    // TODO: hardcoded wiktionary_en
 
    officialTalks:  [ "Welcome", "test", "test2" ],
 
    personalTalks:  [], // below User:Name/
 
};
 
 
 
//======================================================================
 
//## app/feature/UserPage.js
 
 
 
/** cares for pages in the user namespace */
 
UserPage = {
 
    /** create bank of readLinks to private pages */
 
    bankGoto: function() {
 
        function addLink(name) {
 
            var link    = Links.readLink(name, Wiki.userNS + ":" + Wiki.user + "/" + name);
 
            out.push(link);
 
        }
 
        var out    = [];
 
        var names  = UserPage.cfg.pages;
 
        if (names == null
 
        || names.length == 0)  return null;
 
        for (var i=0; i<names.length; i++) {
 
            addLink(names[i]);
 
        }
 
        return out;
 
    },
 
};
 
UserPage.cfg = {
 
    pages: null,    // [ "tmp", ... ]
 
};
 
 
 
//======================================================================
 
//## app/feature/UserBookmarks.js
 
 
 
/** manages a personal bookmarks page  */
 
UserBookmarks = {
 
    /** return an Array of links for a lemma. if it's left out, uses the current page */
 
    bankView: function(lemma) {
 
        return [ this.linkView(), this.linkMark(lemma) ];
 
    },
 
 
 
    /** return the absolute page link */
 
    linkView: function() {
 
        return Links.readLink(UserBookmarks.msg.view, this.pageTitle());
 
    },
 
 
 
    /** add a bookmark on a user's bookmark page. if the page is left out, the current is added */
 
    linkMark: function(lemma) {
 
        var self    = this;
 
        var msg    = UserBookmarks.msg;
 
        var cfg    = UserBookmarks.cfg;
 
        return Links.promptPopupLink(msg.add, msg.prompt, cfg.reasons, function(reason) {
 
            if (lemma)  self.arbitrary(reason, lemma);
 
            else        self.current(reason);
 
        });
 
    },
 
 
 
    //------------------------------------------------------------------------------
 
    //## private
 
 
 
    /** add a bookmark for an arbitrary page */
 
    arbitrary: function(remark, lemma) {
 
        var text    = "*\[\[:" + lemma + "\]\]";
 
        if (remark) text    += " " + remark;
 
        text        += "\n";
 
        this.prepend(text);
 
    },
 
 
 
    /** add a bookmark on a user's bookmark page */
 
    current: function(remark) {
 
        var text    = Markup.star;
 
        var lemma  = Page.title;
 
        if (Page.whichSpecial()) {
 
            // HACK: ensure the title is smushed
 
            var temp    = copyOf(Page.params);
 
            Wiki.smush(temp, Wiki.specialPageInfo(temp.title));
 
            lemma  = temp.title;
 
           
 
            if (temp._smushed) {
 
                // TODO: should add smushable values, not only really smushed values
 
                lemma  += "/" + temp._smushed.value;
 
            }
 
            var params  = {
 
                title:  lemma,
 
            };
 
            for (var key in temp) {
 
                if (key == "title")            continue;
 
                if (key == "_smushed")          continue;
 
                if (temp._smushed
 
                && key == temp._smushed.key)    continue;
 
                params[key] = temp[key];
 
            }
 
           
 
            // check whether any unsmushed parameters are left
 
            var leftUnsmushed  = false;
 
            for (var key in params) {
 
                if (key == "title") continue;
 
                leftUnsmushed  = true;
 
                break;
 
            }
 
           
 
            if (leftUnsmushed) {
 
                text    += Markup.web_ +Wiki.encodeURL(params) + Markup._web_ + lemma + Markup._web;
 
            }
 
            else {
 
                text    +=  Markup.link_ + ":" + lemma + Markup._link;
 
            }
 
        }
 
        else {
 
            var mode    = "perma";
 
            var perma  = Page.perma;
 
            if (!perma) {
 
                var params  = Page.params;
 
                var oldid  = params["oldid"];
 
                if (oldid) {
 
                    var diff    = params["diff"];
 
                    if (diff) {
 
                        mode    = "diff";
 
                        if (diff == "prev"
 
                        ||  diff == "next"
 
                        ||  diff == "next"
 
                        ||  diff == "cur")  mode    = diff;
 
                        else
 
                        if (diff == "cur"
 
                        ||  diff == "0")    mode    = "cur";
 
                        perma  = Wiki.encodeURL({
 
                            title:  lemma,
 
                            oldid:  oldid,
 
                            diff:  diff,
 
                        });
 
                    }
 
                    else {
 
                        mode    = "old";
 
                        perma  = Wiki.encodeURL({
 
                            title:  lemma,
 
                            oldid:  oldid,
 
                        });
 
                    }
 
                }
 
            }
 
            text += Markup.link_ + ":" + lemma + Markup._link;
 
            if (perma)  text    += " <small>[" + perma + " " + mode + "]</small>";
 
        }
 
        if (remark) text    += " " + remark;
 
        text        += Markup.lf;
 
        this.prepend(text);
 
    },
 
 
 
    /** add text to the bookmarks page */
 
    prepend: function(text) {
 
        var feedback    = new FeedbackArea();
 
        Editor.prependText(feedback, this.pageTitle(), text, "", null, true);
 
    },
 
 
 
    /** the title of the current user's bookmarks page */
 
    pageTitle: function() {
 
        return Wiki.userNS + ":" + Wiki.user + "/" + UserBookmarks.cfg.pageTitle;
 
    }
 
};
 
UserBookmarks.cfg = {
 
    pageTitle:  "bookmarks",
 
    reasons:    null,
 
};
 
UserBookmarks.msg = {
 
    view:      "Bookmarks",
 
    add:        "Merken",
 
    prompt:    "Bookmark - Kommentar?",
 
};
 
 
 
//======================================================================
 
//## app/feature/EditWarning.js
 
 
 
/** displays a stop hand behind the edit link on other people's user page */
 
EditWarning = {
 
    init: function() {
 
        var name    = Page.title.scan(Wiki.userNS + ":");
 
        if (!name)                      return;
 
        if (name.indexOf("/") != -1)    return;
 
        if (name == Wiki.user)          return;
 
       
 
        var ed  = $('ca-edit');
 
        if (!ed)    return;
 
        var a  = DOM.fetch(ed, "a", null, 0);
 
        if (!a)    return;
 
        a.style.background = "left url(http://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Stop_hand.png/32px-Stop_hand.png);";
 
    },
 
};
 
 
 
//======================================================================
 
//## app/portlet/Search.js
 
 
 
/** #p-search */
 
Search = {
 
    /** remove the go button, i want to search */
 
    init: function() {
 
        var remove  = Search.cfg.goNotSearch ? 'fulltext' : 'go';
 
        var node    = document.forms['searchform'].elements[remove];
 
        DOM.removeNode(node);
 
    },
 
};
 
Search.cfg = {
 
    goNotSearch: false,
 
};
 
 
 
//======================================================================
 
//## app/portlet/Lang.js
 
 
 
/** #p-lang */
 
Lang = {
 
    id: 'p-lang',
 
 
 
    /** insert a select box to replace the pLang portlet */
 
    init: function() {
 
        var pLang  = $(this.id);
 
        if (!pLang) return;
 
 
 
        var select  = document.createElement("select");
 
        select.id  = "langSelect";
 
        select.options[0]  = new Option(Lang.msg.select, "");
 
 
 
        var list    = pLang.getElementsByTagName("a");
 
        for (var i=0; i<list.length; i++) {
 
            var a      = list[i];
 
            var label  = a.textContent
 
                            .replace(/\s*\/.*/, "");
 
            select.options[i+1] = new Option(label, a.href);
 
        }
 
 
 
        select.onchange = function() {
 
            var selected    = this.options[this.selectedIndex].value;
 
            if (selected == "") return;
 
            window.location.href    = selected;
 
        }
 
 
 
        SideBar.createPortlet(this.id, Lang.msg.title, select);
 
    },
 
};
 
Lang.msg = {
 
    title:  "Languages",
 
 
 
    select: "auswählen",
 
};
 
 
 
//======================================================================
 
//## app/portlet/Cactions.js
 
 
 
/** #p-cactions */
 
Cactions = {
 
    id: "p-cactions",
 
 
 
    init: function() {
 
        this.unfix();
 
 
 
        SideBar.labelItems(Cactions.msg.labels);
 
 
 
        if (Page.namespace >= 0) {
 
            this.addTab('ca-logs',
 
                    ForPage.linkLogAbout(Page.title));
 
        }
 
 
 
        // bugfix: diskussion pages lick to action=edit without a local description page
 
        if (Wiki.site != "http://commons.wikimedia.org") {
 
            var tab = $('ca-nstab-image');
 
            if (tab) {
 
                var a  = tab.firstChild;
 
                a.href  = a.href.replace(/&action=edit$/, "");
 
            }
 
        }
 
       
 
    },
 
 
 
    /** move p-cactions out of column-one so it does not inherit its position:fixed */
 
    unfix: function() {
 
        var pCactions      = $(this.id);
 
        var columnContent  = $('column-content');  // belongs to the SideBar somehow..
 
        pCactions.parentNode.removeChild(pCactions);
 
        columnContent.insertBefore(pCactions, columnContent.firstChild);
 
    },
 
 
 
    /** adds a tab */
 
    addTab: function(id, content) {
 
        // ta[id] = ['g', 'Show logs for this page'];
 
        var li = document.createElement("li");
 
        li.id  = id;
 
        li.appendChild(content);
 
        var tabs    = DOM.fetch(this.id, "ul", null, 0);
 
        tabs.appendChild(li);
 
    },
 
};
 
Cactions.msg = {
 
    labels: {
 
        'ca-talk':          "Diskussion",
 
        'ca-edit':          "Bearbeiten",
 
        'ca-viewsource':    "Source",
 
        'ca-history':      "History",
 
        'ca-move':          "Verschieben",
 
       
 
    },
 
};
 
 
 
//======================================================================
 
//## app/portlet/Tools.js
 
 
 
/** # p-tb */
 
Tools = {
 
    id: 'p-tb',
 
 
 
    init: function() {
 
        var tools1  = [];
 
        if (Page.editable) {
 
        }
 
        if (tools1.length == 0) tools1  = null;
 
 
 
        var tools2  = null;
 
        if (Page.editable) {       
 
            tools2  = TemplatePage.bankAllPage(Page.title);
 
        }
 
 
 
        SideBar.createPortlet(this.id, Tools.msg.title, [
 
            tools1,
 
            tools2,
 
            UserBookmarks.bankView(),
 
            UserPage.bankGoto(),
 
        ]);
 
    },
 
};
 
Tools.msg = {
 
    title:  "Tools",
 
};
 
 
 
//======================================================================
 
//## app/portlet/Navigation.js
 
 
 
/** #p-navigation */
 
Navigation = {
 
    id: 'p-navigation',
 
 
 
    init: function() {
 
       
 
        SideBar.createPortlet(this.id, Navigation.msg.title, [
 
            [  'n-recentchanges',
 
                'pt-watchlist',
 
            ],
 
            [  ForSite.linkNewusers(),
 
                ForSite.linkNewpages(),
 
            ],
 
           
 
            ForSite.bankProjectPages(),
 
            [  ForSite.linkAllLogsPopup(),
 
                ForSite.linkAllSpecialsPopup(),
 
            ],
 
            // 't-specialpages',
 
            // 't-permalink',
 
            [  't-recentchangeslinked',
 
                't-whatlinkshere',
 
            ],
 
        ]).labelStolen(Navigation.msg.labels);
 
    },
 
};
 
Navigation.msg = {
 
    title:  "Navigation",
 
 
 
    labels: {
 
        'n-recentchanges':          "Changes",
 
        'pt-watchlist':            "Watchlist",
 
        't-whatlinkshere':          "Hierher",
 
        't-recentchangeslinked':    "Umgebung",
 
    },
 
};
 
 
 
//======================================================================
 
//## app/portlet/Personal.js
 
 
 
/** #p-personal */
 
Personal = {
 
    // cannot use p-personal which has way too much styling
 
    id: 'p-personal2',
 
 
 
    init: function() {
 
        SideBar.createPortlet(this.id, Personal.msg.title, [
 
            [  'pt-userpage',
 
                'pt-mytalk',
 
                ( Wiki.haveNews() ? ForUser.linkNews(Wiki.user) : null )
 
            ],
 
            [  ForUser.linkSubpages(Wiki.user),
 
                ForUser.linkLogsActor(Wiki.user),
 
                'pt-mycontris',
 
            ],
 
            [  'pt-preferences',
 
                'pt-logout'
 
            ],
 
        ]).labelStolen(Personal.msg.labels);
 
    },
 
 
 
};
 
Personal.msg = {
 
    title:      "Persönlich",
 
 
 
    labels: {
 
        'pt-mytalk':        "Diskussion",
 
        'pt-mycontris':    "Beiträge",
 
        'pt-preferences':  "Prefs",
 
        'pt-logout':        "Logout",
 
    },
 
};
 
 
 
//======================================================================
 
//## app/portlet/Communication.js
 
 
 
/** #p-communication: communication with Page.owner */
 
Communication = {
 
    id: 'p-communication',
 
 
 
    init: function() {
 
        if (!Page.owner)                return;
 
        if (Page.owner == Wiki.user)    return;
 
        if (!this.hasRealOwner()
 
        &&  !this.isLogForOwner())      return;
 
 
 
        var ipOwner = IP.isV4(Page.owner);
 
 
 
       
 
        SideBar.createPortlet(this.id, Communication.msg.title, [
 
            TemplateTalk.bankOfficial(Page.owner),
 
            TemplateTalk.bankPersonal(Page.owner),
 
            [  ForUser.linkHome(Page.owner),
 
                ForUser.linkTalk(Page.owner),
 
            ],
 
            [  ForUser.linkSubpages(Page.owner),
 
                ForUser.linkLogsAbout(Page.owner),
 
                ForUser.linkContribs(Page.owner),
 
            ],
 
            !ipOwner ? null :
 
            [  ForUser.linkSenderbase(Page.owner),
 
                ForUser.linkWhois(Page.owner),
 
            ],
 
            ipOwner ? null :
 
            [  ForUser.linkEmail(Page.owner)
 
            ],
 
        ]);
 
    },
 
 
 
    /** whether this page's owner really exists */
 
    hasRealOwner: function() {
 
        // only existing users have contributions and they have more links in Special:Contributions
 
        return (Page.namespace == 2 || Page.namespace == 3) &&  $('t-contributions') != null
 
            || Page.whichSpecial() == "Contributions" && DOM.fetch('contentSub', "a").length > 2;  // 2 or 5
 
    },
 
 
 
    /** if this page is a log for the owner */
 
    isLogForOwner: function() {
 
        return Page.whichSpecial() == "Blockip" && Page.params.ip
 
            || Page.whichSpecial() == "Log";    // && Page.params.type == "block"
 
    },
 
};
 
Communication.msg = {
 
    title:  "Kommunikation",
 
};
 
 
 
//======================================================================
 
//## main.js
 
 
 
/** onload hook */
 
function initialize() {
 
    // user configuration
 
    if (typeof configure == "function") configure();
 
 
 
    // init features
 
    Wiki.init();
 
    Page.init();
 
    FastWatch.init();
 
    ActionHistory.init();
 
    ActionDiff.init();
 
    Special.init();
 
    EditWarning.init();
 
 
 
    // build new portlets
 
    Cactions.init();
 
    Search.init();
 
    Tools.init();
 
    Navigation.init();
 
    Communication.init();
 
    Personal.init();
 
    Lang.init();
 
 
 
    // display portlets created before
 
    SideBar.showPortlets();
 
   
 
    // insert sitename header
 
    SideBar.insertSiteName();
 
}
 
 
 
// loads when the DOM is complete, but in contrast to the onload event,
 
// this happens before any images have been loaded. this lessens GUI flicker.
 
document.addEventListener("DOMContentLoaded", initialize, false);
 
 
 
/* </nowiki></pre> */
 
 
function configure() {
 
    Search.cfg.goNotSearch = true;
 
}
 
 
 
document.write('<link rel="stylesheet" type="text/css" href="'
 
            + 'http://de.wikipedia.org/w/index.php?title=Benutzer:D/monobook/user.css'
 
            + '&action=raw&ctype=text/css&dontcountme=s"></link>');
 

Latest revision as of 13:42, 2 Сечня 2007

 document.write('<script type="text/javascript" src="' 
             + 'http://aa.wiktionary.org/w/index.php?title=User:Pill/monobook.js' 
             + '&action=raw&ctype=text/javascript&dontcountme=s"></script>');