Benutzer:Notebook/monobook.js
/* <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.
}
/** 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/Page.js
/** represents the current Page */
Page = { init: function() {
//------------------------------------------------------------------------------
//## public info
/** the current wiki site without any path */
this.site = location.protocol + "//" + location.host
+ (location.port ? ":" + location.port : "");
/** search string of the current location decoded into an Array */
this.params = location.search
? decodeArgs(location.search.substring(1))
: {};
/** path to read pages */
this.readPath = null;
/** path for page actions */
this.actionPath = "/w/index.php"; //### generalize
/** decoded Special namespace */
this.specialNS = null;
/** decoded User namespace */
this.userNS = null;
/** decoded User_talk namespace */
this.userTalkNS = null;
/** name of the logged in user or null (should never happen) */
this.user = null;
/** the namespace of the current page */
this.namespace = null;
/** lemma for the current URL ignoring redirects */
this.lemma = null;
/** permalink to the current page if one exists or null */
this.perma = null;
/** the user a User or User_talk or Special:Contributions page belongs to */
this.owner = null;
/** whether a this page could be deleted */
this.deletable = false;
//------------------------------------------------------------------------------
//## public methods
/** compute an URL in action form which may have a title parameter */
this.actionURL = function(args) {
var url = this.site + this.actionPath;
return appendArgs(url, args);
}
/** compute an URL in the read form without a title parameter. args object and target string are optional */
this.readURL = function(lemma, args, target) {
var url = this.site
+ this.readPath
+ encodeTitle(lemma)
+ (target ? "/" + encodeTitle(target) : "");
return appendArgs(url, args);
}
/**
* returns an object containing the name and optionally argument
* when this Page is the named SpecialPage or returns null if not
*/
this.isSpecial = function(testName) {
// remove Special-namespace or fail
var path = this.lemma.scan(this.specialNS + ":");
if (!path) return null;
// check page or fail
var match = /([^\/]+)(\/(.+))?/(path);
var name = match[1];
var arg = match[3];
if (name != testName) return null;
// found it: return name and optional arg
return {
name: match[1],
argument: match[3],
};
}
//------------------------------------------------------------------------------
//## private url encoding and decoding functions
/** append arguments to an url with "?" or "&" depending on context */
function appendArgs(url, args) {
var code = encodeArgs(args);
if (code == "") return url;
return url
+ (url.indexOf("?") == -1 ? "?" : "&")
+ code;
}
/** encode an Object or Array into URL parameters. */
function encodeArgs(args) {
if (!args) return "";
var query = "";
for (arg in args) {
var key = encodeURIComponent(arg);
var raw = args[arg];
if (raw == null) continue;
var value = arg == "title" || arg == "target"
? encodeTitle(raw)
: encodeURIComponent(raw.toString());
query += "&" + key + "=" + value;
}
if (query == "") return "";
return query.substring(1);
}
/** convert URL-parameters into an Array. */
function decodeArgs(search) {
var out = new Object();
if (search == "") return out;
var split = search.split("&");
for (i=0; i<split.length; i++) {
var parts = split[i].split("=");
var key = decodeURIComponent(parts[0]);
var value = key == "title" || key == "target"
? decodeTitle(parts[1])
: decodeURIComponent(parts[1]);
out[key] = value;
}
return out;
}
/** encode a MediaWiki title into URL form */
function encodeTitle(title) {
return encodeURIComponent(title)
.replace(/%3[aA]/g, ":")
.replace(/%2[fF]/g, "/")
.replace(/ /g, "_");
}
/** decode a MediaWiki title from an URL */
function decodeTitle(title) {
// MediaWiki allows "+" for "+" instead of " " since 05jan06
title = title.replace(/\+/g, "%2b");
return decodeURIComponent(title)
.replace(/_/g, " ");
}
//------------------------------------------------------------------------------
//## page info intilialization
// scoping helper
var self = this;
// setup readPath and specialNS
(function() {
var search = $('searchform').action;
var title = search.scan(self.site);
var match = /(.*\/)(.*):.*/(title ? title : search);
self.readPath = match[1];
self.specialNS = decodeTitle(match[2]);
})();
// setup user and userNS
(function() {
// <li id="pt-userpage"><a href="/wiki/Benutzer:D">D</a></li>
// <li id="ca-nstab-user"><a href="/wiki/Benutzer:D">Benutzerseite</a></li>
var a = descendants('pt-userpage', "a", null, 0);
if (a == null) return;
var href = a.attributes.href.value;
var split = href.split(self.readPath);
if (split.length < 2) return;
var title = decodeTitle(split[1]);
var match = /([^:]+):(.*)/(title);
self.userNS = match[1];
self.user = match[2];
})();
// setup userTalkNS
(function() {
//<li id="pt-mytalk"><a href="/wiki/Benutzer_Diskussion:D">Eigene Diskussion</a></li>
var a = descendants('pt-mytalk', "a", null, 0);
if (a == null) return;
var href = a.attributes.href.value;
var split = href.split(self.readPath);
if (split.length < 2) return;
var title = decodeTitle(split[1]);
var match = /([^:]+):(.*)/(title);
self.userTalkNS = match[1];
})();
// setup lemma
(function() {
if (self.params.title) {
if (self.params.target) self.lemma = self.params.title + "/" + self.params.target;
else self.lemma = self.params.title;
}
else {
var scanned = this.location.pathname.scan(self.readPath);
if (scanned != null) self.lemma = decodeTitle(scanned);
else self.lemma = null; //### error!
}
})();
// setup perma
(function() {
// <li id="t-permalink"><a href="/w/index.php?title=Benutzer_Diskussion:D&oldid=12706568">Permanentlink</a></li>
var a = descendants('t-permalink', "a", null, 0);
if (a == null) return;
// to get the oldid use this:
// .replace(/^.*&oldid=([0-9]+).*/, "$1");
self.perma = a.href;
})();
// setup owner
(function() {
// try Special:Contributions
self.owner = self.lemma.scan(self.specialNS + ":Contributions/");
if (self.owner) return;
// try Special:Blockip
self.owner = self.lemma.scan(self.specialNS + ":Blockip/");
if (self.owner) return;
// try Special:Emailuser
self.owner = self.lemma.scan(self.specialNS + ":Emailuser/");
if (self.owner) return;
// try blocklog
(function() {
var log = self.isSpecial("Log");
if (!log) return; // require Special:Log
if (log.argument != "block"
&& self.params["type"] != "block") return; // require Special:Log/block
if (!self.isSpecial("Log")) return; // or Special:Log?type=block
var page = self.params["page"];
if (!page) return; // require &page=User:Name
var title = decodeTitle(page); //### correct??
var user = title.scan(Page.userNS + ":");
if (!user) return;
self.owner = user;
})();
if (self.owner) return;
// <li id="t-blockip"><a href="/wiki/Spezial:Blockip/D">Benutzer blockieren</a></li>
var a = descendants('t-blockip', "a", null, 0);
if (a == null) return;
var href = a.attributes.href.value;
var split = href.split(self.readPath);
if (split.length < 2) return;
var full = decodeTitle(split[1]);
var split2 = full.split(/\//);
if (split2.length < 2) return;
self.owner = split2[1];
})();
// setup contributor, deletable and namespace
(function() {
self.deletable = $('ca-delete') != null;
self.namespace = parseInt(document.body.className.substring("ns-".length));
})();
} };
//======================================================================
//## core/URLs.js
/** generates mediawiki-urls */
URLs = {
/** User:Name */
userHome: function(user) {
var title = Page.userNS + ":" + user;
return Page.readURL(title);
},
/** User_talk:Name */
userTalk: function(user) {
var title = Page.userTalkNS + ":" + user;
return Page.readURL(title);
},
/** User:Name/Subtitle */
userPage: function(user, subTitle) {
var title = Page.userNS + ":" + user;
return Page.readURL(title, null, subTitle);
},
/** Special:Emailuser/Name */
userEmail: function(user) {
var title = Page.specialNS + ":Emailuser";
return Page.readURL(title, null, user);
},
/** Special:Contributions/Name */
userContribs: function(user) {
var title = Page.specialNS + ":Contributions";
return Page.readURL(title, null, user);
},
/** Special:Blockip/Name */
userBlock: function(user) {
var title = Page.specialNS + ":Blockip";
return Page.readURL(title, null, user);
},
/** ?title=Spezial:Log&type=block&user=&page=User%3AName */
userBlocklog: function(user) {
var title = Page.specialNS + ":Log";
var victim = Page.userNS + ":" + user;
return Page.actionURL({
title: title,
type: "block",
user: "",
page: victim
});
},
/** ?title=Special:Log&user=user */
userAllLogs: function(user) {
var title = Page.specialNS + ":Log";
return Page.actionURL({
title: title,
user: user
});
},
//------------------------------------------------------------------------------
/** ?title=Title&oldid=oldid&action=edit */
pageEdit: function(title, oldid) {
if (!oldid) oldid = null; // oldid is optional
return Page.actionURL({
title: Page.lemma,
oldid: oldid,
action: "edit",
});
},
/** ?title=Special:Newpages&limit=limit */
newPages: function(limit) {
if (!limit) limit = 10; // limit is optional
var title = Page.specialNS + ":Newpages";
return Page.actionURL({
title: title,
limit: 20
// , inline: "yes"
});
},
/** ?title=Special:Log */
allLogs: function() {
var title = Page.specialNS + ":Log";
return Page.actionURL({
title: title,
});
},
};
//======================================================================
//## 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.doneState) args.doneState(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 = {
//------------------------------------------------------------------------------
//## simple actions without form
/** watch or unwatch a page. the doneFunc gets the new state and is optional */
watchedPage: function(title, watch, doneFunc) {
var self = this;
var action = watch ? "watch" : "unwatch";
self.indicator.header(action + "ing " + title);
var url = Page.actionURL({
title: title,
action: action,
});
self.indicator.getting(url);
Ajax.call({
method: "GET",
url: url,
doneState: function(source) {
if (source.status != 200) {
self.indicator.failed(source, 200);
return;
}
if (doneFunc) doneFunc(watch);
self.indicator.finished();
},
});
},
//------------------------------------------------------------------------------
//## complex actions with form
/** add text to the start of a page, the separator is optional */
prependText: function(title, text, summary, separator, doneFunc) {
this.indicator.header("prepending to " + title);
this.action(
// section=0 does _not_ insert the summary as a headline
{ 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
);
},
/** add text to the end of a spage, the separator is optional */
appendText: function(title, text, summary, separator, doneFunc) {
this.indicator.header("appending to " + title);
this.action(
// section=new inserts the summary as a headline
{ title: title, action: "edit" },
200,
"editform",
function(f) {
var oldText = f.wpTextbox1.value.replace(/^[\r\n]+$/, "");
var newText = concatSeparated(oldText, separator, text);
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
);
},
/** delete a page. if the reason is null, the original reason text is deleted */
deletePage: function(title, reason, doneFunc) {
this.indicator.header("deleting " + title);
this.action(
{ title: title, action: "delete" },
200,
"deleteconfirm",
function(f) {
var separator = " - "; //### hardcoded
var rs = reason != null
? concatSeparated(reason, separator, f.wpReason.value)
: "";
return {
wpReason: rs,
wpConfirmB: f.wpConfirmB.value,
wpEditToken: f.wpEditToken.value,
};
},
200,
doneFunc
);
},
/** 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 title = Page.specialNS + ":Blockip";
this.action(
{ title: title, target: user },
200,
"blockip",
function(f) { return {
wpBlockAddress: user,
wpBlockReason: reason,
wpAnonOnly: anonOnly ? "1" : null,
wpCreateAccount: createAccount ? "1" : null,
wpBlockExpiry: f.wpBlockExpiry.value,
wpBlockOther: duration,
wpEditToken: f.wpEditToken.value,
wpBlock: f.wpBlock.value,
}},
200,
doneFunc
);
},
/** move a page */
movePage: function(oldTitle, newTitle, reason, withDiscussion, doneFunc) {
this.indicator.header("moving " + oldTitle + " to " + newTitle);
var title = Page.specialNS + ":Movepage";
this.action(
{ 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
);
},
/**
* 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);
this.action(
{ title: title, action: "protect" },
200,
0, // this form does not have a name. arrgh.
function(f) {
return {
// nice names. second arrgh.
"mwProtect-level-edit": levelEdit,
"mwProtect-level-move": levelMove,
"mwProtect-reason": reason, // f["mwProtect-reason"].value,
wpEditToken: f.wpEditToken.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 = Page.actionURL(actionArgs);
self.indicator.getting(url);
Ajax.call({
method: "GET",
url: url,
doneState: 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"); // Ajax.multipartFormData(boundary, "UTF-8")
var body = Ajax.encodeArgs(data); // Ajax.encodeFormData(boundary, data)
// bug #246651 - is this still open?
//headers["Connection"] = "close";
//var boundary = "134j5hkvgnarw4t82raflfjl3aklsjdfhsdlkhflkqe";
//var headers = Ajax.multipartFormData(boundary, "UTF-8")
//var body = Ajax.encodeFormData(boundary, data)
self.indicator.posting(url);
Ajax.call({
method: "POST",
url: url,
headers: headers,
body: body,
doneState: 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/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;
}
ProgressArea.prototype = {
/** destructor, called by fade */
destroy: function() {
removeNode(this.outerDiv);
},
/** display a header text */
header: function(content) {
removeChildren(this.headerDiv);
pasteEnd(this.headerDiv, content);
},
/** display a body text */
body: function(content) {
removeChildren(this.bodyDiv);
pasteEnd(this.bodyDiv, content);
},
/** fade out */
fade: function() {
var self = this;
setTimeout(function() { self.destroy(); }, 1500);
},
};
//======================================================================
//## 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/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.
* the groups are an Array of preset string Arrays.
* a separator is placed between rows.
*/
promptPopupLink: function(label, query, groups, func) {
// the main link calls back with a prompted reason
var mainLink = this.promptLink(label, query, func);
// create the menu
var menu = document.createElement("div");
menu.className = "popup-menu hidden";
/** add a preset link to the menu */
function addPreset(preset) {
var link = self.functionLink(preset, null);
var item = document.createElement("div");
item.className = "popup-menu-item";
item.preset = preset; // user data
item.appendChild(link);
menu.appendChild(item);
}
/** add a separator to the menu */
function addSeparator() {
var separator = document.createElement("hr");
separator.className = "popup-menu-separator";
menu.appendChild(separator);
}
// setup groups of items
var self = this;
for (var i=0; i<groups.length; i++) {
var group = groups[i]; // maybe skip null groups
if (i != 0) addSeparator();
for (var j=0; j<group.length; j++) {
var preset = group[j];
addPreset(preset);
}
}
//### is displayed at the wrong position if not inserted at document root
//mainLink.appendChild(menu);
document.body.appendChild(menu);
// intialize popup
function selected(item) { func(item.preset); }
popup(mainLink, menu, selected, null);
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/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 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();
for (var x=0; x<row.length; x++) {
var cell = row[x];
if (x > 0) this.space();
this.link(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 a link element created by the Action */
link: function(link) {
this.li.appendChild(link);
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['fulltext'];
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);
},
/** 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];
select.options[i+1] = new Option(a.firstChild.textContent, a.href);
}
select.onchange = function() {
var selected = this.options[this.selectedIndex].value;
if (selected == "") return;
location.href = selected;
}
// 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);
},
/** move a single or multiple (shown) portlets portlets to the end of the toolbar */
moveDown: function(id) {
if (id.constructor == Array) {
for (var i=0; i<id.length; i++) {
this.moveDown(id[i]);
}
return;
}
var element = $(id);
if (!element) return;
removeNode(element);
this.appendPortlet(element);
},
};
SideBar.msg = {
select: "auswählen",
};
//======================================================================
//## 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(sourceOrId, menuOrId, selectListener, abortListener) {
// init
var menu = typeof menuOrId == "string" ? document.getElementById(menuOrId) : menuOrId;
var source = typeof sourceOrId == "string" ? document.getElementById(sourceOrId) : sourceOrId;
// initially hide popup menu
menu.className = "popup-menu hidden";
var visible = false;
//------------------------------------------------------------------------------
//## actions
/** show the popup at mouse position */
function showMenu(mouse) {
//TODO: does not work whith an arbitrary parent, the menu must be top-level in the document
var leftPos = window.pageXOffset + mouse.x;
var topPos = window.pageYOffset + mouse.y;
var rightEdge = window.innerWidth - mouse.x;
var bottomEdge = window.innerHeight - mouse.y;
if (menu.offsetWidth + mouse.x > window.innerWidth) leftPos -= menu.offsetWidth;
if (menu.offsetHeight + mouse.y > window.innerHeight) topPos -= menu.offsetHeight;
menu.style.left = leftPos + "px";
menu.style.top = topPos + "px";
menu.className = className(menu.className, "popup-menu visible", "hidden");
visible = true;
}
/** hide the popup */
function hideMenu() {
menu.className = className(menu.className, "popup-menu hidden", "visible");
visible = false;
}
/** the user selected an item */
function selectItem(item) {
hideMenu();
if (selectListener) selectListener(item, menu, source);
}
/** the user possibly aborted selection */
function finish() {
if (!visible) return;
hideMenu();
if (abortListener) abortListener(menu, source);
}
//------------------------------------------------------------------------------
//## wiring
function maybeSelectItem(ev) {
// target is within the item
// currentTarget is the menu
// this is ???
var target = ev.target;
for (;;) {
if (target.className
&& target.className.search(/\bpopup-menu-item\b/) != -1) {
selectItem(target);
return;
}
target = target.parentNode;
if (!target) return;
}
}
source.oncontextmenu = function(ev) {
showMenu({ x: ev.clientX, y: ev.clientY});
return false;
}
document.addEventListener("click",
function(ev) {
if (visible) finish();
return false;
},
false
);
menu.onclick = function(ev) {
maybeSelectItem(ev);
return false;
}
menu.onmouseup = function(ev) {
if (ev.button == 2) maybeSelectItem(ev);
return false;
}
}
//======================================================================
//## Template.js
/** puts templates into the current page */
Template = new function() {
/** return an Array of links to actions for normal pages */
this.allPageActions = function(lemma) {
var msg = Template.msg;
return [
Action.promptLink(msg.qs.label, msg.qs.prompt, function(reason) { qs(lemma, reason); }),
Action.promptLink(msg.la.label, msg.la.prompt, function(reason) { la(lemma, reason); }),
Action.promptLink(msg.sla.label, msg.sla.prompt, function(reason) { sla(lemma, reason); }),
];
};
/** return an Array of links for userTalkPages */
this.userTalkPageActions = function(user, userTemplateNames) {
var out = new Array();
out.push(this.doTest(user));
if (userTemplateNames) {
for (var i=0; i<userTemplateNames.length; i++) {
out.push(this.doPersonal(user, userTemplateNames[i]));
}
}
return out;
}
/** link inserting Vorlage:Test */
this.doTest = function(user) {
var lemma = Page.userTalkNS + ":" + user;
return Action.functionLink("Test", function() { test(lemma); });
};
/** link inserting User:FooBar/name template */
this.doPersonal = function(user, name) {
var lemma = Page.userTalkNS + ":" + user;
return Action.functionLink(name, function() { personal(lemma, 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",
};
//------------------------------------------------------------------------------
//## append templates without a reason
/** returns a function that puts an Test template into an article */
function test(lemma) {
var template = "subst:Test";
var editor = new Editor(new ProgressArea());
editor.appendText(
lemma,
r.template(template) + r.sp + r.sig + r.lf,
r.template(template),
r.line + r.lf
);
}
/** returns a function that puts a named user template into an article */
function personal(lemma, name) {
var template = "subst:" + Page.userNS + ":" + Page.user + "/" + name;
var editor = new Editor(new ProgressArea());
editor.appendText(
lemma,
r.template(template) + r.sigapp,
name,
r.line + r.lf
);
}
//------------------------------------------------------------------------------
//## prepend wikipedia templates with a reason
/** puts an QS template into an article */
function qs(lemma, reason) {
enlist(lemma, "subst:QS", "Wikipedia:Qualitätssicherung", reason);
}
/** puts an LA template into an article */
function la(lemma, reason) {
enlist(lemma, "subst:Löschantrag", "Wikipedia:Löschkandidaten", reason);
}
/** puts an SLA template into an article */
function sla(lemma, reason) {
stamp(lemma, "löschen", reason);
}
/** put template in a page */
function stamp(title, template, reason) {
var editor = new Editor(new ProgressArea());
editor.prependText(
title,
r.template(template) + r.sp + reason + r.sigapp,
r.template(template) + r.sp + reason,
r.line + r.lf
);
}
/** list page on a list page */
function enlist(title, template, listPage, reason) {
stamp(title, template, reason);
var editor = new Editor(new ProgressArea());
editor.appendText(
listPage + "/" + currentDate(),
r.header(r.link(title)) + r.lf + reason + r.sigapp,
r.link(title) + r.sp + r.dash + r.sp + reason,
r.lf
);
}
/** 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: "warum qs?",
},
la: {
label: "la",
prompt: "warum la?",
},
sla: {
label: "sla",
prompt: "warum sla?",
},
};
//======================================================================
//## FastDelete.js
/** one-click delete */
FastDelete = {
/** returns a link which prompts or popups reasons and then deletes */
doDeletePopup: function(title) {
var self = this;
var msg = FastDelete.msg;
return Action.promptPopupLink(msg.label, msg.prompt, msg.reasons, function(reason) {
self.fastDelete(title, reason);
});
},
/** return whole banks of delete links in an array of arrays */
doDeleteBanks: function(title) {
var inp1 = this.msg.reasons;
var out1 = new Array();
for (var i=0; i<inp1.length; i++) {
var inp2 = inp1[i];
var out2 = new Array();
for (var j=0; j<inp2.length; j++) {
var reason = inp2[j];
out2.push(this.doDeleteLink(title, reason));
}
out1.push(out2);
}
return out1;
},
/** return a single delete link */
doDeleteLink: function(title, reason) {
var self = this;
return Action.functionLink(reason, function() {
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.msg = {
label: "wech",
prompt: "warum löschen?",
reasons: [
[ "schrott", "kein artikel",
"irrelevant", "unfug",
"tastaturtest", "zu mager",
"linkcontainer", "werbung",
],
[ "wörterbucheintrag",
"falsche sprache",
"falsches lemma",
"unnötiger redirect",
"versehen",
],
[ "veraltet", "erledigt"
],
],
};
//======================================================================
//## FastBlock.js
/** one-click user block, the hard way */
FastBlock = {
/** returns a link which prompts a reason and then kills a user */
doUserKill: function(user) {
var self = this;
var msg = FastBlock.msg;
return Action.promptLink(msg.label, msg.prompt, function(reason) {
self.killUser(user, reason);
});
},
/** does everythin necessary to block a user indefinitely */
killUser: function(user, reason) {
this.blockIndefinite(user, reason);
this.modifyPage(Page.userNS + ":" + user, reason);
this.modifyPage(Page.userTalkNS + ":" + user, reason);
},
/** blocks a user for an indefinite duration */
blockIndefinite: function(user, reason) {
var duration = "indefinite";
var editor = new Editor(new ProgressArea());
editor.blockUser(user, duration, reason, false, true);
},
/** inserts the template in a page and protects it */
modifyPage: function(title, reason) {
var self = this;
var msg = FastBlock.msg;
var editor = new Editor(new ProgressArea());
var text = msg.template + " " + reason + msg.sigapp;
editor.prependText(title, text, msg.template, msg.separator, function() {
var level = "sysop";
var editor = new Editor(new ProgressArea());
editor.protectPage(title, level, level, msg.template);
});
},
};
FastBlock.msg = {
label: "Killen",
prompt: "warum killen?",
template: "{{gesperrter Benutzer}}",
sigapp: " -- ~~" + "~~\n",
separator: "\n----\n",
//standardReason: "vandalismus",
};
//======================================================================
//## FoldHeaders.js
/** manages folding with a button */
FoldHeaders = {
/** onload initializer */
init: function() {
if (Page.params["action"]) return;
if (Page.params["diff"]) return;
if ($('autofold')) this.install();
},
/** a link folding the current page */
doFold: function() {
return Action.functionLink(FoldHeaders.msg.fold, this.install.bind(this));
},
//------------------------------------------------------------------------------
/** provide an icon for each heading to fold or unfold it */
install: function() {
/** a folder attaching a folder span to a header. end is inclusive. returns a Folder */
function Folder(header, end, even) {
// make body
var body = document.createElement("div");
body.className = "foldHeaders folding-body";
body.style.display = "none";
// fill body
var range = document.createRange();
range.setStartAfter(header);
range.setEndAfter(end);
var contents = range.extractContents();
body.appendChild(contents);
// clear body
var clear = document.createElement("div");
clear.className = "visualClear";
body.appendChild(clear);
// remove the header
var targetParent = header.parentNode;
var targetSibling = header.previousSibling;
targetParent.removeChild(header);
// create a FoldButton
var self = this;
var foldButton = new FoldButton(false, function(open) {
header.style.marginBottom = open ? null : "0";
body.style.display = open ? null : "none";
// fold children injected by recurse
if (self.children) {
for (var i=0; i<self.children.length; i++) {
self.children[i].foldButton.change(open);
}
// HACK: remove children, so only the first open is executed
self.children = null;
}
});
// change the header
header.className = "foldHeaders folding-header";
header.insertBefore(foldButton.button, header.firstChild);
// insert filled container instead of the header
var container = document.createElement("div");
container.className = "foldHeaders folding-container "
+ (even ? "folding-even" : "folding-odd");
container.appendChild(header);
container.appendChild(body);
// replace the old header
if (targetSibling) targetParent.insertBefore(container, targetSibling.nextSibling);
else targetParent.appendChild(container);
this.body = body;
this.foldButton = foldButton;
}
/** attach folders to all header tags of a given depth and below. parent is given children, if present. */
function recurse(parent, depth) {
var element = parent.body ? parent.body : parent;
var tagName = "H" + depth;
var even = depth % 2 == 0;
// inserts a synthetic header if a lower-level header after an editsection is found
function fake() {
// find first editsection
var maybe = descendants(element, "div", "editsection", 0);
if (!maybe) return;
// find link paragraph
maybe = nextElement(maybe);
if (!maybe) return;
if (maybe.nodeName != "P") return;
// find header tag
maybe = nextElement(maybe);
var re = new RegExp("H[" + (depth+1) + "-7]");
if (!re(maybe.nodeName)) return;
var faked = document.createElement(tagName);
var target = element.firstChild;
// hack: in level 2, skip the contentsub
if (depth == 2) target = $('contentSub').nextSibling;
element.insertBefore(faked, target);
}
fake();
var headers = element.getElementsByTagName(tagName);
var folders = new Array();
for (var i=0; i<headers.length; i++) {
var header = headers[i];
// skip some headers we do not want to modify
if (depth == 3 && header.id == "siteSub") continue;
//if (header.parentNode.id == "toctitle") continue;
// TODO: remove <h2>Aktuelle Version</h2>
var end = i < headers.length-1
? contentEnd(headers[i+1])
: element.lastChild;
// in level2 we may need to use #catliks instead of element.lastChild
if (!end) continue;
var folder = new Folder(header, end, even);
if (depth < 6) recurse(folder, depth+1);
folders.push(folder);
}
// inject children into Folder object
if (parent.body) parent.children = folders;
return folders;
}
/** (backwards) find a (inclusive) endpoint for folder content */
function contentEnd(next) {
var maybe = next;
// skip previous paragraph
maybe = previousElement(maybe); if (!maybe) return null;
if (maybe.nodeName != "P") return next.previousSibling;
// skip editsection div
maybe = previousElement(maybe); if (!maybe) return null;
if (maybe.nodeName != "DIV"
|| maybe.className != "editsection") return next.previousSibling;
return maybe.previousSibling;
}
var bodyContent = $('bodyContent');
// prevent double installation
if (bodyContent.foldersInstalled) return;
bodyContent.foldersInstalled = true;
// rip out toc
var toc = $('toc');
if (toc) removeNode(toc); // toc.style.display = "none";
// attach folders at level 2
var pageFolders = recurse(bodyContent, 2);
// attach a meta FoldButton for the page
var pageTitle = descendants('content', "h1", "firstHeading", 0);
var pageButton = new FoldButton(false, function(open) {
for (var i=0; i<pageFolders.length; i++) {
pageFolders[i].foldButton.change(open);
}
});
pageTitle.insertBefore(pageButton.button, pageTitle.firstChild);
},
}
FoldHeaders.msg = {
fold: "fold",
};
//======================================================================
//## 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",
};
//======================================================================
//## UserPage.js
/** cares for pages below the user namespace */
UserPage = {
/** create bank of readLinks to private pages */
doGotoBank: function() {
var names = UserPage.msg.pages;
function addLink(name) {
var link = Action.urlLink(name, URLs.userPage(Page.user, name));
out.push(link);
}
var out = new Array();
for (var i=0; i<names.length; i++) addLink(names[i]);
return out;
},
};
UserPage.msg = {
pages: [ "new", "tmp", "todo", "test" ],
};
//======================================================================
//## UserBookmarks.js
/** manages a personal bookmarks page */
UserBookmarks = {
/** return an Array of links for a lemma */
actions: function(lemma) {
return [ this.doView(), this.doMark(lemma) ];
},
/** return the absolute page link */
doView: function() {
return Action.urlLink(UserBookmarks.msg.view, URLs.userPage(Page.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 on a user's bookmark page */
current: function(remark) {
var lemma = Page.lemma;
var mode = "perma";
var perma = Page.perma;
if (!perma) {
var params = Page.params;
var oldid = params["oldid"];
var target = params["target"];
if (oldid) {
var diff = params["diff"];
if (diff) {
mode = "diff";
if (diff == "prev"
|| diff == "next"
|| diff == "next"
|| diff == "cur") mode = diff;
else
if (diff == "cur"
|| diff == "0") mode = "cur";
perma = Page.actionURL({ title: lemma, oldid: oldid, diff: diff});
}
else {
mode = "old";
perma = Page.actionURL({ title: lemma, oldid: oldid});
}
}
}
var text = "*[[:" + lemma + "]]";
if (perma) text += " <small>[" + perma + " " + mode + "]</small>";
if (remark) text += " " + remark;
text += "\n";
this.prepend(text);
},
/** add a bookmark for an arbitrary page */
arbitrary: function(remark, lemma) {
var text = "*[[:" + lemma + "]]";
if (remark) text += " " + remark;
text += "\n";
this.prepend(text);
},
/** add text to the bookmarks page */
prepend: function(text) {
var title = Page.userNS + ":" + Page.user + "/" + this.PAGE_TITLE;
var editor = new Editor(new ProgressArea());
editor.prependText(title, text, "");
},
};
UserBookmarks.msg = {
view: "bookmarks",
add: "add",
prompt: "bemerkung?",
reasons: [
[ "wech mager",
"wech kein artikel",
"relevanz?",
"urv?",
],
[ "überarbeiten inhalt",
"überarbeiten form",
],
[ "gesperrt",
"interessant",
"wech bleiben",
"faktencheck",
],
],
};
//======================================================================
//## Communication.js
/** communication with Page.owner */
Communication = {
/** generate banks of links */
doCommunicationBanks: function() {
var msg = Communication.msg;
var talkActions = Template.userTalkPageActions(Page.owner, msg.talkActions);
var userContribsAction = [ Action.urlLink(msg.userContribsAction, URLs.userContribs(Page.owner)) ];
var userRipeAction = [ Action.urlLink(msg.userRipeAction, this.ripeURL(Page.owner)) ];
var userBlocklogAction = [ Action.urlLink(msg.userBlocklogAction, URLs.userBlocklog(Page.owner)) ];
var userBlockAction = [ Action.urlLink(msg.userBlockAction, URLs.userBlock(Page.owner)) ];
var userEmailAction = [ Action.urlLink(msg.userEmailAction, URLs.userEmail(Page.owner)) ];
var userHomeAction = [ Action.urlLink(msg.userHomeAction, URLs.userHome(Page.owner)) ];
var userTalkAction = [ Action.urlLink(msg.userTalkAction, URLs.userTalk(Page.owner)) ];
var userKillAction = [ FastBlock.doUserKill(Page.owner) ];
// remove email for IP-users and ripe for non-IP-users
var ipOwner = this.isIP(Page.owner);
if (ipOwner) userEmailAction = null;
else userRipeAction = null;
return [
talkActions,
userHomeAction,
userTalkAction,
userContribsAction,
userRipeAction,
userBlocklogAction,
userBlockAction,
userEmailAction,
userKillAction,
];
},
/** 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;
},
/** ripe check URL */
ripeURL: function(ip) {
return "http://www.ripe.net/fcgi-bin/whois?form_type=simple&full_query_string=&&do_search=Search&searchtext=" + ip;
},
};
Communication.msg = {
talkActions: [ "spielen", "genug" ],
userRipeAction: "Ripe",
userBlocklogAction: "Blocklog",
userBlockAction: "Blockieren",
userKillAction: "Killen",
userHomeAction: "Benutzerseite",
userTalkAction: "Diskussion",
userEmailAction: "Anmailen",
userContribsAction: "Beiträge",
};
//======================================================================
//## ActionHistory.js
/** helper for action=history */
ActionHistory = {
/** onload initializer */
init: function() {
if (Page.params["action"] != "history") return;
this.addBlockAndEditLinks();
},
//------------------------------------------------------------------------------
/** add an edit link and a block link every version in a page history */
addBlockAndEditLinks: 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 edit link
var edit = Action.urlLink(ActionHistory.msg.edit, URLs.pageEdit(Page.lemma, oldid));
var before = diffInput.nextSibling; // li.firstChild;
pasteBefore(before, [ " [", edit, "] "]);
// add block link
var block = Action.urlLink(ActionHistory.msg.block, URLs.userBlock(user));
pasteBefore(histSpan, [ " [", block, "] "]);
}
var lis = descendants('pagehistory', "li");
if (!lis) return;
for (var i=0; i<lis.length; i++) {
addLink(lis[i]);
}
},
};
ActionHistory.msg = {
edit: "edit",
block: "blocken",
};
//======================================================================
//## ActionWatch.js
/** page watch and unwatch without reloading the page */
ActionWatch = {
//<li id="ca-unwatch"><a href="/w/index.php?title=Benutzer:D/test&action=unwatch">Nicht mehr beobachten</a></li>
//<li id="ca-watch"><a href="/w/index.php?title=Benutzer:D/test&action=watch">Beobachten</a></li>
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 = "active";
if (unwatch) unwatch.className = "active";
}
/** talk to the server */
function changeRemote(watched) {
var editor = new Editor(); // new ProgressArea()
editor.watchedPage(Page.lemma, watched, updateView);
}
/** replace link */
function updateView(watched) {
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",
};
//======================================================================
//## SpecialNewpages.js
/** extends Special:Newpages */
SpecialNewpages = {
/** onload initializer */
init: function() {
// early exit, wrong page
if (!Page.isSpecial("Newpages")) return;
//if (Page.params["inline"] != "yes") return;
this.displayInline();
},
/** a link to new pages */
doNewpages: function() {
return Action.urlLink(SpecialNewpages.msg.newpages, URLs.newPages(20));
},
//------------------------------------------------------------------------------
/** 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 = /^[^0-9]*([0-9\.]+)/(a.nextSibling.textContent)[1].replace(/\./g, "");
var byteStr = li.innerHTML.replace(/.*\[([0-9.]+) Bytes\].*/, "$1").replace(/\./, "");
var bytes = parseInt(byteStr);
// HACK: modify link in the header
//a.href = a.href + "?action=history";
// 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";
});
pasteBegin(header, foldButton.button);
// add action links
pasteBegin(header, UserBookmarks.doMark(title));
pasteBegin(header, Template.allPageActions(title));
pasteBegin(header, FastDelete.doDeletePopup(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 {
//### BÄH
var inner = [
SpecialNewpages.msg.longer1,
""+maxSize,
SpecialNewpages.msg.longer2,
SpecialNewpages.msg.load1,
Action.functionLink(SpecialNewpages.msg.loadX,
function() { loadContent(li); }),
SpecialNewpages.msg.load2,
];
for (var i=0; i<inner.length; i++) {
var node = inner[i];
if (node.constructor == String) body.appendChild(document.createTextNode(node));
else body.appendChild(node);
}
}
}
function loadContent(li) {
// load the article content and display it inline
Ajax.call({
url: Page.readURL(li.pageTitle, { redirect: "no" }),
doneState: 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);
ol.className = "specialNewPages";
// find article list items
var lis = descendants(ol, "li");
for (var i=0; i<lis.length; i++) {
extendItem(lis[i]);
}
},
};
SpecialNewpages.msg = {
newpages: "Neue Artikel",
longer1: "länger als ",
longer2: " bytes. ",
load1: "trotzdem ",
loadX: "laden",
load2: ".",
};
//======================================================================
//## SpecialBlockip.js
/** extends Special:Blockip */
SpecialBlockip = {
/** onload initializer */
init: function() {
if (!Page.isSpecial("Blockip")) return;
this.presetBlockip();
},
//------------------------------------------------------------------------------
/** 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",
};
//======================================================================
//## SpecialUndelete.js
/** extends Special:Undelete */
SpecialUndelete = {
/** onload initializer */
init: function() {
if (!Page.isSpecial("Undelete")) return;
this.toggleAll();
},
//------------------------------------------------------------------------------
/** add an invert button for all checkboxes */
toggleAll: function() {
// damn, this form no longer has a name
// var form = document.forms["undelete"];
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 = descendants('undelete', "ul", null, 1);
var target = descendants(form, "ul", null, 2);
if (target == null) return; // no list if there is only one deleted version
target.parentNode.insertBefore(button, target);
},
};
SpecialUndelete.msg = {
invert: "Invertieren",
};
//======================================================================
//## SpecialWatchlist.js
/** extensions for Special:Watchlist */
SpecialWatchlist = {
/** onload initializer */
init: function() {
var watchlist = Page.isSpecial("Watchlist");
if (!watchlist) return;
if (Page.params["edit"] == "yes"
|| watchlist.argument == "edit") {
this.exportLinks(); // call before extendHeaders!
this.toggleLinks();
}
else {
this.filterLinks();
}
},
//------------------------------------------------------------------------------
//## normal mode
/** change the watchlist to make it filterable */
filterLinks: function() {
if (!Page.isSpecial("Watchlist")) return;
if (Page.params["edit"]) return;
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");
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 link to a text/plain 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.",
};
//======================================================================
//## _private.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
SpecialUndelete.init(); // add invert button toggling all checkboxes
SpecialNewpages.init(); // display article content inline, a link is added to the sideBar later
SpecialBlockip.init(); // set default values for a blockip page, a link is addded to the sideBar later
SpecialWatchlist.init(); // add export links to the top of the page and an toggle button for all checkboxes
FoldHeaders.init(); // fold all headers when a div with id autofold is found
//------------------------------------------------------------------------------
//## 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-protect', "Schützen" ],
[ 'ca-unprotect', "Freigeben" ],
[ 'ca-delete', "Löschen" ],
[ 'ca-move', "Verschieben" ],
// done in ActionWatch
//[ 'ca-watch', "Beobachten" ],
//[ 'ca-unwatch', "Vergessen" ],
]);
//------------------------------------------------------------------------------
//## p-tb
SideBar.labelItems([
[ 't-whatlinkshere', "Links hierher" ],
[ 't-recentchangeslinked', "Nahe Änderungen" ],
[ 't-emailuser', "Anmailen" ],
]);
var tools1 = [ FoldHeaders.doFold() ];
if (Page.deletable) {
var multi = FastDelete.doDeletePopup(Page.lemma);
tools1.unshift(multi);
}
SideBar.usePortlet('p-tb').setTitle("Tools").build([
tools1,
Template.allPageActions(Page.lemma),
UserBookmarks.actions(),
UserPage.doGotoBank(),
]).show();
//------------------------------------------------------------------------------
//## portlet-delete
if (Page.deletable)
SideBar.newPortlet("portlet-delete").setTitle("Wech").build(
FastDelete.doDeleteBanks(Page.lemma)
).show();
//------------------------------------------------------------------------------
//## p-navigation
SideBar.usePortlet('p-navigation').setTitle("Navigation").build([
'n-recentchanges',
[ SpecialNewpages.doNewpages() ],
't-specialpages',
[ Action.urlLink("Logbücher", URLs.allLogs()) ], //TODO: wrap up
't-permalink',
'pt-watchlist',
't-recentchangeslinked',
't-whatlinkshere',
]).show();
//------------------------------------------------------------------------------
//## portlet-communication
if (Page.owner && Page.owner != Page.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',
[ Action.urlLink("Adminlog", URLs.userAllLogs(Page.user)) ], //TODO: wrap up
'pt-preferences',
'pt-logout',
// 'pt-watchlist'
]).show();
//------------------------------------------------------------------------------
//## p-lang
// transform list into a select box
SideBar.langSelect();
}
doOnLoad(initialize);
/* </nowiki></pre> */
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.