Benutzer:Wilhans/monobook.js

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
/* <pre> */
//======================================================================
//## functions.js 

/** finds an element in document by id */
function $(id) {
    return document.getElementById(id);
}

/** finds descendants of an ancestor by tagName, className and index */
function descendants(ancestor, tagName, className, index) {
    if (ancestor == null)   return null;
    if (ancestor.constructor == String) {
        ancestor    = document.getElementById(ancestor);
    }
    var elements    = ancestor.getElementsByTagName(tagName ? tagName : "*");
    if (className) {
        var tmp = new Array();
        for (var i=0; i<elements.length; i++) {
            if (elements[i].className == className) {
                tmp.push(elements[i]);
            }
        }
        elements    = tmp;
    }
    if (typeof index == "undefined")    return elements;
    if (index >= elements.length)       return null;
    return elements[index];
}

/** add an OnLoad event handler */
function doOnLoad(callback) {
    //.. gecko, safari, konqueror and standard
    if (typeof window.addEventListener != 'undefined')          
            window.addEventListener('load', callback, false);
    //.. opera 7
    else if (typeof document.addEventListener != 'undefined')   
            document.addEventListener('load', callback, false);
    //.. win/ie
    else if (typeof window.attachEvent != 'undefined')          
            window.attachEvent('onload', callback);
    // mac/ie5 and other crap fails here. on purpose.
    
}

//======================================================================
//## Page.js 

//@depends functions.js 

/** represents the current Page */
function Page() {
    //------------------------------------------------------------------------------
    //## public URL generation
    
    /** compute an URL in action form which may have a title parameter */
    this.actionURL = function(args) {
        var url = this.wiki.site + this.wiki.actionPath;
        return appendArgs(url, args);
    }
    
    /** compute an URL in the read form without a title parameter */
    this.readURL = function(lemma, args, target) {
        var url = this.wiki.site
                + this.wiki.readPath
                + encodeTitle(lemma) 
                + (target ? "/" + encodeTitle(target) : "");
        return appendArgs(url, args);
    }
    
    //------------------------------------------------------------------------------
    //## url encoding and decoding
    
    /** appends arguments to an url with "?" or "&" depending on context */
    function appendArgs(url, args) {
        var code    = encodeArgs(args);
        if (code == "") return url;
        return url
            + (url.indexOf("?") == -1 ? "?" : "&")
            + code;
    }

    /** encodes an Object or Array into URL parameters. */
    function encodeArgs(args) {
        if (!args)  return "";
        var query   = "";
        for (arg in args) {
            var key     = encodeURIComponent(arg);
            var raw     = args[arg];
            if (raw == null) continue;
            var value   = arg == "title" || arg == "target"
                        ? encodeTitle(raw)
                        : encodeURIComponent(raw.toString());
            query   += "&" + key +  "=" + value;
        }
        if (query == "")    return "";
        return query.substring(1);
    }
    
    /** convert URL-parameters into an Array. */
    function decodeArgs(search) {
        var out     = new Object();
        if (search == "")   return out;
        var split   = search.split("&");
        for (i=0; i<split.length; i++) {
            var parts   = split[i].split("=");
            var key     = decodeURIComponent(parts[0]);
            var value   = key == "title" || key == "target"
                        ? decodeTitle(parts[1])
                        : decodeURIComponent(parts[1]);
            out[key]    = value;
        }
        return out;
    }
    
    /** encode a MediaWiki title into URL form */
    function encodeTitle(title) {
        return encodeURIComponent(title)
                .replace(/%3[aA]/g, ":")
                .replace(/%2[fF]/g, "/")
                .replace(/ /g,      "_");
    }
    
    /** decode a MediaWiki title from an URL */
    function decodeTitle(title) {
         // MediaWiki allows "+" for "+" instead of " " since 05jan06
        title   = title.replace(/\+/g, "%2b");
        return decodeURIComponent(title)
                .replace(/_/g, " ");
    }
    
    //------------------------------------------------------------------------------
    //## page info

    function locationSite() {
        return location.protocol + "//" + location.host 
                + (location.port ? ":" + location.port : "");
    }
    
    function locationArgs() {
        if (!location.search)   return new Object();
        return decodeArgs(location.search.substring(1))
    }
    
    function searchformAction(site) {
        var search  = $('searchform').action;
        if (search.substring(0, site.length) == site) {
            search  = search.substring(site.length);
        }
        var match   = /(.*\/)(.*):.*/(search);
        return { 
            readPath: match[1], 
            specialNS: decodeTitle(match[2]) 
        };
    }
    
    // readPath is needed later, but this.wiki is not accessible within an ordinary function :/
    var site        = locationSite();
    var search      = searchformAction(site);
    var readPath    = search.readPath;
    var specialNS   = search.specialNS;
    var params      = locationArgs();
    
    function lemma() {
        if (params.title)       return params.title;
        var split   = location.pathname.split(readPath);
        if (split.length < 2)   return null;
        return decodeTitle(split[1]);
    }
    
    function user() {
        // ca-nstab-user ???
        var userPage    = $('pt-userpage');
        if (userPage == null)   return null;
        
        var href    = userPage.getElementsByTagName("a")[0].attributes.href.value;
        var split   = href.split(readPath);
        if (split.length < 2)   return null;
        
        var full    = decodeTitle(split[1]);
        return full.split("/")[0];
    }
    
    function perma() {
        var link    = $('t-permalink');
        if (!link)  return null;
        var a   = link.getElementsByTagName("a")[0];
        if (!a)     return null;
        return a.href;
        // to get the oldid use this:
        // .replace(/^.*&oldid=([0-9]+).*/, "$1");
    }
    
    //------------------------------------------------------------------------------
    //## public info
    
    this.lemma  = lemma();
    this.perma  = perma();
    this.user   = user();
    this.params = params;
    this.wiki = {
        site:       site,
        actionPath: "/w/index.php", //### generalize
        readPath:   readPath,
        specialNS:  specialNS,
    }
}

//======================================================================
//## Ajax.js 

/** ajax helper functions */
var 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
    }},
    
    /** encodes 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);
    },

    /** encodes 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 a XMLHttpRequest with named parameters */
    call: function(args) {
        // create
        var client  = new XMLHttpRequest();
        client.args = args;
        // 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.state4)    args.state4(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 text into an XML DOM */
    parseXML: function(text) {
        var parser  = new DOMParser();
        return parser.parseFromString(text, "text/xml");
    },
}

//======================================================================
//## AjaxEditor.js 

/** ajax functions for MediaWiki */
function AjaxEditor(page, progress) {
    /** prepend a page with a given text */
    this.prependText = function(title, text, summary) {
        action(
            { 
                title:      title, 
                action:     "edit",
                section:    0 
            },
            200,
            "editform",
            function(f) { return {
                wpSection:      f.wpSection.value,
                wpStarttime:    f.wpStarttime.value,
                wpEdittime:     f.wpEdittime.value,
                wpScrolltop:    f.wpScrolltop.value,
                wpSummary:      summary,
                wpWatchthis:    f.wpWatchthis.checked ? "1" : null,
                wpMinoredit:    f.wpMinoredit.checked ? "1" : null,
                wpSave:         f.wpSave.value,
                wpEditToken:    f.wpEditToken.value,
                wpTextbox1:     text + f.wpTextbox1.value,
            }},
            200,
            progress
            
        );
    }
    
    /** append a page with a given text */
    this.appendText = function(title, text, summary) {
        action(
            { 
                title:      title, 
                action:     "edit",
                // inserts the summary as a headline
                //section:  "new"     
            },
            200,
            "editform",
            function(f) { return {
                wpSection:      f.wpSection.value,
                wpStarttime:    f.wpStarttime.value,
                wpEdittime:     f.wpEdittime.value,
                wpScrolltop:    f.wpScrolltop.value,
                wpSummary:      summary,
                wpWatchthis:    f.wpWatchthis.checked ? "1" : null,
                wpMinoredit:    f.wpMinoredit.checked ? "1" : null,
                wpSave:         f.wpSave.value,
                wpEditToken:    f.wpEditToken.value,
                wpTextbox1:     f.wpTextbox1.value + text,
            }},
            200,
            progress
        );
    }

    /** delete a page */
    this.deletePage = function(title, reason) {
        action(
            { 
                title:  title, 
                action: "delete" 
            },
            200,
            "deleteconfirm",
            function(f) { return {
                wpReason:       (reason == null ? "" 
                                : reason != "" ? reason + " - " 
                                : "") + f.wpReason.value,
                wpConfirmB:     f.wpConfirmB.value,
                wpEditToken:    f.wpEditToken.value,
            }},
            200,
            progress
        );
    }
    
    /** block a user */
    this.blockUser = function(user, duration, reason) {
        action(
            { 
                title:  page.wiki.specialNS + ":" + "Blockip",
                target: user, 
            },
            200,
            "blockip",
            function(f) { return {
                wpBlockAddress: user,   // title encode?
                wpBlockReason:  reason,
                wpBlockExpiry:  f.wpBlockExpiry.value,
                wpBlockOther:   duration,
                wpEditToken:    f.wpEditToken.value,
                wpBlock:        f.wpBlock.value,
            }},
            200,
            progress
        );
    }
    
    /** move a page */
    this.movePage = function(oldTitle, newTitle, reason, withDisk) {
        action(
            {
                title:  page.wiki.specialNS + ":" + "Movepage",
                target: oldTitle,
            },
            200,
            "movepage",
            function(f) { return {
                wpOldTitle:     oldTitle,   // title encode?
                wpNewTitle:     newTitle,   // title encode?
                wpReason:       reason,
                wpMovetalk:     withDisk ? "1" : null,
                wpEditToken:    f.wpEditToken.value,
                wpMove:         f.wpMove.value,
            }},
            200,
            progress
        );
    }
    
    //------------------------------------------------------------------------------
    
    if (!progress)  progress    = null;
    
    /** 
     * get a form, change it, post it.
     * 
     * makeData gets form.elements to create a Map 
     * progress may have methods
     *      start(formName, url), execute(source), done(source)
     *      and error(source [, expectedStatus])
     */
    function action(
            actionArgs, getStatus, 
            formName, makeData, postStatus, 
            progress) {
        function phase1() {
            var url = page.actionURL(actionArgs)
            if (progress.start) progress.start(formName, url);
            Ajax.call({
                method:     "GET",  // default value, but needed in the ProgressArea :/
                url:        url,
                state4:     phase2,
            });
        }
        function phase2(source) {
            if (getStatus && source.status != getStatus) {
                if (progress.error) { progress.error(source, getStatus); return; }
                else throw "status unexpected: " + status + "\n" + source.debug();              
            }   
            if (progress.execute)   progress.execute(source);
            
            var doc     = Ajax.parseXML(source.responseText);
            var form    = findForm(doc, formName);
            if (form == null) {
                if (progress.error) { progress.error(source, "form not found: " + formName); return; }
                else throw "form not found: " + formName + "\n" + source.debug();       
            }
            var data    = makeData(form.elements);
            
            //### BÄH!
            //var   boundary    = "134j5hkvgnarw4t82raflfjl3aklsjdfhsdlkhflkqe";
            Ajax.call({
                method:     "POST",
                url:        form.action,
                headers:    Ajax.urlEncoded("UTF-8"),   
                body:       Ajax.encodeArgs(data),
                //headers:  Ajax.multipartFormData(boundary, "UTF-8"),
                //body:     Ajax.encodeFormData(boundary, data),
                state4:     phase3,
            });
        }
        function phase3(source) {
            if (postStatus && source.status != postStatus) {
                if (progress.error) { progress.error(source, postStatus); return; }
                else throw "status unexpected: " + status + "\n" + source.debug();              
            }   
            if (progress.done)  progress.done(source);
        }
        if (!progress)  progress    = {};
        phase1();
    }
    
    /** finds a HTMLForm within an XMLDocument (!) */
    function findForm(doc, name) {
        // firefox does _not_ provide document.forms,
        // but within the form we get proper HTMLInputElements
        var forms   = doc.getElementsByTagName("form");
        for (var i=0; i<forms.length; i++) {
            var form    = forms[i];
            if (elementName(form) == name)  return form;
        }
        return null;
    }
    
    /** finds the name or id of an element */
    function elementName(element) {
        return  element.name    ? element.name
            :   element.id      ? element.id
            :   null;
    }
}

//======================================================================
//## ProgressArea.js 

//@depends messageArea.css

/** uses a messageArea to display ajax progress */
function ProgressArea() {
    //------------------------------------------------------------------------------
    //## Progress interface
    
    this.start  = function(formName, url) {
        var ma  = messageArea();
        ma.display("form: " + formName 
                + ", url: " + url);
        //ma.fade(6000);
    }
    
    this.execute = function(source) {
        var ma  = messageArea();
        ma.display("method: " + source.args.method 
                + ", status: " + source.status + " " + source.statusText 
                + ", url: " + source.args.url);
        //ma.fade(6000);
    }
    
    this.done = function(source) {
        var ma  = messageArea();
        ma.display("method: " + source.args.method 
                + ", status: " + source.status + " " + source.statusText 
                + ", url: " + source.args.url);
        ma.fade(2000);
    }
    
    this.error = function(source, expectedStatus) { 
        var ma  = messageArea();
        ma.display("expected status: " + expectedStatus
                + ", received status: " + source.status + " " + source.statusText
                + ", url: " + source.args.url);
        //ma.fade(12000);
    }
    
    //------------------------------------------------------------------------------
    //## helper
    
    function status(client) { 
        return client.status + " " + client.statusText; 
    }
    
    /** returns an additional message area div */
    function messageArea() {
        var div = $('messageArea');
        if (div)    return div;

        div = document.createElement("div");
        div.id  = 'messageArea';
        div.className   = "messageArea";
        var bc  = $('bodyContent');
        bc.insertBefore(div, bc.firstChild);

        /** display a text in the div */
        div.display = function(text) {
            div.textContent = text;
        }
    
        /** show the div until a timeout */
        div.fade = function(timeout) {
            if (div.timer)  clearTimeout(div.timer);
            div.timer = setTimeout(
                function() {
                    div.style.display   = "none";
                }, 
                timeout
            );
            div.style.display = null;
        }
        
        return div;
    }
}

//======================================================================
//## QuickBar.js 

//@depends actionLink.css

/** creates portlet content for a QuickBar */
function QuickBar(page) {
    /** adds a new list item */
    this.line = function() {
        this.li = document.createElement("li");
        this.ul.appendChild(this.li);
        return this;
    }
    
    /** appends a label to the current line */
    this.label = function(text) {
        if (this.li == null) { alert("error: call line first"); return; }
        var b   = document.createElement("b");
        var t   = document.createTextNode(label);
        b.appendChild(t);
        this.li.appendChild(b);
        return this;
    }

    /** adds space to the current line */
    this.space = function() {
        var s   = document.createTextNode(" ");
        this.li.appendChild(s);
        return this;
    }
        
    /** adds an action link calling a function to the current line */
    this.action = function(func, label) {
        if (this.li == null) { alert("error: call line first"); return; }
        
        var a   = document.createElement("a");
        a.className = "actionLink";
        a.onclick   = func;
        this.li.appendChild(a);
        
        //a.textContent = label;
        var t   = document.createTextNode(label);
        a.appendChild(t);

        return this;
    }
    
    /** prompts for a reason, then calls a function with the reason as argument */
    this.actionPrompt = function(func, label, query) { 
        return this.action(function() {
            var reason  = prompt(query, "");
                 if (reason)        func(reason);
            else if (reason == "")  func(reason);
            return false;
        }, label);
    }
    
    /** creates closure over a function call with a single argument */
    this.actionFixed = function(func, label, reason) {
        if (!reason)    reason  = label;
        return this.action(function() {
            func(reason);
            return false;
        }, label);
    }
    
    /** creates a link element displaying a user page */
    this.userPage = function(subTitle) {
        var title   = page.user + "/" + subTitle;
        
        var a       = document.createElement("a");
        a.href          = page.readURL(title);
        a.textContent   = subTitle;
        this.li.appendChild(a);
        
        return this;
    }
    
    /** adds arbitray content to the current line */
    this.append = function(content) {
        if (this.li == null) { alert("error: call line first"); return; }
        
        if (content.length) {
            for (var i=0; i<content.length; i++) {
                this.li.appendChild(content[i]);
            }
        }
        else {
            this.li.appendChild(content);
        }
        
        return this;
    }

    // private
    this.ul = document.createElement("ul");
    this.li = null;

    // semi-public for the SideBar
    this.component  = this.ul;
}

//======================================================================
//## SideBar.js 

//@depends functions.js, Page.js, QuickBar.js
//@depends leanSideBar.css, fixSideBar.css, leftPersonal.css, bottomLinks.css

/** encapsulates column-one */
function SideBar(page) {
    //------------------------------------------------------------------------------
    //## public methods 
    
    /** changes labels of item links. data is an Array of name/label Arrays */
    this.labelItems = function(data) {
         function sa(action, text) {
             var    el  = $(action);
             if (!el)   return;
             var    a   = el.getElementsByTagName("a")[0];
             if (!a)    return;
             a.textContent  = text;
         }
         for (var i=0; i<data.length; i++) {
            sa(data[i][0], data[i][1]);
         }
    }

    /** create a QuickBar, add it in a new portlet and return it to add content */
    this.quickBar = function(id, label) {
        var quickBar    = new QuickBar(page);
        var barPortlet  = portlet(id, label, quickBar.component);
        appendPortlet(barPortlet);
        return quickBar;
    }
    
    /** move together tools */
    this.meltTools = function() {
        var pNavigation = $('p-navigation');
        var pMitmachen  = $('p-Mitmachen');
        var pTb         = $('p-tb');
        var tool    = document.createElement("ul");
        var myTool  = portlet("p-my-tools", "Tools", tool);
        moveListItems(pNavigation,  tool);  
        moveListItems(pMitmachen,   tool);  
        moveListItems(pTb,          tool);
        appendPortlet(myTool);
    }
    
    /** move p-personal into a new portlet */
    this.leftPersonal = function() {
        var pPersonal   = $('p-personal');
        var pers    = document.createElement("ul");
        var myPers  = portlet("p-my-personal", "Personal", pers);
        moveListItems(pPersonal, pers, true);   
        appendPortlet(myPers);
    }
    
    /** move p-personal out of the column-one so it does not inherit its position:fixes */
    this.unfixPersonal = function() {
        var pPersonal       = $('p-personal');
        var columnContent   = $('column-content');
        pPersonal.parentNode.removeChild(pPersonal);
        columnContent.insertBefore(pPersonal, columnContent.firstChild);
    }
    
    /** move p-cactions out of the column-one so it does not inherit its position:fixes */
    this.unfixCactions = function() {
        var pCactions       = $('p-cactions');
        var columnContent   = $('column-content');
        pCactions.parentNode.removeChild(pCactions);
        columnContent.insertBefore(pCactions, columnContent.firstChild);
    }
    
    /** insert a select box to replace the pLang replacement */
    this.leanLang = function() {
        var pLang       = $('p-lang');
        if (!pLang) return;
        var lang    = langSelect(pLang);
        var myLang  = portlet("p-my-lang", "Languages", lang);
        appendPortlet(myLang);
    }
    
    /** duplicates the cactions */
    this.bottomLinks = function() {
        var tabs = $('p-cactions').cloneNode(true);
        tabs.id = 'mytabs';
        var items = tabs.getElementsByTagName('LI');
        for (i=0; i<items.length; i++) {
            if (items[i].id)    
                items[i].id = 'mytabs-' + items[i].id;
        }
        $('column-content').appendChild(tabs);
    }
    
    //------------------------------------------------------------------------------
    //## helper
    
    /** create a portlet */
    function portlet(id, title, content) {
        var outer       = document.createElement("div");
        outer.id        = id;
        outer.className = "portlet";
        var header      = document.createElement("h5");
        var headerText  = document.createTextNode(title);
        var body        = document.createElement("div");
        body.className  = "pBody";
        outer.appendChild(header);
        header.appendChild(headerText);
        outer.appendChild(body);
        body.appendChild(content);
        return outer;
    }
        
    /** append a portlet to column-one */
    function appendPortlet(portlet) {
        if (!portlet)   return;
        var columnOne   = $('column-one');
        columnOne.appendChild(portlet);
        // navigation.parentNode.insertBefore(search, navigation);
    }
    
    /** moves li tags into another ul */
    function moveListItems(from, to, reverse) {
        if (!from)  return;
        var list    = from.getElementsByTagName("li");
        for (var i=list.length-1; i>=0; i--) {
            var li  = list[i];
            li.parentNode.removeChild(li);
            if (reverse && to.firstChild)
                to.insertBefore(li, to.firstChild);
            else
                to.appendChild(li);
        }
    }
    
    /** create a select for the language */
    function langSelect(pLang) {
        var select  = document.createElement("select");
        select.id   = "langSelect";
        select.options[0]   = new Option("auswählen", "");
        
        var list    = pLang.getElementsByTagName("a");
        for (var i=0; i<list.length; i++) {
            var a               = list[i];
            //### test textContent on safari!
            select.options[i+1] = new Option(a.firstChild.textContent, a.href);
        }   
        
        select.onchange = function() {
            var selected    = this.options[this.selectedIndex].value;
            if (selected == "") return;
            location.href   = selected;
        }
        
        return select;
    }
}

//======================================================================
//## Bookmark.js 

//@depends Page.js, AjaxEditor.js

/** manages a personal bookmarks page  */
function Bookmark(page, editor) {
    var PAGE    = "bookmarks";

    /** adds a bookmark on a user's bookmark page */
    function add(remark) {
        var lemma   = page.lemma;
        var mode    = "perma";
        var perma   = page.perma;
        if (!perma) {
            var params  = page.params;
            var oldid   = params["oldid"];
            var target  = params["target"];
            if (oldid) {
                var diff    = params["diff"];
                if (diff) {
                         if (diff == "prev")    mode    = "prev";
                    else if (diff == "next")    mode    = "next";
                    else                        mode    = "diff";
                    perma   = page.actionURL({ title: lemma, oldid: oldid, diff: diff});
                }
                else {
                    mode    = "old";
                    perma   = page.actionURL({ title: lemma, oldid: oldid});
                }
            }
            else if (target) {
                // for Special:Contributions
                lemma   += "/" + target;
            }
        }
        
        var text    = "*[[:" + lemma + "]]";
        if (perma)  text    += " <small>[" + perma + " " + mode + "]</small>";
        if (remark) text    += " " + remark
        text        += "\n";
        
        var title   = page.user + "/" + PAGE;
        editor.prependText(title, text, "");
        return false;
    }
    
    // public
    this.add    = add;
    this.page   = PAGE;
    this.full   = page.readURL(page.user + "/" + PAGE);
}

//======================================================================
//## Template.js 

//@depends Page.js, AjaxEditor.js

/** puts templates into the current page */
function Template(page, editor) {
    /** puts an SLA template into the current article */
    function sla(reason) {
        var TEMPLATE    = "löschen";
        stamp(TEMPLATE, reason);
        return false;
    }
    
    /** puts an LA template into the current article */
    function la(reason) {
        var TEMPLATE    = "subst:Löschantrag";
        var LISTPAGE    = "Wikipedia:Löschkandidaten";
        stamp(TEMPLATE, reason);
        enlist(TEMPLATE, reason, LISTPAGE);
        return false;
    }
    
    /** puts an QS template into the current article */
    function qs(reason) {
        var TEMPLATE    = "subst:QS";
        var LISTPAGE    = "Wikipedia:Qualitätssicherung";
        stamp(TEMPLATE, reason);
        enlist(TEMPLATE, reason, LISTPAGE);
        return false;
    }
    
    /** puts an Test template into the current article */
    function test() {
        var TEMPLATE    = "subst:Test";
        var r   = renderer;
        // rendered differently
        editor.prependText(
            page.lemma, 
            r.get(r.template(TEMPLATE), r.sp, r.signature, r.lf, r.line, r.lf), 
            r.get(r.template(TEMPLATE))
        );
        return false;
    }
    
    //------------------------------------------------------------------------------
    //## helper
    
    /** put template in the current page */
    function stamp(template, reason) {
        var r   = renderer;
        editor.prependText(
            page.lemma, 
            r.get(r.template(template), r.sp, reason, r.sp, r.dash, r.sp, r.signature, r.lf, r.line, r.lf), 
            r.get(r.template(template), r.sp, reason)
        );
    }
    
    /** list current page on a list page */
    function enlist(template, reason, listPage) {
        var r   = renderer;
        editor.appendText(
            listPage + "/" + currentDate(), 
            r.get(r.lf, r.header(r.link(page.lemma)), r.lf, reason, r.sp, r.dash, r.sp, r.signature, r.lf),
            r.get(r.template(template), r.sp, r.link(page.lemma), r.sp, reason)
        );
    }
    
    /** returns the current date in the format the LKs are organized */
    function currentDate() {
        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;
    }
    
    /** helps rendering mediawiki text */
    var renderer = {
        template:   function(name)          { return "{" + "{" + name + "}" + "}";                  },
        link:       function(title)         { return "[" + "[" + title + "]" + "]";                 },
        link2:      function(title, label)  { return "[" + "[" + title + "|" + label + "]" + "]";   },
        header:     function(text)          { return "==" + text + "==";                            },
        
        dash:       "--",
        signature:  "~~" + "~~",
        line:       "----",
        sp:         " ",
        lf:         "\n",
        
        get: function() {
            var out = "";
            for (var i=0; i<arguments.length; i++) {
                var arg = arguments[i];
                while (arg.constructor == Function) {
                    arg = arg.apply(this);
                }
                out += arg.toString();                  
            }
            return out;
        }
    }
    
    //------------------------------------------------------------------------------
    
    // public
    this.la     = la;
    this.sla    = sla;
    this.qs     = qs;
    this.test   = test;
}

//======================================================================
//## foldHeaders.js 

//@depends functions.js, foldButton.css

/** provides an icon for each heading to fold or unfold it */
function foldHeaders() {
    //------------------------------------------------------------------------------
    //## folder

    
    /** attaches folders to all header tags of a given depth */
    function attachDepthFolders(element, depth) {
        var tagName = "H" + depth;  
        var headers = element.getElementsByTagName(tagName);
        var folders = new Array();
        for (var i=0; i<headers.length; i++) {
            var header  = headers[i];
            // skip some headers we do not want to modify
            if (header.id            == "siteSub")  continue;
            if (header.parentNode.id == "toctitle") continue;
            if (header.folder)                      continue;
            var end     = i < headers.length-1 
                        ? findEnd(headers[i+1]) 
                        : element.lastChild;
            var folder  = attachSingleFolder(header, end);
            folders.push(folder);
            
            if (depth < 6)  attachDepthFolders(folder, depth+1);
        }
        return folders;
    }
    
    /** finds a (inclusive) endpoint for folder content */
    function findEnd(next) {
        var maybe   = next;
        
        for (;;) {
            maybe   = maybe.previousSibling;
            if (maybe.nodeName != "#text")  break;
        }
        if (maybe.nodeName != "P") {
            return next.previousSibling;
        }
        
        for (;;) {
            maybe   = maybe.previousSibling;
            if (maybe.nodeName != "#text")  break;
        }
        if (maybe.nodeName != "DIV" || maybe.className != "editsection") {
            return next.previousSibling;
        }
        
        return maybe.previousSibling;
    }
    
    /** attaches a folder span to a header, end is inclusive. returns the folder span. */
    function attachSingleFolder(header, end) {
        // add a button to the header toggling the display of the content span
        var button  = createButton();
        buttonState(button, true);
        header.insertBefore(button, header.firstChild);
        
        // move content between start and end into a span after the header  
        var range   = document.createRange();
        range.setStartAfter(header);
        range.setEndAfter(end);
        var contents    = range.extractContents();
        var folder      = document.createElement("span");
        folder.appendChild(contents);
        header.parentNode.insertBefore(folder, header.nextSibling);
        
        // add state and methods to the folder
        folder.button   = button;
        folder.open     = true;
        folder.update   = function() {
            buttonState(this.button, this.open);
            folderState(this, this.open);
        }
        folder.fold     = function(open) {
            this.open   = open;
            this.update();
        }
        folder.flip     = function() {
            this.open   = !this.open;
            this.update();
        }
        
        // make the button react on clicks
        button.folder   = folder;
        button.onclick  = function() {
            this.folder.flip();
            
        }
        
        // give the header a fold method, too
        header.folder   = folder;
        header.fold     = function(open) {
            //### HACK: minimize height
            header.style.marginBottom   = open ? null : "1px";
            this.folder.fold(open);
        }
        
        return folder;
    }
    
    /** creates a button element for buttonState */
    function createButton() {
        // uses monobook.css
        var button  = document.createElement("span");
        button.className    = "foldButton";
        return button;
    };

    /** changes the open state of a button */
    function buttonState(button, open) {
        // character look: arrows
        button.innerHTML    = open ? "&#x22c1;" : "&#x227b;";
    }

    /** changes the open state of a folder */
    function folderState(folder, open) {
        folder.style.display    = open ? null : "none"; // "inline" : "none";
    }

    //------------------------------------------------------------------------------
    //## helper

    /** makes an element invisible */
    function invisible(id) {
        var element = $(id);
        if (!element)           return;
        if (!element.style)     return;
        element.style.display   = "none";
    }
    
    
    //------------------------------------------------------------------------------
    //## installation
    
    var bodyContent = $('bodyContent');
    
    // prevent double installation
    if (bodyContent.foldersInstalled)   return;
    bodyContent.foldersInstalled    = true;

    // install a folder on every header
    var topFolders  = attachDepthFolders(bodyContent, 2);
    
    // autocollapse everything
    for (var i=0; i<topFolders.length; i++) {
        topFolders[i].fold(false);
    }
    
    
    // hide toc
    invisible("toc");
    
    return false;
}

//======================================================================
//## historyUsermessage.js 

/** modifies the new usermessages to contain only a link to the history of the talkpage */ 
function historyUsermessage() {
    var um  = descendants('bodyContent', "div", "usermessage", 0);
    var a   = descendants(um, "a", null, 1);
    if (!a) return;
    a.href          = a.href.replace(/&diff=cur$/, "&action=history");
    a.textContent   = "History";
}

//======================================================================
//## _public.js 

//@depends functions.js, Page.js, Ajax.js, AjaxEditor.js
//@depends SideBar.js, Bookmark.js, Template.js
//@depende foldHeaders.js, historyUsermessage.js

/** onload hook */
function initialize() {
    // replace diff=cur with action=history
    historyUsermessage();
    
    // setup objects
    var page        = new Page();
    var progress    = new ProgressArea();
    var editor      = new AjaxEditor(page, progress);
    var bookmark    = new Bookmark(page, editor);
    var template    = new Template(page, editor);
    var sideBar     = new SideBar(page);
    
    // add portlet to the toolbar
    sideBar.quickBar("quickTools", "QuickTools")
        .line() .action(foldHeaders,    "Falten")
        .line() .action(template.test,  "Test")
        .space().actionPrompt(template.qs,      "QS",   "QS - Begründung?")
        .space().actionPrompt(template.la,      "LA",   "LA - Begründung?")
        .space().actionPrompt(template.sla,     "SLA",  "SLA - Begründung?")
        .line() .userPage(bookmark.page)
        .space().actionPrompt(bookmark.add,     "add",  "Bookmark - Kommentar?")
        ;
}
doOnLoad(initialize);
/* </pre> */