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
/* version für benutzer, nur auf firefox 1.5 getestet */
/* <pre><nowiki> */
//======================================================================
//## core/prototypes.js
/** bind a function to an object */
Function.prototype.bind = function(object) {
var __self = this;
return function() {
__self.apply(object, arguments);
};
}
/** 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");
}
//======================================================================
//## core/functions.js
/** find an element in document by its id */
function $(id) {
return document.getElementById(id);
}
/** find descendants of an ancestor by tagName, className and index */
function descendants(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 = 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];
}
/** find the next element from el which has a given nodeName or is non-text */
function nextElement(el, nodeName) {
for (;;) {
el = el.nextSibling; if (!el) return null;
if (nodeName) { if (el.nodeName.toUpperCase() == nodeName.toUpperCase()) 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 */
function previousElement(el, nodeName) {
for (;;) {
el = el.previousSibling; if (!el) return null;
if (nodeName) { if (el.nodeName.toUpperCase() == nodeName.toUpperCase()) return el; }
else { if (el.nodeName.toUpperCase() != "#TEXT") return el; }
}
}
/** remove a node from its parent node */
function removeNode(node) {
node.parentNode.removeChild(node);
}
/** removes all children of a node */
function removeChildren(node) {
while (node.lastChild) node.removeChild(node.lastChild);
}
/** inserts an element before another one. allows an Array for multiple elements and string for textNodes */
function pasteBefore(targetIdOrElement, element) {
var target = targetIdOrElement.constructor == String
? document.getElementById(targetIdOrElement)
: targetIdOrElement;
if (!target) return;
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.parentNode.insertBefore(element, target);
}
if (element.constructor == Array) {
for (var i=0; i<element.length; i++) { add(element[i]); }
}
else {
add(element);
}
}
/** inserts an element before another one. allows an Array for multiple elements and string for textNodes */
function pasteAfter(targetIdOrElement, element) {
var target = targetIdOrElement.constructor == String
? document.getElementById(targetIdOrElement)
: targetIdOrElement;
if (!target) return;
function addInsert(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.parentNode.insertBefore(element, next);
}
function addAppend(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.parentNode.appendChild(element);
}
var next = target.nextSibling;
var add = next ? addInsert : addAppend;
if (element.constructor == Array) {
for (var i=0; i<element.length; i++) { add(element[i]); }
}
else {
add(element);
}
}
/** insert text, element or elements at the start of a target */
function pasteBegin(targetIdOrElement, element) {
var target = targetIdOrElement.constructor == String
? document.getElementById(targetIdOrElement)
: targetIdOrElement;
if (!target) return;
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
if (target.firstChild) target.insertBefore(element, target.firstChild);
else target.appendChild(element);
}
if (element.constructor == Array) {
for (var i=0; i<element.length; i++) { add(element[i]); }
}
else {
add(element);
}
}
/** insert text, element or elements at the end of a target */
function pasteEnd(targetIdOrElement, element) {
var target = targetIdOrElement.constructor == String
? document.getElementById(targetIdOrElement)
: targetIdOrElement;
if (!target) return;
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.appendChild(element);
}
if (element.constructor == Array) {
for (var i=0; i<element.length; i++) { add(element[i]); }
}
else {
add(element);
}
}
/** adds or removes className parts from a String */
function className(state, add, remove) {
// put existing into a map
var stateSplit = state.split(/\s+/);
var stateMap = new Array();
for (var i=0; i<stateSplit.length; i++) {
var stateClass = stateSplit[i].trim();
if (stateClass.length == 0) continue;
stateMap[stateClass] = 1;
}
// remove parts
var remSplit = remove.split(/\s+/);
for (var i=0; i<remSplit.length; i++) {
var name = remSplit[i];
delete stateMap[name];
}
// add parts
var addSplit = add.split(/\s+/);
for (var i=0; i<addSplit.length; i++) {
var name = addSplit[i];
stateMap[name] = 1;
}
// join parts
var newStr = "";
for (var newClass in stateMap) {
newStr += " " + newClass;
}
if (newStr != "") newStr = newStr.substring(1);
return newStr;
}
/** 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.
}
/**
* adds an onchange handler to elements in a form submitting the form
* and removes the submit button. the latter can be switched of with
* the optional leaveSubmitAlone.
*/
function autoSubmit(form, elementNames, leaveSubmitAlone) {
if (!form) 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) {
var todo = new Array();
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++) {
removeNode(todo[i]);
}
}
}
/** 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;
}
//======================================================================
//## core/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; // de.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 = new Object();
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;
},
};
//======================================================================
//## core/Wiki.js
/** encoding and decoding of MediaWiki URLs */
Wiki = {
//------------------------------------------------------------------------------
//## compatibilty methods
/** compute an URL in the read form without a title parameter. args object and target string are optional */
readURL: function(lemma, target, args) {
var args2 = {
title: lemma + (target ? "/" + target : "")
};
for (var key in args) {
args2[key] = args[key];
}
return this.encodeURL(args2, true);
},
//------------------------------------------------------------------------------
//## site info
/** the current wiki site without any path */
site: wgServer, // "http://de.wikipedia.org",
/** path to read pages */
readPath: wgArticlePath.replace(/\$1$/, ""), // "/wiki/",
/** path for page actions */
actionPath: wgScriptPath + "/index.php", // "/w/index.php",
/** decoded Special namespace */
specialNS: "Spezial", // Namespaces.indexed(-1).name
/** decoded User namespace */
userNS: "Benutzer", // Namespaces.indexed(2).name
/** decoded User_talk namespace */
userTalkNS: "Benutzer Diskussion", // Namespaces.indexed(3).name
/** name of the logged in user or null (should never happen) */
user: wgUserName,
//------------------------------------------------------------------------------
//## public methods
/** encode parameters into an URL */
encodeURL: function(args, shorten) {
if (!args.title) throw "missing page title in:" + args;
var title = args.title.replace(/_/g, " ");
var info = this.info(title);
// TODO: add irreglar smushing for Special:Watchlist
var path;
if (shorten) {
// short URLs may smush one parameter to the title
var first = title;
if (info.smush) {
var value = args[info.smush];
if (value || value == "") {
first += "/" + value;
}
}
// short URLs use "+" literally
path = this.readPath
+ this.fixTitle(encodeURIComponent(first))
.replace(/%2b/gi, "+");
}
else {
path = this.actionPath;
}
path += "?";
for (var key in args) {
// haven been done above
if (shorten
&& (key == "title" || key == info.smush)) continue;
var value = args[key];
if (value == null) continue;
value = value.toString();
// title parameters should use "_" instead of " "
var code = encodeURIComponent(value);
if (key == "title" || info.normalize[key]) {
code = this.fixTitle(code);
}
path += encodeURIComponent(key)
+ "=" + code + "&";
}
return this.site + path.replace(/[?&]$/, "");
},
/**
* decode an URL or path into a map of parameters
* a possiobly smushed parameter is removed from the title and
* stored as if it was a regular parameter.
* a pseudo-parameter "_smushed" provides key and value of it.
*/
decodeURL: function(url) {
var out = {};
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;
out.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");
out[key] = decodeURIComponent(code)
}
}
// normalize the title itself
if (!out.title) throw "missing page title in: " + loc;
out.title = out.title.replace(/_/g, " ");
// normalize title-type parameters
var info = this.info(out.title);
for (var key in info.normalize) {
if (out[key]) out[key] = out[key].replace(/_/g, " ");
}
// desmush
if (info.smush) {
var m = /(.*?)\/(.*)/(out.title);
if (m) {
out.title = m[1];
out[info.smush] = m[2];
}
out._smushed = {
key: info.smush,
value: out[info.smush],
};
}
// Special:Watchlist uses irregular smushing
else {
var smushVal = null;
var action = out.title.scan(this.specialNS + ":Watchlist/");
if (action == "edit" || action == "clear") {
out[action] = "yes";
out.title = this.specialNS + ":Watchlist";
smushVal = action;
}
else if (out.title == this.specialNS + ":Watchlist") {
if (out.edit) smushVal = "edit";
else if (out.clear) smushVal = "clear";
}
if (smushVal) {
out._smushed = {
key: null, // irregular, has no name
value: action
};
}
}
return out;
},
//------------------------------------------------------------------------------
//## private stuff
/** 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 which parameter should be smushed and which needs to be normalize */
info: function(title) {
// attention: the title should contain [ ], not [_]
var name = title.replace(/\/.*/, "")
.scan(this.specialNS + ":");
if (!name) return {
smush: null,
normalize: {},
}
var normalize = {};
var params = this.specialTitle[name];
if (params) {
for (var i=0; i<params.length; i++) {
normalize[params[i]] = true;
}
}
return {
smush: this.specialSmush[name],
normalize: normalize,
};
},
/** 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",
// Special:Watchlist/edit => Special:Watchlist?edit=yes
// Special:Watchlist/clear => Special:Watchlist?clear=yes
},
/** some parameters of special pages point to pages, there space and underscore mean the same */
specialTitle: {
"Emailuser": [ "target" ],
"Contributions": [ "target" ],
"Whatlinkshere": [ "target" ],
"Recentchangeslinked": [ "target" ],
"Undelete": [ "target" ],
"Allpages": [ "from", "namespace" ],
"Prefixindex": [ "from", "namespace" ],
"Blockip": [ "ip" ],
},
};
//======================================================================
//## core/Page.js
/** represents the current Page */
Page = { init: function() {
//------------------------------------------------------------------------------
//## public methods
/** returns the name of the current Specialpage or null */
this.whichSpecial = function() {
// if it contains a slash, unsmushing failed
return this.title.scan(Wiki.specialNS + ":");
}
//------------------------------------------------------------------------------
//## page info
// scoping helper
var self = this;
/** search string of the current location decoded into an Array */
this.params = Wiki.decodeURL(location.href);
/** the namespace of the current page */
this.namespace = parseInt(document.body.className.substring("ns-".length));
/** whether this page could be deleted */
this.deletable = $('ca-delete') != null;
/** whether this page could be edited */
this.editable = $('ca-edit') != null;
/** title for the current URL ignoring redirects */
this.title = this.params.title;
/** permalink to the current page if one exists or null */
this.perma = null;
var a = descendants('t-permalink', "a", null, 0);
if (a != null) {
self.perma = a.href;
}
/** the user a User or User_talk or Special:Contributions page belongs to */
this.owner = null;
(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.type == "block" && self.params.user) {
self.owner = self.params.user.scan(Wiki.userNS + ":");
}
if (self.owner) return;
// try block link
if (!self.owner) {
var a = descendants('t-blockip', "a", null, 0);
if (a == null) return;
var href = a.attributes.href.value;
var args = Wiki.decodeURL(href);
self.owner = args.ip;
}
})();
} };
//======================================================================
//## core/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
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.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;
},
/** parse text into an XML DOM */
parseXML: function(text) {
var parser = new DOMParser();
return parser.parseFromString(text, "text/xml");
},
};
//======================================================================
//## core/Editor.js
/** ajax functions for MediaWiki */
function Editor(progress) {
if (progress) this.indicator = this.progressIndicator(progress);
else this.indicator = this.quietIndicator;
}
Editor.prototype = {
//------------------------------------------------------------------------------
//## change page content
/** replace the text of a page with a replaceFunc */
replaceText: function(title, replaceFunc, summary, minorEdit, doneFunc) {
this.indicator.header("changing page " + title);
this.action(
{ title: title, action: "edit" },
200,
"editform",
function(f) {
var oldText = f.wpTextbox1.value.replace(/^[\r\n]+$/, "");
var newText = replaceFunc(oldText);
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: minorEdit ? "1" : null, // f.wpMinoredit.checked
wpSave: f.wpSave.value,
wpEditToken: f.wpEditToken.value,
wpTextbox1: newText,
};
},
200,
doneFunc
);
},
/** add text to the end of a spage, the separator is optional */
appendText: function(title, text, summary, separator, doneFunc) {
function replace(oldText) { return concatSeparated(oldText, separator, text); }
this.replaceText(title, replace, summary, false, doneFunc);
},
/** add text to the start of a page, the separator is optional */
prependText: function(title, text, summary, separator, doneFunc) {
this.indicator.header("changing page " + title);
this.action(
// section=new would insert the summary as a headline,
// but section=0 does not, and it's faster than replaceText
{ title: title, action: "edit", section: 0 },
200,
"editform",
function(f) {
var oldText = f.wpTextbox1.value.replace(/^[\r\n]+$/, "");
var newText = concatSeparated(text, separator, oldText);
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: newText,
};
},
200,
doneFunc
);
},
//------------------------------------------------------------------------------
//## change page state
/** restores a page to an older version */
restoreVersion: function(title, oldid, summary, doneFunc) {
this.indicator.header("restoring page " + title);
this.action(
{ title: title, action: "edit", oldid: oldid },
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,
};
},
200,
doneFunc
);
},
/** watch or unwatch a page. the doneFuncis optional */
watchedPage: function(title, watch, doneFunc) {
var self = this;
var action = watch ? "watch" : "unwatch";
self.indicator.header(action + "ing " + title);
var url = Wiki.encodeURL({
title: title,
action: action,
});
self.indicator.getting(url);
Ajax.call({
method: "GET",
url: url,
doneFunc: function(source) {
if (source.status != 200) {
self.indicator.failed(source, 200);
return;
}
self.indicator.finished();
if (doneFunc) doneFunc();
},
});
},
/** move a page */
movePage: function(oldTitle, newTitle, reason, withDiscussion, doneFunc) {
this.indicator.header("moving " + oldTitle + " to " + newTitle);
var title = Wiki.specialNS + ":Movepage";
this.action(
// target is url-encoded and mandtory
{ title: title, target: oldTitle },
200,
"movepage",
function(f) { return {
wpOldTitle: oldTitle,
wpNewTitle: newTitle,
wpReason: reason,
wpMovetalk: withDiscussion ? "1" : null,
wpEditToken: f.wpEditToken.value,
wpMove: f.wpMove.value,
}},
200,
doneFunc
);
},
//------------------------------------------------------------------------------
//## action helper
/**
* get a form, change it, post it.
* makeData gets form.elements to create a Map
* the doneFunc is called afterwards and may be left out
*/
action: function(actionArgs, expectedGetStatus,
formName, makeData, expectedPostStatus,
doneFunc) {
function phase1() {
var url = Wiki.encodeURL(actionArgs);
self.indicator.getting(url);
Ajax.call({
method: "GET",
url: url,
doneFunc: phase2,
});
}
function phase2(source) {
if (expectedGetStatus && source.status != expectedGetStatus) {
self.indicator.failed(source, expectedGetStatus);
return;
}
var doc = Ajax.parseXML(source.responseText);
var form = self.findForm(doc, formName);
if (form == null) { self.indicator.missingForm(source, formName); return; }
var url = form.action;
var data = makeData(form.elements);
var headers = Ajax.urlEncoded("UTF-8");
var body = Ajax.encodeArgs(data);
self.indicator.posting(url);
Ajax.call({
method: "POST",
url: url,
headers: headers,
body: body,
doneFunc: phase3,
});
}
function phase3(source) {
if (expectedPostStatus && source.status != expectedPostStatus) {
self.indicator.failed(source, expectedPostStatus);
return;
}
self.indicator.finished();
if (doneFunc) doneFunc();
}
var self = this;
phase1();
},
/** finds a HTMLForm within an XMLDocument (!) */
findForm: function(doc, nameOrIdOrIndex) {
// firefox does _not_ provide document.forms,
// but within the form we get HTMLInputElements (!)
var forms = doc.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;
},
/** finds the name or id of an element */
elementNameOrId: function(element) {
return element.name ? element.name
: element.id ? element.id
: null;
},
//------------------------------------------------------------------------------
//## progress
/** progress indicator */
progressIndicator: function(progress) {
return {
header: function(text) { progress.header(text); },
getting: function(url) { progress.body("getting " + url); },
posting: function(url) { progress.body("posting " + url); },
finished: function() { progress.body("done"); progress.fade(); },
missingForm: function(client, name) { progress.body("form not found: " + name); },
failed: function(client, expectedStatus) {
progress.body(
client.args.method + " " + client.args.url + " "
+ client.status + " " + client.statusText + " "
+ " (expected " + expectedStatus + ")"
);
},
};
},
/** progress indicator */
quietIndicator: {
header: function(text) {},
body: function(text) {},
getting: function(url) {},
posting: function(url) {},
finished: function() {},
missingForm: function(client, name) { throw "form not found: " + name; },
failed: function(client, expectedStatus) { throw "status unexpected\n" + client.debug(); }
},
};
//======================================================================
//## 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;
}
//======================================================================
//## 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
? "▼"
: "►";
return this;
},
};
//======================================================================
//## ui/popup.js
/** display a prefab popup menu */
function popup(source, menu, selectListener, abortListener) {
// initially hidden
var visible;
closeMenu();
/** show the popup at mouse position */
function openMenu(ev) {
menu.style.display = "block";
var mouse = mousePos(ev);
var container = parentPos(menu);
var max = {
x: window.scrollX + window.innerWidth,
y: window.scrollY + window.innerHeight
};
var size = {
x: menu.offsetWidth,
y: menu.offsetHeight
};
if (mouse.x + size.x > max.x) mouse.x = max.x - size.x;
if (mouse.y + size.y > max.y) mouse.y = max.y - size.y;
menu.style.left = (mouse.x - container.x) + "px";
menu.style.top = (mouse.y - container.y) + "px";
menu.style.visibility = "visible";
visible = true;
}
/** hide the popup */
function closeMenu() {
menu.style.display = "none";
menu.style.visibility = "hidden";
visible = false;
}
source.oncontextmenu = function(ev) {
openMenu(ev);
return false;
}
document.addEventListener("click",
function(ev) {
if (visible) {
closeMenu();
if (abortListener) abortListener(menu, source);
}
return false;
},
false
);
menu.onclick = function(ev) {
maybeSelectItem(ev);
return false;
}
menu.onmouseup = function(ev) {
if (ev.button != 2) return false;
maybeSelectItem(ev);
return false;
}
function maybeSelectItem(ev) {
var target = ev.target;
for (;;) {
if (target.className
&& target.className.search(/\bpopup-menu-item\b/) != -1) {
closeMenu();
if (selectListener) selectListener(target, menu, source);
return;
}
target = target.parentNode;
if (!target) return;
}
}
//------------------------------------------------------------------------------
//## helper
/** mouse position in document coordinates */
function mousePos(event) {
return {
x: window.pageXOffset + event.clientX,
y: window.pageYOffset + event.clientY
};
}
/** document base coordinates for an object */
function parentPos(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;
}
}
}
//======================================================================
//## ui/SimplePopup.js
/**
* popup constructor with the element to popup on and
* the callback for selection which gets the userdata as
* supplied in the item method
*/
function SimplePopup(source, selectFunc) {
this.source = source;
this.selectFunc = selectFunc;
this.menu = document.createElement("div");
this.menu.className = "popup-menu";
}
SimplePopup.prototype = {
/** call after adding items and separators */
init: function() {
//### does not work, is displayed behind other elements
//this.source.appendChild(this.menu);
document.body.appendChild(this.menu);
// intialize popup
var self = this;
function selected(item) { self.selectFunc(item.userdata); }
popup(this.source, this.menu, selected, null);
},
/** adds an item, its userdata will be supplied to the selectFunc */
item: function(label, userdata) {
var a = document.createElement("a");
a.className = "functionLink";
a.textContent = label;
var item = document.createElement("div");
item.className = "popup-menu-item";
item.userdata = userdata;
item.appendChild(a);
this.menu.appendChild(item);
},
/** adds a separator */
separator: function() {
var separator = document.createElement("hr");
separator.className = "popup-menu-separator";
this.menu.appendChild(separator);
},
};
//======================================================================
//## ui/Action.js
/** creates links */
Action = {
/**
* 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 iput
*/
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;
//mainLink.className += " popupSource";
// optional parameter
if (!popupFunc) popupFunc = func;
var popup = new SimplePopup(mainLink, popupFunc);
// setup groups of items
for (var i=0; i<groups.length; i++) {
var group = groups[i]; // maybe skip null groups
if (i != 0) popup.separator();
for (var j=0; j<group.length; j++) {
var preset = group[j];
popup.item(preset, preset);
}
}
popup.init();
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 = "functionLink";
a.onclick = func;
a.textContent = label;
return a;
},
/** 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;
},
};
//======================================================================
//## ui/ProgressArea.js
/** uses a messageArea 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);
var mainDiv = $('progress-global');
if (mainDiv == null) {
mainDiv = document.createElement("div");
mainDiv.id = 'progress-global';
mainDiv.className = "progress-global";
//var bc = $('bodyContent');
//bc.insertBefore(mainDiv, bc.firstChild);
pasteBefore('bodyContent', mainDiv);
}
mainDiv.appendChild(outerDiv);
this.headerDiv = headerDiv;
this.bodyDiv = bodyDiv;
this.outerDiv = outerDiv;
this.timeout = null;
}
ProgressArea.prototype = {
/** fade delay in millis */
FADE_TIME: 750,
/** display a header text */
header: function(content) {
this.unfade();
removeChildren(this.headerDiv);
pasteEnd(this.headerDiv, content);
},
/** display a body text */
body: function(content) {
this.unfade();
removeChildren(this.bodyDiv);
pasteEnd(this.bodyDiv, content);
},
/** destructor, called by fade */
destroy: function() {
removeNode(this.outerDiv);
},
/** fade out */
fade: function() {
this.timeout = setTimeout(this.destroy.bind(this), this.FADE_TIME);
},
/** inihibit fade */
unfade: function() {
if (this.timeout != null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
};
//======================================================================
//## ui/Portlet.js
/** create a portlet which has to be initialized with either createNew or useExisting */
function Portlet() {}
Portlet.prototype = {
//------------------------------------------------------------------------------
//## initialization
/** create a new portlet, but do not yet display */
createNew: function(id) {
this.outer = document.createElement("div");
this.outer.id = id;
this.outer.className = "portlet";
this.header = document.createElement("h5");
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;
return this;
},
/** init from a MediaWiki-provided portlet and hide it */
useExisting: function(id) {
this.outer = $(id);
this.header = descendants(this.outer, "h5", null, 0);
this.body = descendants(this.outer, "div", "pBody", 0);
this.ul = descendants(this.body, "ul", null, 0);
this.li = null;
removeNode(this.outer);
return this;
},
/**
* display in the sidebar.
* has to be called after useExisting and createNew and after
* all other methods. this is way faster than messing with nodes
* connected to the DOM-tree
*/
show: function() {
SideBar.appendPortlet(this.outer);
return this;
},
//------------------------------------------------------------------------------
//## properties
/** get the header text */
getTitle: function() {
return this.header.textContent;
},
/** set the header text */
setTitle: function(text) {
this.header.textContent = text;
return this;
},
/** get the inner node */
getInner: function() {
return this.body.firstChild;
},
/** set the inner node */
setInner: function(newChild) {
removeChildren(this.body);
this.body.appendChild(newChild);
return this;
},
//------------------------------------------------------------------------------
//## content builing
/**
* 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
*/
build: function(rows) {
this.list();
for (var y=0; y<rows.length; y++) {
var row = rows[y];
// null rows are silently skipped
if (row == null) continue;
// String objects are ids of elements to be stolen
if (row.constructor == String) {
this.steal(row);
continue;
}
this.item();
var first = true;
for (var x=0; x<row.length; x++) {
var cell = row[x];
if (cell == null) continue;
if (first) first = false;
else this.space();
this.append(cell);
}
}
return this;
},
//------------------------------------------------------------------------------
//## private
/** make a list */
list: function() {
if (this.ul) return this;
this.ul = document.createElement("ul");
this.setInner(this.ul);
return this;
},
/** insert a list item, content is optional */
item: function(content) {
this.li = document.createElement("li");
if (content) {
this.li.appendChild(content);
}
this.ul.appendChild(this.li);
return this;
},
/** steal a list item from another portlet */
steal: function(id) {
var element = $(id);
if (!element) return this;
removeNode(element);
this.ul.appendChild(element);
return this;
},
/** append an element to the current list item */
append: function(child) {
this.li.appendChild(child);
return this;
},
/** create a small space within the current list item */
space: function() {
var s = document.createTextNode(" ");
this.li.appendChild(s);
return this;
},
};
//======================================================================
//## ui/SideBar.js
/** encapsulates column-one */
SideBar = {
//------------------------------------------------------------------------------
//## public methods
/** change labels of item links. data is an Array of name/label Arrays */
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]);
}
},
/** remove the go button, i want to search */
removeSearchGoButton: function() {
var node = document.forms['searchform'].elements['go'];
removeNode(node);
},
/** move p-cactions out of column-one so it does not inherit its position:fixed */
unfixCactions: function() {
var pCactions = $('p-cactions');
var columnContent = $('column-content');
pCactions.parentNode.removeChild(pCactions);
columnContent.insertBefore(pCactions, columnContent.firstChild);
},
/** adds a tab */
addCactionTab: function(id, content) {
// ta[id] = ['g', 'Show logs for this page'];
var li = document.createElement("li");
li.id = id;
li.appendChild(content);
var tabs = descendants('p-cactions', "ul", null, 0);
tabs.appendChild(li);
},
/** insert a select box to replace the pLang portlet */
langSelect: function() {
var pLang = $('p-lang');
if (!pLang) return;
var select = document.createElement("select");
select.id = "langSelect";
select.options[0] = new Option(SideBar.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;
location.href = selected;
}
// replace portlet contents
this.usePortlet('p-lang').setInner(select).show();
},
//------------------------------------------------------------------------------
/** return a portlet object for an existing portlet */
usePortlet: function(id) {
return new Portlet().useExisting(id);
},
/** create a new portlet object */
newPortlet: function(id) {
return new Portlet().createNew(id);
},
/** append a portlet to column-one, called by Portlet.show */
appendPortlet: function(portlet) {
var columnOne = $('column-one');
columnOne.appendChild(portlet);
// navigation.parentNode.insertBefore(search, navigation);
},
};
SideBar.msg = {
select: "auswählen",
};
//======================================================================
//## feature/FastAccess.js
/** one-click Log and Specialpage selection */
FastAccess = {
/** returns a link to the logs for a given page */
doPageLogLink: function(title) {
return Action.urlLink(FastAccess.msg.pageLog, Wiki.encodeURL({
title: Wiki.specialNS + ":Log",
page: title
}));
},
/** return a link for fast logfiles access */
doLogLink: function() {
var mainLink = Action.urlLink(FastAccess.msg.logLabel,
Wiki.readURL(Wiki.specialNS + ":Log"));
var popup = new SimplePopup(mainLink, function(userdata) {
window.location.href = userdata;
});
for (var i=0; i<this.logs.length; i++) {
var page = this.logs[i];
popup.item(page, Wiki.encodeURL({
title: Wiki.specialNS + ":Log",
type: page.toLowerCase(),
}));
}
popup.init();
return mainLink;
},
/** return a link for fast logfiles access */
doSpecialLink: function() {
var mainLink = Action.urlLink(FastAccess.msg.specialLabel,
Wiki.readURL(Wiki.specialNS + ":Specialpages"));
var popup = new SimplePopup(mainLink, function(userdata) {
window.location.href = userdata; });
for (var i=0; i<this.specials.length; i++) {
var page = this.specials[i];
popup.item(page, Wiki.encodeURL({
title: Wiki.specialNS + ":" + page,
}));
}
popup.init();
return mainLink;
},
logs: [
"Move", "Newusers", "Block", "Protect", "Delete", "Upload"
],
specials:[
"Allmessages", "Allpages", "BrokenRedirects", "DoubleRedirects", "Ipblocklist", "Linksearch", "Listusers", "Newimages", "Prefixindex",
],
}
FastAccess.msg = {
pageLog: "Seitenlog",
logLabel: "Logbücher",
specialLabel: "Spezialseiten",
};
//======================================================================
//## feature/Usermessage.js
/** changes usermessages */
Usermessage = {
/** onload initializer */
init: function() {
this.historyLink();
},
/** modify the new usermessages to contain only a link to the history of the talkpage */
historyLink: function() {
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 = Usermessage.msg.history;
}
};
Usermessage.msg = {
history: "History",
};
//======================================================================
//## 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 */
doActions: function(lemma) {
return [ this.doView(), this.doMark(lemma) ];
},
/** return the absolute page link */
doView: function() {
return Action.urlLink(UserBookmarks.msg.view,
Wiki.readURL(Wiki.userNS + ":" + Wiki.user, this.PAGE_TITLE));
},
/** add a bookmark on a user's bookmark page. if the page is left out, the current is added */
doMark: function(lemma) {
var self = this;
var msg = UserBookmarks.msg;
return Action.promptPopupLink(msg.add, msg.prompt, msg.reasons, function(reason) {
if (lemma) self.arbitrary(reason, lemma);
else self.current(reason);
});
},
//------------------------------------------------------------------------------
/** user page name */
PAGE_TITLE: "bookmarks",
/** 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 lemma = Page.title;
if (Page.params._smushed) {
lemma += "/" + Page.params._smushed.value;
}
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,
});
}
}
}
var text = "*[[:" + lemma + "]]";
if (perma) text += " <small>[" + perma + " " + mode + "]</small>";
if (remark) text += " " + remark;
text += "\n";
this.prepend(text);
},
/** add text to the bookmarks page */
prepend: function(text) {
var title = Wiki.userNS + ":" + Wiki.user + "/" + this.PAGE_TITLE;
var editor = new Editor(new ProgressArea());
editor.prependText(title, text, "");
},
};
UserBookmarks.msg = {
view: "Bookmarks",
add: "Merken",
prompt: "Bookmark - Kommentar?",
reasons: null,
};
//======================================================================
//## feature/ActionHistory.js
/** helper for action=history */
ActionHistory = {
/** onload initializer */
init: function() {
if (Page.params["action"] != "history") return;
this.addLinks();
},
//------------------------------------------------------------------------------
/** additional links for every version in a page history */
addLinks: function() {
function addLink(li) {
var diffInput = descendants(li, "input", null, 1);
if (!diffInput) return;
// gather data
var histSpan = descendants(li, "span", "history-user", 0);
var histA = descendants(histSpan, "a", null, 0);
var dateA = nextElement(diffInput, "a");
var oldid = diffInput.value;
var user = histA.textContent;
var date = dateA.textContent;
// add restore version link
var restore = Action.functionLink(ActionHistory.msg.restore, function() {
var editor = new Editor();
var summary = "restore " + user + " " + date; // TODO: i18n
restore.className = className(restore.className, "running", "");
editor.restoreVersion(Page.title, oldid, summary, function() {
restore.className = className(restore.className, "", "running");
window.location.reload(true);
});
});
var before = diffInput.nextSibling;
pasteBefore(before, [ " [", restore, "] "]);
// add edit link
var edit = Action.urlLink(ActionHistory.msg.edit, Wiki.encodeURL({
title: Page.title,
oldid: oldid,
action: "edit",
}));
var before = diffInput.nextSibling;
pasteBefore(before, [ " [", edit, "] "]);
}
var lis = descendants('pagehistory', "li");
if (!lis) return;
for (var i=0; i<lis.length; i++) {
addLink(lis[i]);
}
},
};
ActionHistory.msg = {
edit: "edit",
restore: "restore",
};
//======================================================================
//## feature/ActionWatch.js
/** page watch and unwatch without reloading the page */
ActionWatch = {
init: function() {
/** initialize link */
function initView() {
var watch = $('ca-watch');
var unwatch = $('ca-unwatch');
if (watch) exchangeItem(watch, true);
else if (unwatch) exchangeItem(unwatch, false);
}
/** show we are talking to the server */
function progressView() {
var watch = descendants('ca-watch', "a", null, 0);
var unwatch = descendants('ca-unwatch', "a", null, 0);
if (watch) watch.className = "running";
if (unwatch) unwatch.className = "running";
}
/** talk to the server */
function changeRemote(watched) {
var editor = new Editor(); // new ProgressArea()
editor.watchedPage(Page.title, watched, updateView(watched));
}
/** replace link */
function updateView(watched) {
return function() {
var watch = $('ca-watch');
var unwatch = $('ca-unwatch');
if ( watched && watch ) exchangeItem(watch, false);
if (!watched && unwatch) exchangeItem(unwatch, true);
}
}
/** 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 ? ActionWatch.msg.watch : ActionWatch.msg.unwatch;
var a = Action.functionLink(label, function() {
progressView();
changeRemote(watchable);
});
li.appendChild(a);
target.parentNode.replaceChild(li, target);
}
initView();
},
};
ActionWatch.msg = {
watch: "Beobachten",
unwatch: "Vergessen",
};
//======================================================================
//## feature/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) return;
if (feature.init) {
feature.init();
}
},
};
//======================================================================
//## feature/SpecialNewpages.js
/** extends Special:Newpages */
SpecialNewpages = {
/** onload initializer */
init: function() {
autoSubmit(document.forms[0], [ "namespace", "username" ]);
this.displayInline();
},
/** a link to new pages */
doNewpages: function() {
return Action.urlLink(SpecialNewpages.msg.newpages, Wiki.encodeURL({
title: Wiki.specialNS + ":Newpages",
limit: 20,
}));
},
//------------------------------------------------------------------------------
/** maximum number of articles displayed inline */
MAX_ARTICLES: 200,
/** extend Special:Newpages with the content of the articles */
displayInline: function() {
// maximum number of bytes an article may have to be loaded immediately
var maxSize = 2048;
/** parse one list item and insert its content */
function extendItem(li) {
// fetch data
var a = li.getElementsByTagName("a")[0];
var title = a.title;
var byteStr = li.innerHTML.replace(/.*\[([0-9.]+) Bytes\].*/, "$1").replace(/\./, "");
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";
// add a FoldButton to 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;
pasteBegin(header, foldButton.button);
// add action links
pasteBegin(header, UserBookmarks.doMark(title));
pasteBegin(header, Template.allPageActions(title));
// change listitem
li.pageTitle = title;
li.contentBytes = bytes;
li.headerDiv = header;
li.bodyDiv = body;
//TODO: set folding-even and folding-odd
li.className = "folding-container";
li.innerHTML = "";
li.appendChild(header);
li.appendChild(body);
if (li.contentBytes <= maxSize) {
loadContent(li);
}
else {
foldButton.change(false);
foldButton.needsLoad = true;
}
}
function loadContent(li) {
li.bodyDiv.textContent = SpecialNewpages.msg.loading;
// load the article content and display it inline
Ajax.call({
url: Wiki.readURL(li.pageTitle, null, { redirect: "no" }),
doneFunc: function(source) {
var content = /<!-- start content -->([^]*)<div class="printfooter">/(source.responseText);
if (content) li.bodyDiv.innerHTML = content[1] + '<div class="visualClear" />';
}
});
}
// find article list
var ol = descendants('bodyContent', "ol", null, 0);
if (!ol) return;
ol.className = "specialNewPages";
// find article list items
var lis = descendants(ol, "li");
for (var i=0; i<lis.length; i++) {
if (i >= this.MAX_ARTICLES) break;
extendItem(lis[i]);
}
},
};
SpecialNewpages.msg = {
newpages: "Neue Artikel",
loading: "lade seite..",
};
//======================================================================
//## feature/SpecialWatchlist.js
/** extensions for Special:Watchlist */
SpecialWatchlist = {
/** onload initializer */
init: function() {
if (Page.params["edit"]) {
this.exportLinks(); // call before extendHeaders!
this.toggleLinks();
}
else if (Page.params["clear"]) {}
else {
this.filterLinks();
autoSubmit(document.forms[0], [ "namespace" ]);
}
},
//------------------------------------------------------------------------------
//## normal mode
/** change the watchlist to make it filterable */
filterLinks: function() {
var ip = /^(\d{1,3}\.){3}\d{1,3}$/;
/** set a source-ip or source-name class on every list item in every ul-special */
function init() {
var uls = descendants(document, "ul", "special");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var lis = descendants(ul, "li");
for (var j=0; j<lis.length; j++) {
var li = lis[j];
var a = descendants(li, "a", null, 3);
li.className = ip(a.textContent)
? "source-ip"
: "source-name";
}
}
}
/** show all list items */
function showAll() {
mode("source-all", "source-ip source-name");
}
/** show all list items from ip users */
function showIp() {
mode("source-ip", "source-all source-name");
}
/** show all list items from logged in users */
function showName() {
mode("source-name", "source-all source-ip");
}
/**
* add and remove classNames from the bodyContent.
* this is used to display or hide li-tags setup in init with CSS
*/
function mode(add,sub) {
var bodyContent = $('bodyContent');
bodyContent.className = className(bodyContent.className, add, sub);
}
// change list items and add buttons
init();
// add buttons to the bodyContent
// TODO: show current state
var target = nextElement($('jump-to-nav'), "h4");
if (target) {
pasteBefore(target, [
SpecialWatchlist.msg.show1,
Action.functionLink(SpecialWatchlist.msg.all, showAll), ' | ',
Action.functionLink(SpecialWatchlist.msg.names, showName), ' | ',
Action.functionLink(SpecialWatchlist.msg.ips, showIp),
SpecialWatchlist.msg.show2,
]);
}
},
//------------------------------------------------------------------------------
//## edit mode
/** extend Special:Watchlist?edit=yes with a links to a wikitext and a csv version */
exportLinks: function() {
// parse and generate wiki and csv text
var wiki = "";
var csv = '"title","namespace","exists"\n';
var ns = "";
var form = descendants(document, "form", null, 0);
var uls = descendants(form, "ul");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var h2 = previousElement(ul);
if (h2) ns = h2.textContent;
wiki += "== " + ns + " ==\n";
var lis = descendants(ul, "li");
for (var j=0; j<lis.length; j++) {
var li = lis[j];
var as = descendants(li, "a");
var a = as[0];
var title = a.title;
var exists = a.className != "new";
wiki += '*[[' + title + ']]'
+ (exists ? "" : " (new)")
+ '\n';
csv += '"' + title.replace(/"/g, '""') + '"' + ','
+ '"' + ns.replace(/"/g, '""') + '"' + ','
+ '"' + (exists ? "yes": "no") + '"' + '\n';
}
}
// create wiki link
var wikiLink = document.createElement("a");
wikiLink.textContent = "watchlist.wkp";
wikiLink.title = "Markup";
wikiLink.href = "data:text/plain;charset=utf-8," + encodeURIComponent(wiki);
// create csv link
var csvLink = document.createElement("a");
csvLink.textContent = "watchlist.csv";
csvLink.title = "CSV";
csvLink.href = "data:text/csv;charset=utf-8," + encodeURIComponent(csv);
// insert links
var target = nextElement($('jump-to-nav'), "form");
pasteBefore(target, [
"export as ", wikiLink, " (Markup) ",
"or as ", csvLink, " (CSV).",
]);
},
/** extends header structure and add toggle buttons for all checkboxes */
toggleLinks: function() {
var form = descendants(document, "form", null, 0)
// be folding-friendly: add a header for the article namespace
var ul = descendants(form, "ul", null, 0);
var articleHdr = document.createElement("h2");
articleHdr.textContent = SpecialWatchlist.msg.article;
pasteBefore(ul, articleHdr);
// add invert buttons for single namespaces
var uls = descendants(form, "ul");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var button = this.toggleButton(ul);
var target = previousElement(ul, "h2");
pasteAfter(target.lastChild, [ ' ', button ]);
}
// be folding-friendly: add a header for the global controls
var globalHdr = document.createElement("h2");
globalHdr.textContent = SpecialWatchlist.msg.global;
var target = form.elements["remove"];
pasteBefore(target, globalHdr);
// add a gobal invert button
var button = this.toggleButton(form);
pasteAfter(globalHdr.lastChild, [ ' ', button ]);
},
/** creates a toggle button for all input children of an element */
toggleButton: function(container) {
return Action.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;
}
});
},
};
SpecialWatchlist.msg = {
invert: "Invertieren",
article: "Artikel",
global: "Alle",
show1: "Änderungen ",
all: "von allen Nutzern",
ips: "nur von Ips",
names: "nur von Angemeldeten",
show2: " anzeigen.",
};
//======================================================================
//## feature/SpecialSpecialpages.js
/** extends Special:Specialpages */
SpecialSpecialpages = {
/** onload initializer */
init: function() {
this.extendLinks();
},
/** make a sorted tables from the links */
extendLinks: function() {
var uls = descendants('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 = descendants(ul, "li", null);
var lines = new Array();
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);
}
pasteBefore(ul, table);
removeNode(ul);
},
};
//======================================================================
//## feature/SpecialLog.js
/** extends Special:Log */
SpecialLog = {
/** onload initializer */
init: function() {
autoSubmit(document.forms[0], [ "type", "user", "page" ]);
},
/** a link to new pages */
doNewusers: function() {
return Action.urlLink(SpecialLog.msg.newusers, Wiki.encodeURL({
title: Wiki.specialNS + ":Log",
type: "newusers",
limit: 20,
}));
},
};
SpecialLog.msg = {
newusers: "Neue Benutzer",
};
//======================================================================
//## feature/SpecialAllpages.js
/** extends Special:Allpages */
SpecialAllpages = {
/** onload initializer */
init: function() {
autoSubmit(document.forms[0], [ "namespace", "nsfrom" ]);
},
};
//======================================================================
//## feature/SpecialRecentchanges.js
/** extends Special:Recentchanges */
SpecialRecentchanges = {
/** onload initializer */
init: function() {
autoSubmit(document.forms[0], [ "namespace", "invert" ]);
},
};
//======================================================================
//## feature/SpecialListusers.js
/** extends Special:Listusers */
SpecialListusers = {
/** onload initializer */
init: function() {
autoSubmit(document.forms[0], [ "group", "username" ]);
},
};
//======================================================================
//## feature/Template.js
/** puts templates into the current page */
Template = new function() {
/** return an Array of links to actions for normal pages */
this.allPageActions = function(title) {
var msg = Template.msg;
return [
Action.promptLink(msg.qs.label, msg.qs.prompt, function(reason) { qs(title, reason); }),
Action.promptLink(msg.la.label, msg.la.prompt, function(reason) { la(title, reason); }),
Action.promptLink(msg.sla.label, msg.sla.prompt, function(reason) { sla(title, reason); }),
];
};
/** return an Array of links for userTalkPages */
this.officialTalks = function(user) {
var templateNames = Template.cfg.officialTalks;
if (!templateNames) return null;
var out = new Array();
for (var i=0; i<templateNames.length; i++) {
out.push(this.doOfficial(user, templateNames[i]));
}
return out;
}
/** return an Array of links for userTalkPages */
this.personalTalks = function(user) {
var templateNames = Template.cfg.personalTalks;
if (!templateNames) return null;
var out = new Array();
for (var i=0; i<templateNames.length; i++) {
out.push(this.doPersonal(user, templateNames[i]));
}
return out;
}
/** link inserting Vorlage:name */
this.doOfficial = function(user, name) {
var title = Wiki.userTalkNS + ":" + user;
return Action.functionLink(name, function() { official(title, name); });
};
/** link inserting User:FooBar/name template */
this.doPersonal = function(user, name) {
var title = Wiki.userTalkNS + ":" + user;
return Action.functionLink(name, function() { personal(title, name); });
};
//------------------------------------------------------------------------------
//## text constants
var r = {
template: function(title) { return "{" + "{" + title + "}" + "}"; },
link: function(title) { return "[" + "[" + title + "]" + "]"; },
link2: function(title, label) { return "[" + "[" + title + "|" + label + "]" + "]"; },
header: function(text) { return "==" + text + "=="; },
dash: "--", // "—" em dash U+2014 —
sig: "~~" + "~~",
sigapp: " -- ~~" + "~~\n",
line: "----",
sp: " ",
lf: "\n",
};
//------------------------------------------------------------------------------
//## simple template insertions
/** puts an SLA template into an article */
function sla(title, reason) {
simple(title, "löschen", reason);
}
/** puts an QS template into an article */
function qs(title, reason) {
enlist(title, "subst:QS", "Wikipedia:Qualitätssicherung", reason);
}
/** puts an LA template into an article */
function la(title, reason) {
enlist(title, "subst:Löschantrag", "Wikipedia:Löschkandidaten", reason);
}
/** puts an "official" template into an article */
function official(title, name) {
var template = "subst:" + name;
var text = r.template(template) + r.sp + r.sig + r.lf;
var sepa = r.line + r.lf;
var editor = new Editor(new ProgressArea());
editor.appendText(title, text, name, sepa, maybeReloadFunc(title));
}
/** puts a named user template into an article */
function personal(title, name) {
var template = "subst:" + Wiki.userNS + ":" + Wiki.user + "/" + name;
var text = r.template(template) + r.sigapp;
var sepa = r.line + r.lf;
var editor = new Editor(new ProgressArea());
editor.appendText(title, text, name, sepa, maybeReloadFunc(title));
}
/** puts a simple template into an article */
function simple(title, template, reason) {
var text = r.template(template) + r.sp + reason + r.sigapp;
var summary = r.template(template) + r.sp + reason;
var sepa = r.line + r.lf;
var editor = new Editor(new ProgressArea());
editor.prependText(title, text, summary, sepa, maybeReloadFunc(title));
}
/** list page on a list page */
function enlist(title, template, listPage, reason) {
var progress = new ProgressArea();
// insert template
function phase1() {
var text = r.template(template) + r.sp + reason + r.sigapp;
var summary = r.template(template) + r.sp + reason;
var sepa = r.line + r.lf;
var editor = new Editor(progress);
editor.prependText(title, text, summary, sepa, phase2);
}
// add to list page
function phase2() {
var page = listPage + "/" + currentDate();
var text = r.header(r.link(title)) + r.lf + reason + r.sigapp;
var summary = r.link(title) + r.sp + r.dash + r.sp + reason;
var sepa = r.lf;
var editor = new Editor(progress);
editor.appendText(page, text, summary, sepa, maybeReloadFunc(title));
}
phase1();
}
/** creates a function to reload the current page, if it has the given title */
function maybeReloadFunc(title) {
return function() {
if (Page.title == title) {
window.location.href = Wiki.readURL(title);
}
}
}
/** 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;
}
};
Template.msg = {
qs: {
label: "QS",
prompt: "QS - Begründung?",
},
la: {
label: "LA",
prompt: "LA - Begründung?",
},
sla: {
label: "SLA",
prompt: "SLA - Begründung?",
},
};
Template.cfg = {
officialTalks: [ "Hallo", "Test" ],
personalTalks: null, // below User:Name/
};
//======================================================================
//## feature/Communication.js
/** communication with Page.owner */
Communication = {
/** generate banks of links */
doCommunicationBanks: function() {
var msg = Communication.msg;
// remove email for IP-users and whois for non-IP-users
var ipOwner = this.isIP(Page.owner);
return [
Template.personalTalks(Page.owner),
Template.officialTalks(Page.owner),
[ Action.urlLink(msg.userHomeAction, Wiki.readURL(Wiki.userNS + ":" + Page.owner)) ],
[ Action.urlLink(msg.userTalkAction, Wiki.readURL(Wiki.userTalkNS + ":" + Page.owner)) ],
[ Action.urlLink(msg.userContribsAction, Wiki.readURL(Wiki.specialNS + ":Contributions", Page.owner)) ],
!ipOwner ? null :
[ Action.urlLink(msg.userWhoisAction, this.whoisURL(Page.owner)) ],
[ Action.urlLink(msg.userBlocklogAction, Wiki.encodeURL({
title: Wiki.specialNS + ":Log",
type: "block",
page: Wiki.userNS + ":" + Page.owner
})) ],
ipOwner ? null :
[ Action.urlLink(msg.userEmailAction, Wiki.readURL(Wiki.specialNS + ":Emailuser", Page.owner)) ],
];
},
/** true when the name String denotes an v4 IP-address */
isIP: function(ip) {
if (!ip.match(/^(\d{1,3}\.){3}\d{1,3}$/)) return false;
var parts = ip.split(/\./);
if (parts.length != 4) return false;
for (var i=0; i<parts.length; i++) {
var byt = parseInt(parts[i]);
if (byt < 0 || byt > 255) return false;
}
return true;
},
/** whois check URL */
whoisURL: function(ip) {
//return "http://www.ripe.net/fcgi-bin/whois?form_type=simple&full_query_string=&&do_search=Search&searchtext=" + ip;
return "http://www.iks-jena.de/cgi-bin/whois?submit=Suchen&charset=iso-8859-1&search=" + ip;
},
};
Communication.msg = {
userWhoisAction: "Whois",
userBlocklogAction: "Blocklog",
userHomeAction: "Benutzerseite",
userTalkAction: "Diskussion",
userEmailAction: "Anmailen",
userContribsAction: "Beiträge",
};
//======================================================================
//## main.js
/** onload hook */
function initialize() {
//------------------------------------------------------------------------------
//## init functions
Page.init(); // gather globally used information
Usermessage.init(); // replace diff=cur with action=history
ActionWatch.init(); // one-click watch and unwatch
ActionHistory.init(); // add edit and block link for every version
Special.init(); // dispatch specialpage extensions
//------------------------------------------------------------------------------
//## p-search
// remove the go button, i want to search on enter
SideBar.removeSearchGoButton();
//------------------------------------------------------------------------------
//## p-cactions
// cactions should move with the page
SideBar.unfixCactions();
SideBar.labelItems([
[ 'ca-talk', "Diskussion" ],
[ 'ca-edit', "Bearbeiten" ],
[ 'ca-viewsource', "Source" ],
[ 'ca-history', "History" ],
[ 'ca-move', "Verschieben" ],
// done in ActionWatch
//[ 'ca-watch', "Beobachten" ],
//[ 'ca-unwatch', "Vergessen" ],
]);
// add a tab to page logs
if (!Page.whichSpecial()) {
SideBar.addCactionTab('ca-logs',
FastAccess.doPageLogLink(Page.title));
}
//------------------------------------------------------------------------------
//## p-tb
SideBar.labelItems([
[ 't-whatlinkshere', "Links hierher" ],
[ 't-recentchangeslinked', "Nahe Änderungen" ],
[ 't-emailuser', "Anmailen" ],
]);
var tools = null;
if (Page.editable) tools = Template.allPageActions(Page.title);
SideBar.usePortlet('p-tb').setTitle("Tools").build([
tools,
UserBookmarks.doActions(),
]).show();
//------------------------------------------------------------------------------
//## p-navigation
SideBar.usePortlet('p-navigation').setTitle("Navigation").build([
'n-recentchanges',
[ SpecialNewpages.doNewpages() ],
[ FastAccess.doLogLink() ],
[ FastAccess.doSpecialLink() ],
// 't-specialpages',
// 't-permalink',
'pt-watchlist',
't-recentchangeslinked',
't-whatlinkshere',
]).show();
//------------------------------------------------------------------------------
//## portlet-communication
if (Page.owner && Page.owner != Wiki.user) {
var banks = Communication.doCommunicationBanks();
// build portlet
SideBar.newPortlet('portlet-communication').setTitle("Kommunikation").build(
Communication.doCommunicationBanks()
).show();
}
//------------------------------------------------------------------------------
//## p-personal
SideBar.labelItems([
[ 'pt-mytalk', "Diskussion" ],
[ 'pt-mycontris', "Beiträge" ],
]);
// cannot use p-personal which has too much styling
SideBar.newPortlet('portlet-personal').setTitle("Persönlich").build([
'pt-userpage',
'pt-mytalk',
'pt-mycontris',
'pt-preferences',
'pt-logout',
// 'pt-watchlist'
]).show();
//------------------------------------------------------------------------------
//## p-lang
// transform list into a select box
SideBar.langSelect();
}
doOnLoad(initialize);
/* </nowiki></pre> */