Benutzer:D/monobook/admin.js
Erscheinungsbild
< Benutzer:D | monobook
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 admins, nur auf firefox 1.5 getestet''' -- [[Benutzer:D/monobook/admin.js]] */
/* <pre><nowiki> */
//======================================================================
//## util/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");
};
//======================================================================
//## util/functions.js
/** find an element in document by its id */
function $(id) {
return document.getElementById(id);
}
/** 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.
}
/** HACK: call a target's zero-argument method after a timeout */
function doLater(target, method, timeout) {
return window.setTimeout(
method.bind(target), timeout);
}
/** 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;
}
/** matches IPv4-like strings */
var ipRE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
/** true when the ip String denotes an IPv4-address */
function isIP(s) {
var m = ipRE(s);
if (!m) return false;
for (var i=1; i<=4; i++) {
var byt = parseInt(m[i]);
if (byt < 0 || byt > 255) return false;
}
return true;
}
//======================================================================
//## util/DOM.js
/** DOM helper functions */
DOM = {
//------------------------------------------------------------------------------
//## finder
/** find descendants of an ancestor by tagName, className and index */
fetch: function(ancestor, tagName, className, index) {
if (ancestor && ancestor.constructor == String) {
ancestor = document.getElementById(ancestor);
}
if (ancestor === null) return null;
var elements = ancestor.getElementsByTagName(tagName ? tagName : "*");
if (className) {
var tmp = [];
for (var i=0; i<elements.length; i++) {
if (this.hasClass(elements[i], className)) {
tmp.push(elements[i]);
}
}
elements = tmp;
}
if (typeof index == "undefined") return elements;
if (index >= elements.length) return null;
return elements[index];
},
/** find the next element from el which has a given nodeName or is non-text */
nextElement: function(el, nodeName) {
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 */
previousElement: function(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; }
}
},
//------------------------------------------------------------------------------
//## manipulation
/** remove a node from its parent node */
removeNode: function(node) {
node.parentNode.removeChild(node);
},
/** removes all children of a node */
removeChildren: function(node) {
while (node.lastChild) node.removeChild(node.lastChild);
},
/** inserts an element before another one. allows an Array for multiple elements and string for textNodes */
pasteBefore: function(target, element) {
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.parentNode.insertBefore(element, target);
}
if (element.constructor == Array) { // works
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 */
pasteAfter: function(target, element) {
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) { // works
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 */
pasteBegin: function(target, element) {
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
if (target.firstChild) target.insertBefore(element, target.firstChild);
else target.appendChild(element);
}
// TODO: does not work with an Array (?)
if (element.constructor == Array) { // order fixed
for (var i=element.length-1; i>=0; i--) { add(element[i]); }
}
else {
add(element);
}
},
/** insert text, element or elements at the end of a target */
pasteEnd: function(target, element) {
function add(element) {
if (element.constructor == String) element = document.createTextNode(element);
target.appendChild(element);
}
if (element.constructor == Array) { // order correct
for (var i=0; i<element.length; i++) { add(element[i]); }
}
else {
add(element);
}
},
//------------------------------------------------------------------------------
//## css classes
/** creates a RegExp matching a className */
classNameRE: function(className) {
return new RegExp("(^|\\s+)" + className.escapeRegexp() + "(\\s+|$)");
},
/** returns an Array of the classes of an element */
getClasses: function(element) {
return element.className.split(/\s+/);
},
/** returns whether an element has a class */
hasClass: function(element, className) {
if (!element.className) return false;
var re = this.classNameRE(className);
return re.test(element.className);
},
/** adds a class to an element, maybe a second time */
addClass: function(element, className) {
if (this.hasClass(element, className)) return;
var old = element.className ? element.className : "";
element.className = (old + " " + className).trim();
},
/** removes a class to an element */
removeClass: function(element, className) {
var re = this.classNameRE(className);
var old = element.className ? element.className : "";
element.className = old.replace(re, "");
},
/** replaces a class in an element with another*/
replaceClass: function(element, oldClassName, newClassName) {
this.removeClass(element, oldClassName);
this.addClass(element, newClassName);
},
//------------------------------------------------------------------------------
//## positioning
/** mouse position in document coordinates */
mousePos: function(event) {
return {
x: window.pageXOffset + event.clientX,
y: window.pageYOffset + event.clientY
};
},
/** document base coordinates for an object */
parentPos: function(element) {
var pos = { x: 0, y: 0 };
for (;;) {
var mode = window.getComputedStyle(element, null).position;
if (mode == "fixed") {
pos.x += window.pageXOffset;
pos.y += window.pageYOffset;
return pos;
}
var parent = element.offsetParent;
if (!parent) return pos;
pos.x += parent.offsetLeft;
pos.y += parent.offsetTop;
element = parent;
}
},
};
//======================================================================
//## util/Loc.js
/**
* tries to behave similar to a Location object
* protocol includes everything before the //
* host is the plain hostname
* port is a number or null
* pathname includes the first slash or is null
* hash includes the leading # or is null
* search includes the leading ? or is null
*/
function Loc(urlStr) {
var m = this.parser(urlStr);
if (!m) throw "cannot parse URL: " + urlStr;
this.local = !m[1];
this.protocol = m[2] ? m[2] : null; // http:
this.host = m[3] ? m[3] : null; // 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 = {};
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;
},
};
//======================================================================
//## util/Ajax.js
/** ajax helper functions */
Ajax = {
/** headers preset for POSTs */
urlEncoded: function(charset) { return {
"Content-Type": "application/x-www-form-urlencoded; charset=" + charset
}},
/** headers preset for POSTs */
multipartFormData: function(boundary, charset) { return {
"Content-Type": "multipart/form-data; boundary=" + boundary + "; charset=" + charset
}},
/** encode an Object or Array into URL parameters. */
encodeArgs: function(args) {
if (!args) return "";
var query = "";
for (var arg in args) {
var key = encodeURIComponent(arg);
var raw = args[arg];
if (raw === null) continue;
var value = encodeURIComponent(raw.toString());
query += "&" + key + "=" + value;
}
if (query == "") return "";
return query.substring(1);
},
/** encode form data as multipart/form-data */
encodeFormData: function(boundary, data) {
var out = "";
for (name in data) {
var raw = data[name];
if (raw === null) continue;
out += '--' + boundary + '\r\n';
out += 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n';
out += raw.toString() + '\r\n';
}
out += '--' + boundary + '--';
return out;
},
/** create and use an XMLHttpRequest with named parameters */
call: function(args) {
// create
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/Wiki.js
/** encoding and decoding of MediaWiki URLs */
Wiki = {
/** the current wiki site without any path */
site: wgServer, // "http://de.wikipedia.org",
/** language of the site */
language: wgContentLanguage, // "de"
/** path to read pages */
readPath: wgArticlePath.replace(/\$1$/, ""), // "/wiki/",
/** path for page actions */
actionPath: wgScriptPath + "/index.php", // "/w/index.php",
/** decoded Special namespace */
specialNS: null,
/** decoded User namespace */
userNS: null,
/** decoded User_talk namespace */
userTalkNS: null,
/** name of the logged in user */
user: wgUserName,
/** whether user has news */
haveNews: function() {
return DOM.fetch('bodyContent', "div", "usermessage", 0) != null;
},
/** compute an URL in the read form without a title parameter. the args object is optional */
readURL: function(lemma, args) {
var args2 = { title: lemma };
for (var key in args) {
args2[key] = args[key];
}
return this.encodeURL(args2, true);
},
/** encode parameters into an URL */
encodeURL: function(args, shorten) {
if (!args.title) throw "encode: 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 "decode: 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
/** to be called onload */
init: function() {
var nss = this.namespaces[this.language];
if (!nss) throw "unconfigured language: " + language;
this.specialNS = nss.special; // -1
this.userNS = nss.user; // 2
this.userTalkNS = nss.userTalk; // 3
},
/** 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,
};
},
/** indexed by language */
namespaces: {
de: {
special: "Spezial",
user: "Benutzer",
userTalk: "Benutzer Diskussion",
},
en: {
special: "Special",
user: "User",
userTalk: "User talk",
},
},
/** some special pages can smush one parameter to the page title */
specialSmush: {
"Emailuser": "target",
"Contributions": "target",
"Whatlinkshere": "target",
"Recentchangeslinked": "target",
"Undelete": "target",
"Linksearch": "target",
"Newpages": "limit",
"Newimages": "limit",
"Wantedpages": "limit",
"Recentchanges": "limit",
"Allpages": "from",
"Prefixindex": "from",
"Log": "type",
"Blockip": "ip",
"Listusers": "group",
"Filepath": "file",
// 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", ],
"Prefixindex": [ "from" ],
"Blockip": [ "ip" ],
"Log": [ "page" ],
"Filepath": [ "file" ],
},
};
//======================================================================
//## core/Page.js
/** represents the current Page */
Page = {
/** returns the name of the current Specialpage or null */
whichSpecial: function() {
// if it contains a slash, unsmushing failed
return this.title.scan(Wiki.specialNS + ":");
},
/** search string of the current location decoded into an Array */
params: null,
/** the namespace of the current page */
namespace: null,
/** title for the current URL ignoring redirects */
title: null,
/** permalink to the current page if one exists or null */
perma: null,
/** whether this page could be deleted */
deletable: false,
/** whether this page could be edited */
editable: false,
/** the user a User or User_talk or Special:Contributions page belongs to */
owner: false,
//------------------------------------------------------------------------------
//## private
/** to be called onload */
init: function() {
this.params = Wiki.decodeURL(location.href);
var m = /(^| )ns-(-?[0-9]+)( |$)/(document.body.className);
if (m) this.namespace = parseInt(m[2]);
// else error
this.title = this.params.title;
this.deletable = $('ca-delete') != null;
this.editable = $('ca-edit') != null;
var a = DOM.fetch('t-permalink', "a", null, 0);
if (a != null) {
this.perma = a.href;
}
var self = this;
(function() {
// try User namespace
var tmp = self.title.scan(Wiki.userNS + ":");
if (tmp) self.owner = tmp.replace(/\/.*/, "");
if (self.owner) return;
// try User_talk namespace
var tmp = self.title.scan(Wiki.userTalkNS + ":");
if (tmp) self.owner = tmp.replace(/\/.*/, "");
if (self.owner) return;
// try some special pages
var special = self.whichSpecial();
if (special == "Contributions" || special == "Emailuser") {
self.owner = self.params.target;
}
else if (special == "Blockip") {
self.owner = self.params.ip;
}
else if (special == "Log" && self.params.type == "block" && self.params.page) {
self.owner = self.params.page.scan(Wiki.userNS + ":");
}
if (self.owner) return;
// try block link
if (!self.owner) {
var a = DOM.fetch('t-blockip', "a", null, 0);
if (a === null) return;
var href = a.attributes.href.value;
var args = Wiki.decodeURL(href);
self.owner = args.ip;
}
})();
},
};
//======================================================================
//## 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);
var args = {
title: title,
action: "edit"
};
function change(v) { return {
wpSummary: summary,
wpMinoredit: minorEdit,
wpTextbox1: replaceFunc(
v.wpTextbox1.replace(/^[\r\n]+$/, "")),
}; }
this.action(args, 200, "editform", change, 200, doneFunc);
},
/** add text to the end of a spage, the separator is optional */
appendText: function(title, text, summary, separator, doneFunc) {
this.indicator.header("appending to page " + title);
var args = {
title: title,
action: "edit"
};
function change(v) { return {
wpSummary: summary,
wpTextbox1: concatSeparated(
v.wpTextbox1.replace(/^[\r\n]+$/, ""),
separator,
text),
}; }
this.action(args, 200, "editform", change, 200, doneFunc);
},
/** add text to the start of a page, the separator is optional */
prependText: function(title, text, summary, separator, doneFunc) {
this.indicator.header("prepending to page " + title);
var args = {
title: title,
action: "edit",
section: 0
};
function change(v) { return {
wpSummary: summary,
wpTextbox1: concatSeparated(
text,
separator,
v.wpTextbox1.replace(/^[\r\n]+$/, "")),
}; }
this.action(args, 200, "editform", change, 200, doneFunc);
},
/** restores a page to an older version */
restoreVersion: function(title, oldid, summary, doneFunc) {
this.indicator.header("restoring page " + title);
var args = {
title: title,
oldid: oldid,
action: "edit"
};
function change(v) { return {
wpSummary: summary,
}; }
this.action(args, 200, "editform", change, 200, doneFunc);
},
//------------------------------------------------------------------------------
//## change page state
/** watch or unwatch a page. the doneFuncis optional */
watchedPage: function(title, watch, doneFunc) {
var action = watch ? "watch" : "unwatch";
this.indicator.header(action + "ing " + title);
var url = Wiki.encodeURL({
title: title,
action: action,
});
this.indicator.getting(url);
var self = this;
function done(source) {
if (source.status != 200) {
self.indicator.failed(source, 200);
return;
}
self.indicator.finished();
if (doneFunc) doneFunc( );
}
Ajax.call({
method: "GET",
url: url,
doneFunc: done,
});
},
/** move a page */
movePage: function(oldTitle, newTitle, reason, withDiscussion, doneFunc) {
this.indicator.header("moving " + oldTitle + " to " + newTitle);
var args = {
title: Wiki.specialNS + ":Movepage",
target: oldTitle, // url-encoded, mandtory
};
function change(v) { return {
wpOldTitle: oldTitle,
wpNewTitle: newTitle,
wpReason: reason,
wpMovetalk: withDiscussion,
}; }
this.action(args, 200, "movepage", change, 200, doneFunc);
},
/** delete a page. if the reason is null, the original reason text is deleted */
deletePage: function(title, reason, doneFunc) {
this.indicator.header("deleting " + title);
var args = {
title: title,
action: "delete"
};
function change(v) { return {
wpReason: reason != null
? concatSeparated(reason, " - ", v.wpReason)
: "",
}; }
this.action(args, 200, "deleteconfirm", change, 200, doneFunc);
},
/**
* change a page's protection state
* allowed values for the levels are "", "autoconfirmed" and "sysop"
*/
protectPage: function(title, levelEdit, levelMove, reason, doneFunc) {
this.indicator.header("protecting " + title);
var args = {
title: title,
action: "protect"
};
function change(v) { return {
"mwProtect-level-edit": levelEdit,
"mwProtect-level-move": levelMove,
"mwProtect-reason": reason,
}; }
// this form does not have a name
this.action(args, 200, 0, change, 200, doneFunc);
},
//------------------------------------------------------------------------------
//## change other data
/** block a user, anonOnly defaults to false, createAccounts defaults to true */
blockUser: function(user, duration, reason, anonOnly, createAccount, doneFunc) {
this.indicator.header("blocking " + user);
var args = {
title: Wiki.specialNS + ":Blockip",
ip: user, // url-encoded, optional
};
function change(v) { return {
wpBlockAddress: user,
wpBlockReason: reason,
wpAnonOnly: anonOnly,
wpCreateAccount: createAccount,
wpBlockOther: duration,
}; }
this.action(args, 200, "blockip", change, 200, doneFunc);
},
//------------------------------------------------------------------------------
//## private
/**
* 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, changeFunc, 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 = self.changedForm(form, changeFunc);
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 XHTML document*/
findForm: function(doc, nameOrIdOrIndex) {
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;
},
/** uses a changeFunc to create Ajax arguments from modified form contents */
changedForm: function(form, changeFunc) {
var original = {};
for (var i=0; i<form.elements.length; i++) {
var element = form.elements[i];
var check = element.type == "radio" || element.type == "checkbox";
original[element.name] = check ? element.checked : element.value;
// select has no value, but (possibly multiple) Options
// the type can be "select-one" or "select-multiple"
// with select-one selectedIndex is usable, else option.selected
}
var changes = changeFunc(original);
var out = {};
for (var i=0; i<form.elements.length; i++) {
var element = form.elements[i];
var changed = element.name in changes;
var value = changed ? changes[element.name] : original[element.name];
if (element.type == "submit" || element.type == "button") {
if (changed) out[element.name] = changes[element.name].toString();
}
else if (element.type == "radio" || element.type == "checkbox") {
if (value) out[element.name] = "1";
}
else if (element.type != "file") { // hidden select password text textarea
out[element.name] = value.toString();
}
}
return out;
},
//------------------------------------------------------------------------------
//## 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(); }
},
};
//======================================================================
//## core/Markup.js
/** WikiText constants */
Markup = {
// own creations
dash: "--", // "—" em dash U+2014 —
sigapp: " -- ~\~\~\~\n",
// enclosing
template_: "\{\{",
_template: "\}\}",
link_: "\[\[",
_link: "\]\]",
_link_: "\|",
h2_: "==",
_h2: "==",
// simple
sig: "~\~\~\~",
line: "----",
// control chars
star: "*",
hash: "#",
colon: ":",
semi: ";",
sp: " ",
lf: "\n",
};
//======================================================================
//## 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/PopupMenu.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 PopupMenu(source, selectFunc, abortFunc) {
this.source = source;
this.selectFunc = selectFunc;
this.abortFunc = abortFunc;
this.init();
}
PopupMenu.prototype = {
/** adds an item, its userdata will be supplied to the selectFunc */
item: function(label, userdata) {
var a = document.createElement("a");
a.className = "link-function";
a.textContent = label;
var item = document.createElement("div");
item.className = "popup-menu-item";
item.userdata = userdata;
item.appendChild(a);
this.menu.appendChild(item);
// could return the item...
},
/** adds a separator */
separator: function() {
var separator = document.createElement("hr");
separator.className = "popup-menu-separator";
this.menu.appendChild(separator);
},
//------------------------------------------------------------------------------
/** setup */
init: function() {
this.menu = document.createElement("div");
this.menu.className = "popup-menu";
// initially hidden
this.visible = true;
this.closeMenu();
DOM.addClass(this.source, "popup-source");
document.body.appendChild(this.menu);
//### does not work, is displayed behind other elements
//this.source.appendChild(this.menu);
// install mouse listeners
var self = this;
this.source.oncontextmenu = function(ev) {
self.openMenu(ev);
return false;
}
this.menu.onmouseup = function(ev) {
// TODO: ensure the window is not closed when
// the button was held down for a really short time
self.maybeSelectItem(ev);
return false;
}
document.addEventListener("mouseup", function(ev) {
self.maybeAbortSelection(ev);
return false;
}, false);
},
/** show the popup at mouse position */
openMenu: function(ev) {
// with display:none we'd havwe no koordinates,
// and the menu still has visibiliy:hidden
this.menu.style.display = "block";
var mouse = DOM.mousePos(ev);
var container = DOM.parentPos(this.menu);
var max = {
x: window.scrollX + window.innerWidth,
y: window.scrollY + window.innerHeight
};
var size = {
x: this.menu.offsetWidth,
y: this.menu.offsetHeight
};
// HACK, why does the menu go too far to the right without this?
size.x += 16;
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;
this.menu.style.left = (mouse.x - container.x) + "px";
this.menu.style.top = (mouse.y - container.y) + "px";
// make menu visible
this.menu.style.visibility = "visible";
this.visible = true;
},
/** hide the popup */
closeMenu: function() {
this.menu.style.display = "none";
this.menu.style.visibility = "hidden";
this.visible = false;
},
/** if a selection happened, tell the client and close */
maybeSelectItem: function(ev) {
var target = ev.target;
for (;;) {
if (DOM.hasClass(target, "popup-menu-item")) {
this.closeMenu();
if (this.selectFunc) this.selectFunc(target.userdata, this.menu, this.source);
return;
}
target = target.parentNode;
if (!target) return;
}
},
/** if no selection happened, tell the client and close */
maybeAbortSelection: function(ev) {
if (!this.visible) return;
this.closeMenu();
if (this.abortFunc) this.abortFunc(this.menu, this.source);
}
};
//======================================================================
//## ui/SwitchBoard.js
/** contains a number of on/off-switches */
function SwitchBoard() {
this.knobs = [];
this.board = document.createElement("span");
this.board.className = "switch-board";
// public
this.component = this.board;
}
SwitchBoard.prototype = {
/** add a knob and set its className */
add: function(knob) {
DOM.addClass(knob, "switch-knob");
DOM.addClass(knob, "switch-off");
this.knobs.push(knob);
this.board.appendChild(knob);
},
/** selects a single knob */
select: function(knob) {
this.changeAll(false);
this.change(knob, true);
},
/** changes selection state of one knob */
change: function(knob, selected) {
if (selected) DOM.replaceClass(knob, "switch-off", "switch-on");
else DOM.replaceClass(knob, "switch-on", "switch-off");
},
/** changes selection state of all knobs */
changeAll: function(selected) {
for (var i=0; i<this.knobs.length; i++) {
this.change(this.knobs[i], selected);
}
},
};
//======================================================================
//## ui/Links.js
/** creates links */
Links = {
/**
* create an action link which
* - onclick queries a text or
* - oncontextmenu opens a popup with default texts
* and calls a single-argument function with it.
*
* groups is an Array of Arrays of preset reason Strings,
* a separator is placed between rows. null is allowed to
* disable the popup.
*
* the popupFunc is optional, when it's given it's called instead
* of the func for a popup reason, but not for manual 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;
// optional parameter
if (!popupFunc) popupFunc = func;
var popup = new PopupMenu(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);
}
}
return mainLink;
},
/** create an action link which onclick queries a text and calls a function with it */
promptLink: function(label, query, func) {
return this.functionLink(label, function() {
var reason = prompt(query);
if (reason != null) func(reason);
});
},
/** create an action link calling a function on click */
functionLink: function(label, func) {
var a = document.createElement("a");
a.className = "link-function";
a.onclick = func;
a.textContent = label;
return a;
},
/** create a link to a readURL */
readLink: function(label, title, args) {
return this.urlLink(label, Wiki.readURL(title, args));
},
/** create a link to an actionURL */
pageLink: function(label, args) {
return this.urlLink(label, Wiki.encodeURL(args));
},
/** create a link to an URL within the current list item */
urlLink: function(label, url) {
var a = document.createElement("a");
a.href = url;
a.textContent = label;
return a;
},
};
//======================================================================
//## 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);
DOM.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();
DOM.removeChildren(this.headerDiv);
DOM.pasteEnd(this.headerDiv, content);
},
/** display a body text */
body: function(content) {
this.unfade();
DOM.removeChildren(this.bodyDiv);
DOM.pasteEnd(this.bodyDiv, content);
},
/** destructor, called by fade */
destroy: function() {
DOM.removeNode(this.outerDiv);
},
/** fade out */
fade: function() {
this.timeout = setTimeout(this.destroy.bind(this), 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(id, title, rows, withoutPBody) {
this.outer = document.createElement("div");
this.outer.id = id;
this.outer.className = "portlet";
if (withoutPBody) {
this.body = this.outer;
}
else {
this.header = document.createElement("h5");
this.header.textContent = title;
this.body = document.createElement("div");
this.body.className = "pBody";
this.outer.appendChild(this.header);
this.outer.appendChild(this.body);
}
this.ul = null;
this.li = null;
this.canLabel = {};
this.render(rows);
// public
this.component = this.outer;
}
Portlet.prototype = {
/** change labels of action links */
labelStolen: function(labels) {
for (var id in labels) {
var target = this.canLabel[id];
if (target) target.textContent = labels[id];
}
},
render: function(rows) {
if (rows.constructor == Array) {
// add rows
this.ul = document.createElement("ul");
this.body.appendChild(this.ul);
this.renderRows(rows);
}
else {
// add singlerow
this.body.appendChild(rows);
}
},
renderRows: function(rows) {
for (var y=0; y<rows.length; y++) {
var row = rows[y];
if (row === null) continue;
if (row.constructor == String) {
// steal row
var element = $(row);
if (element) {
var clone = element.cloneNode(true);
this.ul.appendChild(clone);
this.canLabel[element.id] = clone.firstChild;
}
}
else if (row.constructor == Array) {
// add cells
this.li = document.createElement("li");
this.ul.appendChild(this.li);
this.renderCells(row);
}
else {
// singlecell
this.li = document.createElement("li");
this.ul.appendChild(this.li);
this.li.appendChild(row);
}
}
},
renderCells: function(row) {
var first = true;
for (var x=0; x<row.length; x++) {
var cell = row[x];
if (cell === null) continue;
// insert separator
if (!first) this.li.appendChild(document.createTextNode(" "));
else first = false;
if (cell.constructor == String) {
// steal singlerow as cell
var element = $(cell);
// problem: interferes with relabelling later!
if (element) {
var clone = element.firstChild.cloneNode(true);
this.li.appendChild(clone);
this.canLabel[element.id] = clone;
}
}
else {
// add link
this.li.appendChild(cell);
}
}
},
};
//======================================================================
//## ui/SideBar.js
/** encapsulates column-one */
SideBar = {
/**
* change labels of action links
* root is a common parent of all items, f.e. document
* labels is a Map from id to label
*/
labelItems: function(labels) {
for (var id in labels) {
var el = document.getElementById(id);
if (!el) continue;
var a = el.getElementsByTagName("a")[0];
if (!a) continue;
a.textContent = labels[id];
}
},
//------------------------------------------------------------------------------
/** the portlets remembered in createPortlet and sidplayed in showPortlets */
preparedPortlets: [],
/**
* render an array of arrays of links.
* the outer array may contains strings to steal list items
* null items in the outer array or inner are legal and skipped
* withoutPBody is optional
*/
createPortlet: function(id, title, rows, withoutPBody) {
var portlet = new Portlet(id, title, rows, withoutPBody);
this.preparedPortlets.push(portlet);
return portlet;
},
/** display the portlets created before and remove older ones with the same id */
showPortlets: function() {
var columnOne = $('column-one');
for (var i=0; i<this.preparedPortlets.length; i++) {
var portlet = this.preparedPortlets[i];
var replaces = $(portlet.component.id);
if (replaces) DOM.removeNode(replaces);
columnOne.appendChild(portlet.component);
}
// HACK for speedup, hidden in sideBar.css
columnOne.style.visibility = "visible";
},
};
//======================================================================
//## extend/ActionHistory.js
/** helper for action=history */
ActionHistory = {
/** onload initializer */
init: function() {
if (Page.params["action"] != "history") return;
this.addLinks();
},
//------------------------------------------------------------------------------
//## private
/** additional links for every version in a page history */
addLinks: function() {
function addLink(li) {
var diffInput = DOM.fetch(li, "input", null, 1);
if (!diffInput) return;
// gather data
var histSpan = DOM.fetch(li, "span", "history-user", 0);
var histA = DOM.fetch(histSpan, "a", null, 0);
var dateA = DOM.nextElement(diffInput, "a");
var oldid = diffInput.value;
var user = histA.textContent;
var date = dateA.textContent;
var msg = ActionHistory.msg;
// add restore version link
function done() { window.location.reload(true); }
var summary = msg.restored + " " + user + " " + date;
var restore = FastRestore.linkFastRestoreVersion(Page.title, oldid, summary, done);
var before = diffInput.nextSibling;
DOM.pasteBefore(before, [ " [", restore, "] "]);
// add edit link
var edit = Links.pageLink(msg.edit, {
title: Page.title,
oldid: oldid,
action: "edit",
});
var before = diffInput.nextSibling;
DOM.pasteBefore(before, [ " [", edit, "] "]);
// add block link
var block = Links.readLink(msg.block, Wiki.specialNS + ":Blockip/" + user);
DOM.pasteBefore(histSpan, [ " [", block, "] "]);
}
var lis = DOM.fetch('pagehistory', "li");
if (!lis) return;
for (var i=0; i<lis.length; i++) {
addLink(lis[i]);
}
},
};
ActionHistory.msg = {
edit: "edit",
restored: "zurück auf ",
block: "blocken",
};
//======================================================================
//## extend/ActionDiff.js
/** revert in the background for action=diff */
ActionDiff = {
/** onload initializer */
init: function() {
if (!Page.params["diff"]) return; //if (Page.params["action"] != "history")
this.fastRevert();
this.addLinks();
},
//------------------------------------------------------------------------------
//## private
/** extend rollback links */
fastRevert: function() {
function done(link) {
link.textContent = ActionDiff.msg.reverted;
window.location.href = Wiki.encodeURL({
title: Page.title,
action: "history",
});
}
var td = DOM.fetch(document, "td", "diff-ntitle", 0);
if (td === null) return;
var as = DOM.fetch(td, "a");
for (var i=0; i<as.length; i++) {
var a = as[i];
var params = Wiki.decodeURL(a.href);
if (params.action != "rollback") continue;
Background.immediatize(a, done);
}
},
/** add restore-links */
addLinks: function() {
var msg = ActionDiff.msg;
/** extends one of the two sides */
function extend(tdClassName) {
// get cell
var td = DOM.fetch(document, "td", tdClassName, 0);
if (!td) return;
// extract data
var as = DOM.fetch(td, "a");
if (as.length < 3) return;
var a0 = as[0];
var a1 = as[1];
var a2 = as[2];
// get oldid
var params = Wiki.decodeURL(a0.href);
if (!params.oldid) return;
var oldid = params.oldid;
// get version date
//### hardcoded RegExp!
var versionRE = /Version vom (.*)/;
var dateP = versionRE(a0.textContent);
var date = dateP ? dateP[1] : null;
// get version user
var user = a2.textContent; // not a1!
// add restore version link
function done() {
window.location.href = Wiki.encodeURL({
title: Page.title,
action: "history",
});
}
var summary = msg.restored + " " + user + " " + date;
var restore = FastRestore.linkFastRestoreVersion(Page.title, oldid, summary, done);
DOM.pasteBefore(a1, [ restore, " | "]);
}
extend("diff-ntitle");
extend("diff-otitle");
},
};
ActionDiff.msg = {
reverted: "zurückgesetzt",
restored: "zurück auf ",
};
//======================================================================
//## extend/Special.js
/** dispatcher for Specialpages */
Special = {
/** dispatches calls to Special* objects */
init: function() {
var name = Page.whichSpecial();
if (!name) return;
var feature = window["Special" + name];
if (feature && feature.init) {
feature.init();
}
var elements = this.autoSubmitElements[name];
if (elements) {
this.autoSubmit(document.forms[0], elements);
}
},
/** adds an onchange handler to elements in a form submitting the form and removes the submit button. */
autoSubmit: function(form, elementNames, leaveSubmitAlone) {
if (!form) return;
// if there is only one form, it's the searchform
if (document.forms.length < 2) return;
var elements = form.elements;
function change() { form.submit(); }
for (var i=0; i<elementNames.length; i++) {
var element = elements[elementNames[i]];
if (!element) continue;
element.onchange = change;
}
// would make sense in case of multiple submit buttons!
// if (leaveSubmitAlone) return;
var todo = [];
for (var i=0; i<elements.length; i++) {
var element = elements[i];
if (element.type == "submit") todo.push(element);
}
for (var i=0; i<todo.length; i++) {
DOM.removeNode(todo[i]);
}
},
/** maps Specialpage names to the autosubmitting form elements */
autoSubmitElements: {
Allpages: [ "namespace", "nsfrom" ],
Contributions: [ "namespace" ],
Ipblocklist: [ "title" ],
Linksearch: [ "title" ],
Listusers: [ "group", "username" ],
Log: [ "type", "user", "page" ],
Newimages: [ "wpIlMatch" ],
Newpages: [ "namespace", "username" ],
Prefixindex: [ "namespace", "nsfrom" ],
Recentchanges: [ "namespace", "invert" ],
Watchlist: [ "namespace" ],
},
};
//======================================================================
//## extend/SpecialBlockip.js
/** extends Special:Blockip */
SpecialBlockip = {
/** onload initializer */
init: function() {
this.presetBlockip();
},
//------------------------------------------------------------------------------
//## private
/** fill in default values into the blockip form */
presetBlockip: function() {
var form = document.forms["blockip"];
if (!form) return; // action=success
form.elements["wpBlockExpiry"].value = "other";
form.elements["wpBlockOther"].value = "1 hour";
form.elements["wpBlockReason"].value = SpecialBlockip.msg.standardReason;
form.elements["wpBlockReason"].select();
form.elements["wpBlockReason"].focus();
},
};
SpecialBlockip.msg = {
standardReason: "vandalismus",
};
//======================================================================
//## extend/SpecialContributions.js
/** revert in the background for Special:Contributions */
SpecialContributions = {
/** onload initializer */
init: function() {
doLater(this, this.fastRevert, 150);
},
//------------------------------------------------------------------------------
//## private
/** extend rollback links */
fastRevert: function() {
var ul = DOM.fetch('bodyContent', "ul", null, 0);
if (ul === null) return;
var all = [];
function done(link) { link.textContent = SpecialContributions.msg.reverted; }
var as = DOM.fetch(ul, "a");
for (var i=0; i<as.length; i++) {
var a = as[i];
var params = Wiki.decodeURL(a.href);
if (params.action != "rollback") continue;
Background.immediatize(a, done);
all.push(a);
}
},
};
SpecialContributions.msg = {
reverted: "zurückgesetzt",
};
//======================================================================
//## extend/SpecialNewpages.js
/** extends Special:Newpages */
SpecialNewpages = {
/** onload initializer */
init: function() {
this.displayInline();
},
//------------------------------------------------------------------------------
//## private
/** 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";
// a FoldButton for the header
var foldButton = new FoldButton(true, function(open) {
body.style.display = open ? null : "none";
if (open && foldButton.needsLoad) {
loadContent(li);
foldButton.needsLoad = false;
}
});
foldButton.needsLoad = false;
DOM.pasteBegin(header, foldButton.button);
// add action links
DOM.pasteBegin(header, UserBookmarks.linkMark(title));
DOM.pasteBegin(header, Template.bankAllPage(title));
DOM.pasteBegin(header, FastDelete.linkDeletePopup(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, { 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 = DOM.fetch('bodyContent', "ol", null, 0);
if (!ol) return;
ol.className = "specialNewPages";
// find article list items
var lis = DOM.fetch(ol, "li");
for (var i=0; i<lis.length; i++) {
if (i >= this.MAX_ARTICLES) break;
extendItem(lis[i]);
}
},
};
SpecialNewpages.msg = {
loading: "lade seite..",
};
//======================================================================
//## extend/SpecialSpecialpages.js
/** extends Special:Specialpages */
SpecialSpecialpages = {
/** onload initializer */
init: function() {
this.extendLinks();
},
//------------------------------------------------------------------------------
//## private
/** make a sorted tables from the links */
extendLinks: function() {
var uls = DOM.fetch('bodyContent', "ul", null);
for (var i=uls.length-1; i>=0; i--) {
var ul = uls[i];
this.extendGroup(ul);
}
},
/** make a sorted table from the links of one group */
extendGroup: function(ul) {
var lis = DOM.fetch(ul, "li", null);
var lines = [];
for (var i=0; i<lis.length; i++) {
var li = lis[i];
var a = li.firstChild;
lines.push({
href: a.href,
title: a.title,
text: a.textContent,
});
}
lines.sort(function(a,b) {
return a.title < b.title ? -1
: a.title > b.title ? 1
: 0;
});
var table = document.createElement("table");
for (var i=0; i<lines.length; i++) {
var line = lines[i];
var tr = document.createElement("tr");
var td1 = document.createElement("td");
var a = document.createElement("a");
a.href = line.href;
a.title = line.title;
a.textContent = line.title.scan(Wiki.specialNS + ":");
td1.appendChild(a);
var td2 = document.createElement("td");
var text = document.createTextNode(line.text);
td2.appendChild(text);
tr.appendChild(td1);
tr.appendChild(td2);
table.appendChild(tr);
}
DOM.pasteBefore(ul, table);
DOM.removeNode(ul);
},
};
//======================================================================
//## extend/SpecialUndelete.js
/** extends Special:Undelete */
SpecialUndelete = {
/** onload initializer */
init: function() {
this.toggleAll();
},
//------------------------------------------------------------------------------
//## private
/** add an invert button for all checkboxes */
toggleAll: function() {
var form = document.forms[0];
if (!form) return;
var button = document.createElement("input");
button.type = "button";
button.value = SpecialUndelete.msg.invert;
button.onclick = function() {
var els = form.elements;
for (var i=0; i<els.length; i++) {
var el = els[i];
if (el.type == "checkbox")
el.checked = !el.checked;
}
}
var target = DOM.fetch(form, "ul", null, 2);
// no list if there is only one deleted version
if (target === null) return;
target.parentNode.insertBefore(button, target);
},
};
SpecialUndelete.msg = {
invert: "Invertieren",
};
//======================================================================
//## extend/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 {
doLater(SpecialRecentchanges, SpecialRecentchanges.filterLinks, 150);
}
},
//------------------------------------------------------------------------------
//## 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 = DOM.fetch(document, "form", null, 0);
var uls = DOM.fetch(form, "ul");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var h2 = DOM.previousElement(ul);
if (h2) ns = h2.textContent;
wiki += "== " + ns + " ==\n";
var lis = DOM.fetch(ul, "li");
for (var j=0; j<lis.length; j++) {
var li = lis[j];
var as = DOM.fetch(li, "a");
var a = as[0];
var title = a.title;
var exists = a.className != "new"; // TODO: use hasClass
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 = DOM.nextElement($('jump-to-nav'), "form");
DOM.pasteBefore(target, [
"export as ", wikiLink, " (Markup) ",
"or as ", csvLink, " (CSV).",
]);
},
/** extends header structure and add toggle buttons for all checkboxes */
toggleLinks: function() {
var form = DOM.fetch(document, "form", null, 0)
// be folding-friendly: add a header for the article namespace
var ul = DOM.fetch(form, "ul", null, 0);
var articleHdr = document.createElement("h2");
articleHdr.textContent = SpecialWatchlist.msg.article;
DOM.pasteBefore(ul, articleHdr);
// add invert buttons for single namespaces
var uls = DOM.fetch(form, "ul");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var button = this.toggleButton(ul);
var target = DOM.previousElement(ul, "h2");
DOM.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"];
DOM.pasteBefore(target, globalHdr);
// add a gobal invert button
var button = this.toggleButton(form);
DOM.pasteAfter(globalHdr.lastChild, [ ' ', button ]);
},
/** creates a toggle button for all input children of an element */
toggleButton: function(container) {
return Links.functionLink(SpecialWatchlist.msg.invert, function() {
var inputs = container.getElementsByTagName("input");
for (var i=0; i<inputs.length; i++) {
var el = inputs[i];
if (el.type == "checkbox")
el.checked = !el.checked;
}
});
},
};
SpecialWatchlist.msg = {
invert: "Invertieren",
article: "Artikel",
global: "Alle",
};
//======================================================================
//## extend/SpecialRecentchanges.js
/** extensions for Special:Recentchanges */
SpecialRecentchanges = {
/** onload initializer */
init: function() {
// HACK: uses exactly the same format
doLater(this, this.filterLinks, 150);
},
//------------------------------------------------------------------------------
//## private
/** change the watchlist to make it filterable */
filterLinks: function() {
var bodyContent = $('bodyContent');
// tag list items with is-ip or is-named
var uls = DOM.fetch(bodyContent, "ul", "special");
for (var i=0; i<uls.length; i++) {
var ul = uls[i];
var lis = DOM.fetch(ul, "li");
for (var j=0; j<lis.length; j++) {
var li = lis[j];
var a = DOM.fetch(li, "a", null, 3);
if (isIP(a.textContent)) DOM.addClass(li, "is-ip");
else DOM.addClass(li, "is-named");
}
}
// filtering is done with CSS
function all() {
board.select(linkAll);
DOM.removeClass(bodyContent, "hide-ip");
DOM.removeClass(bodyContent, "hide-named");
}
function ips() {
board.select(linkIPs);
DOM.removeClass(bodyContent, "hide-ip");
DOM.addClass( bodyContent, "hide-named");
}
function names() {
board.select(linkNames);
DOM.addClass( bodyContent, "hide-ip");
DOM.removeClass(bodyContent, "hide-named");
}
var linkAll = Links.functionLink(SpecialRecentchanges.msg.all, all);
var linkIPs = Links.functionLink(SpecialRecentchanges.msg.ips, ips);
var linkNames = Links.functionLink(SpecialRecentchanges.msg.names, names);
var board = new SwitchBoard();
board.add(linkAll);
board.add(linkIPs);
board.add(linkNames);
board.select(linkAll);
var target = DOM.nextElement($('jump-to-nav'), "h4");
if (!target) return;
DOM.pasteBefore(target, [
document.createElement("br"), //### HACK
SpecialRecentchanges.msg.intro,
board.component
]);
},
};
SpecialRecentchanges.msg = {
intro: "Filter: ",
all: "Alle Edits",
ips: "nur von Ips",
names: "nur von Angemeldeten",
};
//======================================================================
//## extend/SpecialPrefixindex.js
/** extends Special:Prefixindex */
SpecialPrefixindex = {
/** onload initializer */
init: function() {
this.sortItems();
},
//------------------------------------------------------------------------------
//## private
/** sort items into a straight list */
sortItems: function() {
var table = DOM.fetch('bodyContent', "table", null, 2);
if (!table) return; // no results
var tds = DOM.fetch(table, "td");
var ol = document.createElement("ol");
for (var i=0; i<tds.length; i++) {
var td = tds[i];
var li = document.createElement("li");
var c = td.firstChild.cloneNode(true)
li.appendChild(c);
ol.appendChild(li);
}
table.parentNode.replaceChild(ol, table);
},
};
//======================================================================
//## feature/ForSite.js
/** links for the whole siwe */
ForSite = {
/** a link to new pages */
linkNewpages: function() {
return Links.pageLink(ForSite.msg.newpages, {
title: Wiki.specialNS + ":Newpages",
limit: 20,
});
},
/** a link to new pages */
linkNewusers: function() {
return Links.pageLink(ForSite.msg.newusers, {
title: Wiki.specialNS + ":Log",
type: "newusers",
limit: 20,
});
},
/** a link to speedy delete candidates */
bankProjectPages: function() {
var pages = ForSite.cfg.projectPages[Wiki.site];
if (!pages) return null;
var out = [];
for (var i=0; i<pages.length; i++) {
var page = pages[i];
var link = Links.readLink(page[0], page[1]);
out.push(link);
}
return out;
},
/** return a link for fast logfiles access */
linkAllLogsPopup: function() {
function selected(userdata) {
window.location.href = Wiki.readURL(Wiki.specialNS + ":Log" + "/" + userdata.toLowerCase());
}
return this.linkAllPopup(
ForSite.msg.logLabel,
Wiki.specialNS + ":Log",
ForSite.cfg.logs,
selected);
},
/** return a link for fast logfiles access */
linkAllSpecialsPopup: function() {
function selected(userdata) {
window.location.href = Wiki.readURL(Wiki.specialNS + ":" + userdata);
}
return this.linkAllPopup(
ForSite.msg.specialLabel,
Wiki.specialNS + ":Specialpages",
ForSite.cfg.specials,
selected);
},
//------------------------------------------------------------------------------
//## private
/** returns a linkPopup */
linkAllPopup: function(linkLabel, mainPage, pages, selectFunc) {
var mainLink = Links.readLink(linkLabel, mainPage);
var popup = new PopupMenu(mainLink, selectFunc);
for (var i=0; i<pages.length; i++) {
var page = pages[i];
popup.item(page, page); // the page is the userdata
}
return mainLink;
},
}
ForSite.cfg = {
/** maps sites to an Array of interresting pages */
projectPages: {
"http://de.wikipedia.org": [
[ "WW", "Wikipedia:Wiederherstellungswünsche" ],
[ "EW", "Wikipedia:Entsperrwünsche" ],
[ "VS", "Wikipedia:Vandalensperrung" ],
[ "LK", "Wikipedia:Löschkandidaten" ],
[ "SL", "Kategorie:Wikipedia:Schnelllöschen" ],
],
"http://de.wikiversity.org": [
[ "Löschen", "Kategorie:Wikiversity:Löschen" ],
],
},
/** which logs are displayed in the opoup */
logs: [
"Move", "Newusers", "Block", "Protect", "Delete", "Upload"
],
/** which specialpages are displayed in the opoup */
specials:[
"Allmessages", "Allpages", "BrokenRedirects", "Ipblocklist", "Linksearch", "Listusers", "Newimages", "Prefixindex",
],
};
ForSite.msg = {
logLabel: "Logs",
specialLabel: "Spezial",
newpages: "Neuartikel",
newusers: "Newbies",
};
//======================================================================
//## feature/ForPage.js
/** links for arbitrary pages */
ForPage = {
/** returns a link to the logs for a given page */
linkLogAbout: function(title) {
return Links.pageLink(ForPage.msg.pageLog, {
title: Wiki.specialNS + ":Log",
page: title
});
},
};
ForPage.msg = {
pageLog: "Seitenlog",
};
//======================================================================
//## feature/ForUser.js
/** links for users */
ForUser = {
/** returns a link to the homepage of a user */
linkHome: function(user) {
return Links.readLink(ForUser.msg.home, Wiki.userNS + ":" + user);
},
/** returns a link to the talkpage of a user */
linkTalk: function(user) {
return Links.readLink(ForUser.msg.talk, Wiki.userTalkNS + ":" + user);
},
/** returns a link to new messages or null when none exist */
linkNews: function(user) {
return Links.readLink(ForUser.msg.news, Wiki.userTalkNS + ":" + user, { diff: "cur" });
},
/** returns a link to a users contributions */
linkContribs: function(user) {
return Links.readLink(ForUser.msg.contribs, Wiki.specialNS + ":Contributions/" + user);
},
/** returns a link to the blockpage for a user */
linkBlock: function(user) {
return Links.readLink(ForUser.msg.block, Wiki.specialNS + ":Blockip/" + user);
},
/** returns a link to a users emailpage */
linkEmail: function(user) {
// does not make sense with ipOwner or without realOwner
return Links.readLink(ForUser.msg.email, Wiki.specialNS + ":Emailuser/" + Page.owner)
},
/** returns a link to a users log entries */
linkLogsAbout: function(user) {
return Links.pageLink(ForUser.msg.logsAbout, {
title: Wiki.specialNS + ":Log",
page: Wiki.userNS + ":" + user
});
},
/** returns a link to a users log entries */
linkLogsActor: function(user) {
return Links.pageLink(ForUser.msg.logsActor, {
title: Wiki.specialNS + ":Log",
user: user
});
},
/** returns a link to show subpages of a user */
linkSubpages: function(user) {
return Links.pageLink(ForUser.msg.subpages, {
title: Wiki.specialNS + ":Prefixindex",
namespace: 2, // User
from: user + "/",
});
},
/** whois check */
linkWhois: function(user) {
// do not make sense without ipOwner!
return Links.urlLink(ForUser.msg.whois,
"http://www.iks-jena.de/cgi-bin/whois?submit=Suchen&charset=iso-8859-1&search=" + user);
//return "http://www.ripe.net/fcgi-bin/whois?form_type=simple&full_query_string=&&do_search=Search&searchtext=" + ip;
},
/** senderbase check */
linkSenderbase: function(user) {
// do not make sense without ipOwner!
return Links.urlLink(ForUser.msg.senderbase,
"http://www.senderbase.org/search?searchString=" + user);
},
};
ForUser.msg = {
home: "Benutzer",
talk: "Diskussion",
email: "Anmailen",
contribs: "Beiträge",
block: "Blocken",
news: "☏",
logsAbout: "Logs",
logsActor: "Logs",
subpages: "Sub",
whois: "Whois",
senderbase: "More",
};
//======================================================================
//## feature/FastWatch.js
/** page watch and unwatch without reloading the page */
FastWatch = {
init: function() {
/** initialize link */
function initView() {
var watch = $('ca-watch');
var unwatch = $('ca-unwatch');
if (watch) exchangeItem(watch, true);
if (unwatch) exchangeItem(unwatch, false);
}
/** talk to the server, then updates the UI */
function changeState(watched) {
function update() {
var watch = $('ca-watch');
var unwatch = $('ca-unwatch');
if ( watched && watch ) exchangeItem(watch, false);
if (!watched && unwatch) exchangeItem(unwatch, true);
}
var editor = new Editor(); // new ProgressArea()
editor.watchedPage(Page.title, watched, update);
}
/** create a li with a link in it */
function exchangeItem(target, watchable) {
var li = document.createElement("li");
li.id = watchable ? "ca-watch" : "ca-unwatch";
var label = watchable ? FastWatch.msg.watch : FastWatch.msg.unwatch;
var a = Links.functionLink(label, function() {
DOM.addClass(a, "link-running");
changeState(watchable);
});
DOM.addClass(a, "link-immediate");
li.appendChild(a);
target.parentNode.replaceChild(li, target);
}
initView();
},
};
FastWatch.msg = {
watch: "Beobachten",
unwatch: "Vergessen",
};
//======================================================================
//## feature/FastDelete.js
/** one-click delete */
FastDelete = {
/** returns a link which prompts or popups reasons and then deletes */
linkDeletePopup: function(title) {
var self = this;
var msg = FastDelete.msg;
var cfg = FastDelete.cfg;
return Links.promptPopupLink(msg.label, msg.prompt, cfg.reasons, function(reason) {
self.fastDelete(title, reason);
});
},
/** delete an article with a reason */
fastDelete: function(title, reason) {
var editor = new Editor(new ProgressArea());
editor.deletePage(title, reason);
},
};
FastDelete.cfg = {
reasons: null;
};
FastDelete.msg = {
prompt: "Warum löschen?",
label: "löschen",
};
//======================================================================
//## feature/FastRestore.js
/** page restore mechanisms */
FastRestore = {
/** returns a link restoring a given version */
linkFastRestoreVersion: function(title, oldid, summary, doneFunc) {
var restore = Links.functionLink(FastRestore.msg.restore, function() {
var editor = new Editor();
DOM.addClass(restore, "link-running");
editor.restoreVersion(title, oldid, summary, function() {
DOM.removeClass(restore, "link-running");
doneFunc();
});
});
DOM.addClass(restore, "link-immediate");
return restore;
},
};
FastRestore.msg = {
restore: "restore",
};
//======================================================================
//## feature/Background.js
/** page restore mechanisms */
Background = {
/** make a link act in the background, the doneFunc is called wooth the link */
immediatize: function(link, doneFunc) {
DOM.addClass(link, "link-immediate");
link.onclick = this.immediateOnclick;
link._doneFunc = doneFunc;
},
/** onclick handler function for immediateLink */
immediateOnclick: function() {
var link = this; // (!)
DOM.addClass(link, "link-running");
Ajax.call({
url: link.href,
doneFunc: function(source) {
DOM.removeClass(link, "link-running");
if (link._doneFunc) link._doneFunc(link);
}
});
return false;
},
};
//======================================================================
//## feature/Template.js
/** puts templates into the current page */
Template = {
//------------------------------------------------------------------------------
//## templates for user talk pages
/** return an Array of links for userTalkPages or null if none exist */
bankTalks: function(user) {
var official = this.talksArray(user, Template.cfg.officialTalks, false, false);
var personal = this.talksArray(user, Template.cfg.personalTalks, true, true);
var all = official.concat(personal);
return all.length > 0 ? all : null;
},
/** returns an Array of links to "talk" to a user in different templates */
talksArray: function(user, templateNames, ownTemplate, dashSig) {
var out = [];
for (var i=0; i<templateNames.length; i++) {
out.push(this.linkTalkTo(user, templateNames[i], ownTemplate, dashSig));
}
return out;
},
/** creates a link to "talk" to a user */
linkTalkTo: function(user, templateName, ownTemplate, dashSig) {
var self = this;
// this is simple currying!
function handler() { self.talkTo(user, templateName, ownTemplate, dashSig); }
var link = Links.functionLink(templateName, handler);
DOM.addClass(link, "link-immediate");
return link;
},
/** puts a signed talk-template into a user's talkpage */
talkTo: function(user, templateName, ownTemplate, dashSig) {
var r = Markup;
var title = Wiki.userTalkNS + ":" + user;
var text = r.template_ + "subst:";
if (ownTemplate)
text += Wiki.userNS + ":" + Wiki.user + "/";
text += templateName + r._template + r.sp;
if (dashSig)
text += r.dash + r.sp;
text += r.sig + r.lf;
var sepa = r.line + r.lf;
var editor = new Editor(new ProgressArea());
editor.appendText(title, text, templateName, sepa, this.maybeReloadFunc(title));
},
//------------------------------------------------------------------------------
//## termplates for arbitrary pages
/** return an Array of links to actions for normal pages */
bankAllPage: function(title) {
var msg = Template.msg;
var self = this;
return [
Links.promptLink(msg.qs.label, msg.qs.prompt, function(reason) { self.qs(title, reason); }),
Links.promptLink(msg.la.label, msg.la.prompt, function(reason) { self.la(title, reason); }),
Links.promptLink(msg.sla.label, msg.sla.prompt, function(reason) { self.sla(title, reason); }),
];
},
/** puts an SLA template into an article */
sla: function(title, reason) {
this.simple(title, "löschen", reason);
},
/** puts an QS template into an article */
qs: function(title, reason) {
this.enlist(title, "subst:QS", "Wikipedia:Qualitätssicherung", reason);
},
/** puts an LA template into an article */
la: function(title, reason) {
this.enlist(title, "subst:Löschantrag", "Wikipedia:Löschkandidaten", reason);
},
/** puts a simple template into an article */
simple: function(title, template, reason) {
var r = Markup;
var summary = r.template_ + template + r._template + r.sp + reason;
var text = summary + r.sigapp;
var sepa = r.line + r.lf;
var editor = new Editor(new ProgressArea());
editor.prependText(title, text, summary, sepa, this.maybeReloadFunc(title));
},
/** list page on a list page */
enlist: function(title, template, listPage, reason) {
var r = Markup;
var self = this;
var progress = new ProgressArea();
// insert template
function phase1() {
var summary = r.template_ + template + r._template + r.sp + reason;
var text = summary + r.sigapp;
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 + "/" + self.currentDate();
var text = r.h2_ + r.link_ + title + r._link + r._h2 + r.lf + reason + r.sigapp;
var summary = r.link_ + title + r._link + r.sp + r.dash + r.sp + reason;
var sepa = r.lf;
var editor = new Editor(progress);
editor.appendText(page, text, summary, sepa, self.maybeReloadFunc(title));
}
phase1();
},
//------------------------------------------------------------------------------
//## helper
/** creates a function to reload the current page, if it has the given title */
maybeReloadFunc: function(title) {
return function() {
if (Page.title == title) {
window.location.href = Wiki.readURL(title);
}
}
},
/** returns the current date in the format the LKs are organized */
currentDate: function() {
var months = [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli",
"August", "September", "Oktober", "November", "Dezember" ];
var now = new Date();
var year = now.getYear();
if (year < 999) year += 1900;
return now.getDate() + ". " + months[now.getMonth()] + " " + year;
},
};
Template.cfg = {
officialTalks: [ "Hallo", "Test" ],
personalTalks: [], // below User:Name/
};
Template.msg = {
qs: {
label: "QS",
prompt: "QS - Begründung?",
},
la: {
label: "LA",
prompt: "LA - Begründung?",
},
sla: {
label: "SLA",
prompt: "SLA - Begründung?",
},
};
//======================================================================
//## feature/UserBookmarks.js
/** manages a personal bookmarks page */
UserBookmarks = {
/** return an Array of links for a lemma. if it's left out, uses the current page */
bankView: function(lemma) {
return [ this.linkView(), this.linkMark(lemma) ];
},
/** return the absolute page link */
linkView: function() {
return Links.readLink(UserBookmarks.msg.view, this.pageTitle());
},
/** add a bookmark on a user's bookmark page. if the page is left out, the current is added */
linkMark: function(lemma) {
var self = this;
var msg = UserBookmarks.msg;
var cfg = UserBookmarks.cfg;
return Links.promptPopupLink(msg.add, msg.prompt, cfg.reasons, function(reason) {
if (lemma) self.arbitrary(reason, lemma);
else self.current(reason);
});
},
//------------------------------------------------------------------------------
//## private
/** add a bookmark for an arbitrary page */
arbitrary: function(remark, lemma) {
var text = "*\[\[:" + lemma + "\]\]";
if (remark) text += " " + remark;
text += "\n";
this.prepend(text);
},
/** add a bookmark on a user's bookmark page */
current: function(remark) {
var 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 = Markup.star + Markup.link_ + ":" + lemma + Markup._link;
if (perma) text += " <small>[" + perma + " " + mode + "]</small>";
if (remark) text += " " + remark;
text += Markup.lf;
this.prepend(text);
},
/** add text to the bookmarks page */
prepend: function(text) {
new Editor(new ProgressArea())
.prependText(this.pageTitle(), text, "");
},
/** the title of the current user's bookmarks page */
pageTitle: function() {
return Wiki.userNS + ":" + Wiki.user + "/" + UserBookmarks.cfg.pageTitle;
}
};
UserBookmarks.cfg = {
pageTitle: "bookmarks",
reasons: null,
};
UserBookmarks.msg = {
view: "Bookmarks",
add: "Merken",
prompt: "Bookmark - Kommentar?",
};
//======================================================================
//## portlet/Search.js
/** #p-search */
Search = {
/** remove the go button, i want to search */
init: function() {
var node = document.forms['searchform'].elements['go'];
DOM.removeNode(node);
},
};
//======================================================================
//## portlet/Lang.js
/** #p-lang */
Lang = {
id: 'p-lang',
/** insert a select box to replace the pLang portlet */
init: function() {
var pLang = $(this.id);
if (!pLang) return;
var select = document.createElement("select");
select.id = "langSelect";
select.options[0] = new Option(Lang.msg.select, "");
var list = pLang.getElementsByTagName("a");
for (var i=0; i<list.length; i++) {
var a = list[i];
var label = a.textContent
.replace(/\s*\/.*/, "");
select.options[i+1] = new Option(label, a.href);
}
select.onchange = function() {
var selected = this.options[this.selectedIndex].value;
if (selected == "") return;
location.href = selected;
}
SideBar.createPortlet(this.id, Lang.msg.title, select);
},
};
Lang.msg = {
title: "Languages",
select: "auswählen",
};
//======================================================================
//## portlet/Cactions.js
/** #p-cactions */
Cactions = {
id: "p-cactions",
init: function() {
this.unfix();
SideBar.labelItems(Cactions.msg.labels);
if (Page.namespace >= 0) {
this.addTab('ca-logs',
ForPage.linkLogAbout(Page.title));
}
},
/** move p-cactions out of column-one so it does not inherit its position:fixed */
unfix: function() {
var pCactions = $(this.id);
var columnContent = $('column-content'); // belongs to the SideBar somehow..
pCactions.parentNode.removeChild(pCactions);
columnContent.insertBefore(pCactions, columnContent.firstChild);
},
/** adds a tab */
addTab: function(id, content) {
// ta[id] = ['g', 'Show logs for this page'];
var li = document.createElement("li");
li.id = id;
li.appendChild(content);
var tabs = DOM.fetch(this.id, "ul", null, 0);
tabs.appendChild(li);
},
};
Cactions.msg = {
labels: {
'ca-talk': "Diskussion",
'ca-edit': "Bearbeiten",
'ca-viewsource': "Source",
'ca-history': "History",
'ca-protect': "Schützen",
'ca-unprotect': "Freigeben",
'ca-delete': "Löschen",
'ca-undelete': "Entlöschen",
'ca-move': "Verschieben",
},
};
//======================================================================
//## portlet/Tools.js
/** # p-tb */
Tools = {
id: 'p-tb',
init: function() {
var tools1 = null;
var tools2 = null;
if (Page.editable) {
tools1 = [];
if (Page.deletable)
tools1.push(FastDelete.linkDeletePopup(Page.title));
tools2 = Template.bankAllPage(Page.title);
}
SideBar.createPortlet(this.id, Tools.msg.title, [
tools1,
tools2,
UserBookmarks.bankView(),
]);
},
};
Tools.msg = {
title: "Tools",
};
//======================================================================
//## portlet/Navigation.js
/** #p-navigation */
Navigation = {
id: 'p-navigation',
init: function() {
SideBar.createPortlet(this.id, Navigation.msg.title, [
[ 'n-recentchanges',
'pt-watchlist',
],
[ ForSite.linkNewusers(),
ForSite.linkNewpages(),
],
ForSite.bankProjectPages(),
[ ForSite.linkAllSpecialsPopup(),
ForSite.linkAllLogsPopup(),
],
// 't-specialpages',
// 't-permalink',
[ 't-recentchangeslinked',
't-whatlinkshere',
],
]).labelStolen(Navigation.msg.labels);
},
};
Navigation.msg = {
title: "Navigation",
labels: {
'n-recentchanges': "Changes",
'pt-watchlist': "Watchlist",
't-whatlinkshere': "Hierher",
't-recentchangeslinked': "Umgebung",
},
};
//======================================================================
//## portlet/Personal.js
/** #p-personal */
Personal = {
// cannot use p-personal which has way too much styling
id: 'p-personal2',
init: function() {
SideBar.createPortlet(this.id, Personal.msg.title, [
[ 'pt-userpage',
'pt-mytalk',
( Wiki.haveNews() ? ForUser.linkNews(Wiki.user) : null )
],
[ ForUser.linkSubpages(Wiki.user),
ForUser.linkLogsActor(Wiki.user),
'pt-mycontris',
],
[ 'pt-preferences',
'pt-logout'
],
]).labelStolen(Personal.msg.labels);
},
};
Personal.msg = {
title: "Persönlich",
labels: {
'pt-mytalk': "Diskussion",
'pt-mycontris': "Beiträge",
},
};
//======================================================================
//## portlet/Communication.js
/** #p-communication: communication with Page.owner */
Communication = {
id: 'p-communication',
init: function() {
if (!Page.owner) return;
if (Page.owner == Wiki.user) return;
if (!(this.hasRealOwner()
|| this.isLogForOwner())) return;
var ipOwner = isIP(Page.owner);
SideBar.createPortlet(this.id, Communication.msg.title, [
Template.bankTalks(Page.owner),
[ ForUser.linkHome(Page.owner),
ForUser.linkTalk(Page.owner),
],
[ ForUser.linkSubpages(Page.owner),
ForUser.linkLogsAbout(Page.owner),
ForUser.linkContribs(Page.owner),
],
!ipOwner ? null :
[ ForUser.linkWhois(Page.owner),
ForUser.linkSenderbase(Page.owner),
],
ipOwner ? null :
[ ForUser.linkEmail(Page.owner)
],
[ ForUser.linkBlock(Page.owner),
],
]);
},
/** whether this page's owner really exists */
hasRealOwner: function() {
// only existing users have contributions and they have more links in Special:Contributions
return (Page.namespace == 2 || Page.namespace == 3) && $('t-contributions') != null
|| Page.whichSpecial() == "Contributions" && DOM.fetch('contentSub', "a").length > 2; // 2 or 4
},
/** if this page is a log for the owner */
isLogForOwner: function() {
return Page.whichSpecial() == "Blockip" && Page.params.ip
|| Page.whichSpecial() == "Log" && Page.params.type == "block";
},
};
Communication.msg = {
title: "Kommunikation",
};
//======================================================================
//## main.js
/** onload hook */
function initialize() {
// init features
Wiki.init();
Page.init();
FastWatch.init();
ActionHistory.init();
ActionDiff.init();
Special.init();
// build new portlets
Cactions.init();
Search.init();
Tools.init();
Navigation.init();
Communication.init();
Personal.init();
Lang.init();
// display portlets created before
SideBar.showPortlets();
}
doOnLoad(initialize);
/* </nowiki></pre> */