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
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/bandersnatch]] <nowiki>
/*global mediaWiki, OO*/
(function ($, mw, libs) {
"use strict";

//jscs:disable maximumLineLength
var global = {}, l10n = {
	en: {
		'diff': 'diff',
		'minoreditletter': 'm',
		'boteditletter': 'b',
		'article': 'Content page',
		'pagehist': 'Page history',

		'schnark-bandersnatch-error-abort': 'abort',
		'schnark-bandersnatch-error-editconflict': 'edit conflict',
		'schnark-bandersnatch-error-jserror': 'error in JS code',
		'schnark-bandersnatch-error-lostlogin': 'login got lost',
		'schnark-bandersnatch-error-maxlag': 'database lag too big',
		'schnark-bandersnatch-error-noedit': 'no edit necessary',
		'schnark-bandersnatch-error-notverified': 'discarded',
		'schnark-bandersnatch-error-server': 'server error',
		'schnark-bandersnatch-error-spam': 'detected as spam',
		'schnark-bandersnatch-error-unknown': 'unknown error',
		'schnark-bandersnatch-error-preview': 'Preview failed!',
		'schnark-bandersnatch-error-diff': 'Diff script not loaded!',
		'schnark-bandersnatch-diff-show': 'show diff',
		'schnark-bandersnatch-diff-hide': 'hide diff',
		'schnark-bandersnatch-preview-wait': 'Preview is being generated',

		'schnark-bandersnatch-batch-summary': 'Edit summary:',
		'schnark-bandersnatch-batch-minor': 'minor edit',
		'schnark-bandersnatch-batch-bot': 'bot edit',
		'schnark-bandersnatch-batch-preview': 'Preview',
		'schnark-bandersnatch-batch-diff': 'Diff',
		'schnark-bandersnatch-batch-revert': 'Discard change',
		'schnark-bandersnatch-batch-skip': 'Skip',
		'schnark-bandersnatch-batch-save': 'Save',
		'schnark-bandersnatch-check-no': 'Discard change',
		'schnark-bandersnatch-check-ok': 'Accept change',
		'schnark-bandersnatch-abort': 'Abort',
		'schnark-bandersnatch-step-continue': 'Continue',
		'schnark-bandersnatch-step-back': 'Back',
		'schnark-bandersnatch-step-go': 'Go!',
		'schnark-bandersnatch-step-1-head': 'Pages',
		'schnark-bandersnatch-step-1-search-simple': 'In a first step you must specify which pages should be edited. Enter one title per line to do so. If you use my script search++, you\'ll have a way to use complex queries to find pages.',
		'schnark-bandersnatch-step-1-search-complex': 'In a first step you must specify which pages should be edited. Enter one title per line to do so. You can also use the search.',
		'schnark-bandersnatch-step-1-search-query': 'Search query:',
		'schnark-bandersnatch-step-1-search-submit': 'Search',
		'schnark-bandersnatch-step-1-search-error': 'Error: $1',
		'schnark-bandersnatch-step-1-pages': 'Pages:',
		'schnark-bandersnatch-step-1-xml': 'You also can upload an XML dump.',
		'schnark-bandersnatch-step-1-xml-read': 'Upload an XML dump',
		'schnark-bandersnatch-step-2-head': 'Code',
		'schnark-bandersnatch-step-2-text': 'In a second step you must enter the JavaScript code that should be used to edit the pages. Enter the body of a function with parameters <code>title</code> for the title, and <code>oldText</code> for the old text of the page, which returns an object with properties <code>text</code> (the new text or <code>false</code> to skip), <code>summary</code> (optional for an edit summary, otherwise the default summary will be used), <code>minor</code> (<code>true</code> or <code>false</code> to flag the edit as minor, optional), and <code>bot</code> (likewise).',
		'schnark-bandersnatch-step-2-code': 'Code for edit function',
		'schnark-bandersnatch-step-2-tools': 'Tools',
		'schnark-bandersnatch-step-2-tools-tool': 'Tool',
		'schnark-bandersnatch-step-2-tools-use': 'Usage',
		'schnark-bandersnatch-step-2-tools-load': 'Load',
		'schnark-bandersnatch-tool-load': 'Load',
		'schnark-bandersnatch-tool-noload': 'not necessary',
		'schnark-bandersnatch-tool-replace': 'Simple replacement',
		'schnark-bandersnatch-tool-replace-code': 'newText = oldText.replace(/foo/g, \'bar\');',
		'schnark-bandersnatch-tool-template': 'Template<br>Schnark',
		'schnark-bandersnatch-tool-template-code': 'template = new mw.libs.Template(\'templateName\', oldText);\n//...e.g. template.insert(\'new parameter\', \'new value\');\nnewText = template.toString();',
		'schnark-bandersnatch-step-2-test': 'Test environment',
		'schnark-bandersnatch-step-2-test-title': 'Title:',
		'schnark-bandersnatch-step-2-test-title-default': 'Test title:',
		'schnark-bandersnatch-step-2-test-text': 'Test content:',
		'schnark-bandersnatch-step-2-test-run': 'Run test',
		'schnark-bandersnatch-step-3-head': 'Parameter',
		'schnark-bandersnatch-step-3-text': 'As a last step now enter the default parameters for all edits and make sure that you know what you are doing before clicking "Go!"',
		'schnark-bandersnatch-step-3-summary': 'Edit summary:',
		'schnark-bandersnatch-step-3-minor': 'minor edit',
		'schnark-bandersnatch-step-3-bot': 'bot edit',
		'schnark-bandersnatch-step-3-wait': 'Time between two edits (seconds):',
		'schnark-bandersnatch-step-3-maxlag': 'Maxlag:',
		'schnark-bandersnatch-step-3-maxlag-ignore': 'ignore',
		'schnark-bandersnatch-step-3-maxlag-5x5': '5 seconds, 5 tries',
		'schnark-bandersnatch-step-3-mode': 'Mode:',
		'schnark-bandersnatch-step-3-mode-simulate': 'simulae',
		'schnark-bandersnatch-step-3-mode-batch': 'batch mode',
		'schnark-bandersnatch-step-3-mode-check': 'semi-automatic',
		'schnark-bandersnatch-step-3-mode-auto': 'automatic',
		'schnark-bandersnatch-title': 'Bandersnatch',
		'schnark-bandersnatch-version': 'Bandersnatch (version $1)'
	},
	de: {
		'diff': 'Unterschied',
		'minoreditletter': 'K',
		'boteditletter': 'B',
		'article': 'Seite',
		'pagehist': 'Versionsgeschichte',
		'schnark-bandersnatch-error-abort': 'Abbruch',
		'schnark-bandersnatch-error-editconflict': 'Bearbeitungskonflikt',
		'schnark-bandersnatch-error-jserror': 'Fehler im JS-Code',
		'schnark-bandersnatch-error-lostlogin': 'Login verloren gegangen',
		'schnark-bandersnatch-error-maxlag': 'Datenbank-Lag zu groß',
		'schnark-bandersnatch-error-noedit': 'keine Bearbeitung nötig',
		'schnark-bandersnatch-error-notverified': 'verworfen',
		'schnark-bandersnatch-error-server': 'Serverfehler',
		'schnark-bandersnatch-error-spam': 'als Spam erkannt',
		'schnark-bandersnatch-error-unknown': 'unbekannter Fehler',
		'schnark-bandersnatch-error-preview': 'Vorschau fehlgeschlagen!',
		'schnark-bandersnatch-error-diff': 'Diff-Skript nicht geladen!',
		'schnark-bandersnatch-diff-show': 'Unterschied anzeigen',
		'schnark-bandersnatch-diff-hide': 'Unterschied verstecken',
		'schnark-bandersnatch-preview-wait': 'Vorschau wird ermittelt',
		'schnark-bandersnatch-batch-summary': 'Bearbeitungskommentar:',
		'schnark-bandersnatch-batch-minor': 'kleine Änderung',
		'schnark-bandersnatch-batch-bot': 'Bot-Änderung',
		'schnark-bandersnatch-batch-preview': 'Vorschau',
		'schnark-bandersnatch-batch-diff': 'Unterschied',
		'schnark-bandersnatch-batch-revert': 'Änderung verwerfen',
		'schnark-bandersnatch-batch-skip': 'Überspringen',
		'schnark-bandersnatch-batch-save': 'Speichern',
		'schnark-bandersnatch-check-no': 'Verwerfen',
		'schnark-bandersnatch-check-ok': 'Akzeptieren',
		'schnark-bandersnatch-abort': 'Abbrechen',
		'schnark-bandersnatch-step-continue': 'Weiter',
		'schnark-bandersnatch-step-back': 'Zurück',
		'schnark-bandersnatch-step-go': 'Los!',
		'schnark-bandersnatch-step-1-head': 'Seiten',
		'schnark-bandersnatch-step-1-search-simple': 'Als erstes musst du die Seiten angeben, die bearbeitet werden sollen. Schreibe dazu in jede Zeile genau einen Titel. Wenn du mein Skript search++ verwendest, hast du die Möglichkeit, komplexe Suchanfragen zu verwenden.',
		'schnark-bandersnatch-step-1-search-complex': 'Als erstes musst du die Seiten angeben, die bearbeitet werden sollen. Schreibe dazu in jede Zeile genau einen Titel. Du kannst dazu auch die Suchfunktion nutzen.',
		'schnark-bandersnatch-step-1-search-query': 'Suchanfrage:',
		'schnark-bandersnatch-step-1-search-submit': 'Suchen',
		'schnark-bandersnatch-step-1-search-error': 'Fehler: $1',
		'schnark-bandersnatch-step-1-pages': 'Seiten:',
		'schnark-bandersnatch-step-1-xml': 'Du kannst auch einen XML-Dump einlesen.',
		'schnark-bandersnatch-step-1-xml-read': 'XML-Dump einlesen',
		'schnark-bandersnatch-step-2-head': 'Code',
		'schnark-bandersnatch-step-2-text': 'Als nächstes musst du den JavaScript-Code angeben, der bei der Bearbeitung verwendet werden soll. Gib den Körper einer Funktion an, die die Parameter <code>title</code> für den Titel und <code>oldText</code> für den alten Text der Seite kennt und ein Objekt mit den Eigenschaften <code>text</code> (der neue Text oder <code>false</code> zum Überspringen), <code>summary</code> (optional als Zusammenfassung, es wird die Standard-Zusammenfassung verwendet, wenn die Eigenschaft fehlt), <code>minor</code> (<code>true</code> oder <code>false</code> zur Markierung als kleine Bearbeitung, optional) und <code>bot</code> (ebenso) zurückgibt.',
		'schnark-bandersnatch-step-2-code': 'Code für Bearbeitungsfunktion',
		'schnark-bandersnatch-step-2-tools': 'Werkzeuge',
		'schnark-bandersnatch-step-2-tools-tool': 'Werkzeug',
		'schnark-bandersnatch-step-2-tools-use': 'Verwendung',
		'schnark-bandersnatch-step-2-tools-load': 'Laden',
		'schnark-bandersnatch-tool-load': 'Laden',
		'schnark-bandersnatch-tool-noload': 'nicht nötig',
		'schnark-bandersnatch-tool-replace': 'Einfache Ersetzung',
		'schnark-bandersnatch-tool-replace-code': 'neu = alt.replace(/foo/g, \'bar\');',
		'schnark-bandersnatch-tool-template': 'Template<br>Schnark',
		'schnark-bandersnatch-tool-template-code': 'template = new mw.libs.Template(\'Vorlagenname\', alt);\n//...z. B. template.insert(\'neuer Parameter\', \'neuer Wert\');\nneu = template.toString();',
		'schnark-bandersnatch-step-2-test': 'Testumgebung',
		'schnark-bandersnatch-step-2-test-title': 'Titel:',
		'schnark-bandersnatch-step-2-test-title-default': 'Testtitel',
		'schnark-bandersnatch-step-2-test-text': 'Text:',
		'schnark-bandersnatch-step-2-test-run': 'Testen',
		'schnark-bandersnatch-step-3-head': 'Parameter',
		'schnark-bandersnatch-step-3-text': 'Gib als letztes die Standard-Parameter für alle Bearbeitungen an und stelle sicher, dass du weißt, was du tust, bevor du auf „Los!“ klickst.',
		'schnark-bandersnatch-step-3-summary': 'Bearbeitungskommentar:',
		'schnark-bandersnatch-step-3-minor': 'kleine Änderung',
		'schnark-bandersnatch-step-3-bot': 'Bot-Änderung',
		'schnark-bandersnatch-step-3-wait': 'Sekunden zwischen Bearbeitungen:',
		'schnark-bandersnatch-step-3-maxlag': 'Maxlag:',
		'schnark-bandersnatch-step-3-maxlag-ignore': 'Ignorieren',
		'schnark-bandersnatch-step-3-maxlag-5x5': '5 Sekunden, 5 Versuche',
		'schnark-bandersnatch-step-3-mode': 'Modus:',
		'schnark-bandersnatch-step-3-mode-simulate': 'Simulation',
		'schnark-bandersnatch-step-3-mode-batch': 'Stapelverarbeitung',
		'schnark-bandersnatch-step-3-mode-check': 'halbautomatisch',
		'schnark-bandersnatch-step-3-mode-auto': 'automatisch',
		'schnark-bandersnatch-title': 'Bandersnatch',
		'schnark-bandersnatch-version': 'Bandersnatch (Version $1)'
	},
	'de-ch': {
		'schnark-bandersnatch-error-maxlag': 'Datenbank-Lag zu gross',
		'schnark-bandersnatch-step-3-text': 'Gib als letztes die Standard-Parameter für alle Bearbeitungen an und stelle sicher, dass du weisst, was du tust, bevor du auf «Los!» klickst.'
	},
	'de-formal': {
		'schnark-bandersnatch-step-1-search-simple': 'Als erstes müssen Sie die Seiten angeben, die bearbeitet werden sollen. Schreiben Sie dazu in jede Zeile genau einen Titel. Wenn Sie mein Skript search++ verwenden, haben Sie die Möglichkeit, komplexe Suchanfragen zu verwenden.',
		'schnark-bandersnatch-step-1-search-complex': 'Als erstes müssen Sie die Seiten angeben, die bearbeitet werden sollen. Schreiben Sie dazu in jede Zeile genau einen Titel. Sie können dazu auch die Suchfunktion nutzen.',
		'schnark-bandersnatch-step-1-xml': 'Sie können auch einen XML-Dump einlesen.',
		'schnark-bandersnatch-step-2-text': 'Als nächstes müssen Sie den JavaScript-Code angeben, der bei der Bearbeitung verwendet werden soll. Geben Sie den Körper einer Funktion an, die die Parameter <code>title</code> für den Titel und <code>oldText</code> für den alten Text der Seite kennt und ein Objekt mit den Eigenschaften <code>text</code> (der neue Text oder <code>false</code> zum Überspringen), <code>summary</code> (optional als Zusammenfassung, es wird die Standard-Zusammenfassung verwendet, wenn die Eigenschaft fehlt), <code>minor</code> (<code>true</code> oder <code>false</code> zur Markierung als kleine Bearbeitung, optional) und <code>bot</code> (ebenso) zurückgibt.',
		'schnark-bandersnatch-step-3-text': 'Geben Sie als letztes die Standard-Parameter für alle Bearbeitungen an und stellen Sie sicher, dass Sie wissen, was Sie tun, bevor Sie auf „Los!“ klicken.'
	}
};
//jscs:enable maximumLineLength

global.version = '2.1';

(function () {
//Backend

var ABORT = 0,
	EDITCONFLICT = 1,
	LOSTLOGIN = 2,
	MAXLAG = 3,
	NOEDIT = 4,
	NOTVERIFIED = 5,
	SERVER = 6,
	SPAM = 7,
	UNKNOWN = 8,
	JSERROR = 9;
global.error = {
	ABORT: ABORT,
	EDITCONFLICT: EDITCONFLICT,
	LOSTLOGIN: LOSTLOGIN,
	MAXLAG: MAXLAG,
	NOEDIT: NOEDIT,
	NOTVERIFIED: NOTVERIFIED,
	SERVER: SERVER,
	SPAM: SPAM,
	UNKNOWN: UNKNOWN,
	JSERROR: JSERROR
};

function makeParam (data, maxlag) {
	var param = {
		action: 'edit',
		title: data.title,
		text: data.text,
		summary: data.summary,
		basetimestamp: data.basetimestamp,
		starttimestamp: data.starttimestamp,
		token: data.token,
		format: 'json',
		formatversion: 2
	};
	if (data.bot) {
		param.bot = '';
	}
	if (data.minor) {
		param.minor = '';
	} else {
		param.notminor = '';
	}
	if (maxlag !== false) {
		param.maxlag = maxlag;
	}
	return param;
}

function reallyEdit (data, maxlagF, onSuccess, onError, n) {
	n = n || 0;
	var maxlag = maxlagF(n), headers = {};
	if (maxlag === true) {
		onError(MAXLAG);
		return;
	}
	if (mw.user.options.get('userjs-schnark-bandersnatch-ua', true)) {
		headers['Api-User-Agent'] =
			mw.user.options.get('userjs-schnark-bandersnatch-ua', 'bandersnatch.js/v' + global.version);
	}
	$.ajax({
		url: mw.util.wikiScript('api'),
		data: makeParam(data, maxlag),
		type: 'post',
		headers: headers,
		dataType: 'json'
	}).then(function (json) {
		if (json && json.edit && json.edit.result === 'Success') {
			onSuccess();
		} else if (json && json.error) {
			switch (json.error.code) {
			case 'maxlag':
				if (maxlagF(n + 1) === true) {
					onError(MAXLAG);
				} else {
					window.setTimeout(function () {
						reallyEdit(data, maxlagF, onSuccess, onError, n + 1);
					}, (maxlagF(n, true) || 5) * 1000);
				}
				break;
			case 'editconflict':
			case 'pagedeleted':
				onError(EDITCONFLICT);
				break;
			case 'spamdetected':
				onError(SPAM);
				break;
			default:
				onError(UNKNOWN);
			}
		} else {
			onError(SERVER);
		}
	}, function () {
		onError(SERVER);
	});
}

function getData (title, onSuccess, onError) {
	$.getJSON(mw.util.wikiScript('api'), {
		action: 'query',
		prop: 'info|revisions',
		meta: 'tokens',
		titles: title,
		rvprop: 'timestamp|content',
		rvslots: 'main',
		rvlimit: 1,
		curtimestamp: true,
		assert: 'user',
		format: 'json',
		formatversion: 2
	}).then(function (json) {
		var text = '', basetimestamp, page;
		if (json && json.error && json.error.code === 'assertuserfailed') {
			onError(LOSTLOGIN);
			return;
		}
		if (!json || !json.query || !json.query.pages || !json.query.tokens || !json.curtimestamp) {
			onError(SERVER);
			return;
		}
		page = json.query.pages[0];
		if (!page) {
			onError(SERVER);
			return;
		}
		if (page.missing) {
			basetimestamp = json.curtimestamp;
		} else if (page.revisions && page.revisions[0]) {
			text = page.revisions[0].slots.main.content;
			basetimestamp = page.revisions[0].timestamp;
		} else {
			onError(SERVER);
			return;
		}
		onSuccess({
			text: text,
			basetimestamp: basetimestamp,
			starttimestamp: json.curtimestamp,
			token: json.query.tokens.csrftoken
		});
	}, function () {
		onError(SERVER);
	});
}

function editOne (title, timestamp, defaultParam, editF, verifyF, abortF, maxlagF, onSuccess, onError) {
	if (abortF()) {
		onError(ABORT);
		return;
	}
	getData(title, function (data) {
		if (abortF()) {
			onError(ABORT);
			return;
		}
		editF(title, data.text, function (ret) {
			var edit = $.extend({text: false}, defaultParam, ret || {text: false});
			if (abortF()) {
				onError(ABORT);
				return;
			}
			if (edit.error) {
				onError(JSERROR);
				return;
			}
			if (edit.text === false || edit.text === data.text) {
				onError(NOEDIT);
				return;
			}
			verifyF(title, data.text, edit, function () {
				if (abortF()) {
					onError(ABORT);
					return;
				}
				var wait = Math.round(timestamp - mw.now());
				if (wait < 0) {
					wait = 0;
				}
				window.setTimeout(function () {
					reallyEdit({
						title: title,
						text: edit.text,
						summary: edit.summary,
						minor: edit.minor,
						bot: edit.bot,
						basetimestamp: data.basetimestamp,
						starttimestamp: data.starttimestamp,
						token: data.token
					}, maxlagF, function () {
						onSuccess({
							oldText: data.text,
							newText: edit.text,
							summary: edit.summary,
							minor: edit.minor,
							bot: edit.bot
						});
					}, onError);
				}, wait);
			}, function () {
				onError(NOTVERIFIED);
			});
		});
	}, onError);
}

function editMany (titles, wait, defaultParam, editF, verifyF, abortF, maxlagF, onSuccess, onError, onFinish) {
	var i = -1, title = '', timestamp = 0;
	function successHandler (data) {
		timestamp = mw.now();
		onSuccess(title, data);
		editNext();
	}
	function errorHandler (code) {
		onError(title, code);
		if (code === ABORT || code === LOSTLOGIN) {
			onFinish();
		} else {
			editNext();
		}
	}
	function editNext () {
		i++;
		if (i >= titles.length) {
			onFinish();
			return;
		}
		title = titles[i];
		editOne(title, timestamp + wait, defaultParam, editF, verifyF, abortF, maxlagF, successHandler, errorHandler);
	}
	editNext();
}

global.editMany = editMany;

})();

(function () {
//Verbindung Backend-Frontend

function getErrorDescription (code) {
	switch (code) {
	case global.error.ABORT: code = 'abort'; break;
	case global.error.EDITCONFLICT: code = 'editconflict'; break;
	case global.error.JSERROR: code = 'jserror'; break;
	case global.error.LOSTLOGIN: code = 'lostlogin'; break;
	case global.error.MAXLAG: code = 'maxlag'; break;
	case global.error.NOEDIT: code = 'noedit'; break;
	case global.error.NOTVERIFIED: code = 'notverified'; break;
	case global.error.SERVER: code = 'server'; break;
	case global.error.SPAM: code = 'spam'; break;
	case global.error.UNKNOWN: code = 'unknown'; break;
	}
	return mw.msg('schnark-bandersnatch-error-' + code);
}

function getEditFlags (minor, bot) {
	return (minor ? '<b>' + mw.msg('minoreditletter') + '</b>' : '&#160;') + ' ' +
		(bot ? '<b>' + mw.msg('boteditletter') + '</b>' : '&#160;');
}

function getEditHtml (title, edit, inclDiffLink) {
	var diff = global.diff(edit.oldText, edit.newText),
		diffLink = ' (' + mw.html.element('a', {href: mw.util.getUrl(title, {diff: 'curr'})}, mw.msg('diff')) + ')';
	return mw.html.element('a', {href: mw.util.getUrl(title)}, title) +
		(inclDiffLink ? diffLink : '') +
		mw.html.element('br') + getEditFlags(edit.minor, edit.bot) + ' ' +
		mw.html.element('code', {}, edit.summary) +
		mw.html.element('div', {
			'data-collapsetext': mw.msg('schnark-bandersnatch-diff-hide'),
			'data-expandtext': mw.msg('schnark-bandersnatch-diff-show')
		}, new mw.html.Raw(diff));
}

function showEdit (title, edit, type) {
	var $li = $('<li>').html(getEditHtml(title, edit, type !== 'simulate'));
	$li.find('div').addClass('mw-collapsed').makeCollapsible();
	global.addLog($li, type || 'success');
}

function showError (title, code) {
	var $li = $('<li>').addClass('autoedit-error').html(
		mw.html.element('a', {href: mw.util.getUrl(title)}, title) +
		'&#160;– ' + getErrorDescription(code)
	);
	global.addLog($li, 'error');
}

function verifySimulate (title, oldText, edit, doit, dontdoit) {
	showEdit(title, {
		oldText: oldText,
		newText: edit.text,
		summary: edit.summary,
		minor: edit.minor,
		bot: edit.bot
	}, 'simulate');
	dontdoit();
}

function errorSimulate (title, code) {
	if (code !== global.error.NOTVERIFIED) {
		showError(title, code);
	}
}

function verifyCheck (title, oldText, edit, doit, dontdoit) {
	var html = getEditHtml(title, {
		oldText: oldText,
		newText: edit.text,
		summary: edit.summary,
		minor: edit.minor,
		bot: edit.bot
	}, 'check');
	global.checkEdit(html, function (verified) {
		if (verified) {
			doit();
		} else {
			dontdoit();
		}
	});
}

function verifyAuto (title, oldText, edit, doit /*, dontdoit*/) {
	doit();
}

function getHandlersFor (type) {
	switch (type) {
	case 'simulate':
		return [verifySimulate, $.noop, errorSimulate];
	case 'batch':
	case 'auto':
		return [verifyAuto, showEdit, showError];
	case 'check':
		return [verifyCheck, showEdit, showError];
	}
}

function makeFunction (code) {
	/*jshint evil: true*/
	var f;
	try {
		f = new Function ('title', 'oldText', code);
		return function (title, oldText) {
			try {
				return f(title, oldText);
			} catch (e) {
				return {error: e};
			}
		};
	} catch (e) {
		return function () {
			return {error: e};
		};
	}
}

function getEditFunction (code, type, defaultParam) {
	var f = makeFunction(code);
	if (type === 'batch') {
		return function (title, oldText, callback) {
			var edit = $.extend({text: false}, defaultParam, f(title, oldText) || {text: false});
			if (edit.text === false) {
				callback(edit);
			} else {
				global.batchEdit(title, edit, oldText, callback);
			}
		};
	} else {
		return function (title, oldText, callback) {
			callback(f(title, oldText));
		};
	}
}

function maxlagIgnore () {
	return false;
}

function maxlag5x5 (n) {
	if (n >= 5) {
		return true;
	}
	return 5;
}

function getMaxlagFunction (type) {
	switch (type) {
	case 'ignore': return maxlagIgnore;
	case 'default': return maxlag5x5;
	}
}

function editFromForm (
	titleList, defaultSummary, defaultMinor, defaultBot, waitSeconds,
	maxlagType, editCode, editType, abortButton, onFinish
) {
	var abort = false,
		titles = titleList.replace(/\r\n/g, '\n').replace(/^\n+|\n+$/g, '').split('\n'),
		wait = Number(waitSeconds) * 1000,
		defaultParam = {
			summary: defaultSummary,
			minor: defaultMinor,
			bot: defaultBot
		},
		editF = getEditFunction(editCode, editType, defaultParam),
		funcs = getHandlersFor(editType),
		verifyF = funcs[0],
		//jscs:disable requireFunctionDeclarations
		abortF = function () {
			return abort;
		},
		//jscs:enable requireFunctionDeclarations
		maxlagF = getMaxlagFunction(maxlagType),
		onSuccess = funcs[1],
		onError = funcs[2];
	abortButton.on('click', function () {
		abort = true;
	});
	global.editMany(titles, wait, defaultParam, editF, verifyF, abortF, maxlagF, onSuccess, onError, onFinish);
}

global.editFromForm = editFromForm;
global.makeFunction = makeFunction;

})();

(function () {
//Check mode

var $edit, checkPanel, currentCallback;

function verify () {
	checkPanel.toggle(false);
	if (currentCallback) {
		currentCallback(true);
	}
}

function cancel () {
	checkPanel.toggle(false);
	if (currentCallback) {
		currentCallback(false);
	}
}

function checkEdit (html, callback) {
	$edit.html(html);
	currentCallback = callback;
	checkPanel.toggle(true);
}

function makeCheckInterface ($container, abortButton) {
	var noButton, okButton, buttonBar, buttonFieldset;

	$edit = $('<div>');

	noButton = new OO.ui.ButtonWidget({
		id: 'check-no', //ID here and below only to make testing easy
		label: mw.msg('schnark-bandersnatch-check-no'),
		icon: 'close',
		flags: ['primary', 'destructive']
	});
	okButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-check-ok'),
		icon: 'check',
		flags: ['primary', 'progressive']
	});

	buttonBar = new OO.ui.HorizontalLayout({
		items: [noButton, okButton]
	});
	buttonFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(new OO.ui.Widget({content: [buttonBar]}))
		]
	});

	noButton.on('click', cancel);
	okButton.on('click', verify);
	abortButton.on('click', cancel);

	checkPanel = new OO.ui.PanelLayout({
		expanded: false,
		framed: true,
		padded: true,
		$content: $('<div>').append(
				$edit,
				buttonFieldset.$element
			)
	});
	checkPanel.toggle(false);
	$container.append(checkPanel.$element);
}

global.checkEdit = checkEdit;
global.makeCheckInterface = makeCheckInterface;
})();

(function () {
//Batch mode

var widgets = {}, elements = {},
	currentTitle = '', currentOldText = '', currentCallback;

function setDisabled (disabled) {
	elements.$textbox.prop('disabled', disabled);
	widgets.summary.setDisabled(disabled);
	widgets.minor.setDisabled(disabled);
	widgets.bot.setDisabled(disabled);
	widgets.previewButton.setDisabled(disabled);
	widgets.diffButton.setDisabled(disabled);
	widgets.revertButton.setDisabled(disabled);
	widgets.skipButton.setDisabled(disabled);
	widgets.saveButton.setDisabled(disabled);
}

function createPreview (text, title, $area) {
	$.post(mw.util.wikiScript('api'), {
		action: 'parse',
		title: title,
		prop: 'text|categorieshtml|langlinks|modules|jsconfigvars|displaytitle|indicators',
		useskin: mw.config.get('skin'),
		uselang: mw.config.get('wgUserLanguage'),
		pst: true,
		disableeditsection: true,
		preview: true,
		text: text,
		format: 'json',
		formatversion: 2
	}).then(function (json) {
		if (!json || !json.parse || !json.parse.text) {
			$area.html(mw.msg('schnark-bandersnatch-error-preview'));
			return;
		}
		var html = json.parse.text, indicators = [];
		if (json.parse.categorieshtml) {
			html += json.parse.categorieshtml;
		}
		if (json.parse.langlinks && json.parse.langlinks.length) {
			html += '<hr>' + json.parse.langlinks.map(function (ll) {
				return mw.html.element('a', {
					href: ll.url,
					title: ll.langname + ' – ' + ll.title
				}, ll.autonym);
			}).join(', ');
		}

		if (json.parse.displaytitle) {
			elements.$title.html(json.parse.displaytitle);
		}

		$.each(json.parse.indicators, function (name, indicator) {
			indicators.push(
				$('<div>')
					.addClass('mw-indicator')
					.attr('id', mw.util.escapeIdForAttribute('mw-indicator-' + name))
					.html(indicator)
					.get(0),
				document.createTextNode('\n')
			);
		});
		$('.mw-indicators').empty().append(indicators);

		if (json.parse.jsconfigvars) {
			mw.config.set(json.parse.jsconfigvars);
		}
		if (json.parse.modules) {
			mw.loader.load(json.parse.modules.concat(
				json.parse.modulestyles
			));
		}

		$area.html(html);
		mw.hook('wikipage.content').fire($area);
	}, function () {
		$area.html(mw.msg('schnark-bandersnatch-error-preview'));
	});
}

function showPreview () {
	elements.$area.html(mw.msg('schnark-bandersnatch-preview-wait'));
	createPreview(elements.$textbox.val(), currentTitle, elements.$area);
}

function showDiff () {
	elements.$area.html(global.diff(currentOldText, elements.$textbox.val()));
	$('.mw-indicators').empty();
}

function revertText () {
	elements.$textbox.val(currentOldText);
}

function skipEdit () {
	setDisabled(true);
	if (currentCallback) {
		currentCallback({text: false});
	}
}

function saveEdit () {
	setDisabled(true);
	if (currentCallback) {
		currentCallback({
			text: elements.$textbox.val(),
			summary: widgets.summary.getValue(),
			minor: widgets.minor.isSelected(),
			bot: widgets.bot.isSelected()
		});
	}
}

function batchEdit (title, edit, oldText, callback) {
	currentTitle = title;
	currentOldText = oldText;
	currentCallback = callback;
	document.title = document.title.replace(/ –.*/, '') + ' – ' + title;
	$('.mw-indicators').empty();
	elements.$title.text(title);
	elements.$information.html(
		mw.html.element('a', {href: mw.util.getUrl(title)}, mw.msg('article')) +
		' (' + mw.html.element('a', {href: mw.util.getUrl(title, {action: 'history'})}, mw.msg('pagehist')) + ')'
	);
	elements.$area.html(global.diff(oldText, edit.text));
	elements.$textbox.val(edit.text);
	widgets.summary.setValue(edit.summary);
	widgets.minor.setSelected(edit.minor);
	widgets.bot.setSelected(edit.bot);
	setDisabled(false);
}

function makeBatchInterface ($container, abortButton, isBot) {
	var summaryInput, minorInput, botInput,
		previewButton, diffButton, revertButton, skipButton, saveButton,
		buttonBar, controlFieldset;

	summaryInput = new OO.ui.TextInputWidget({
		id: 'batch-summary'
	});

	minorInput = new OO.ui.CheckboxInputWidget();
	botInput = new OO.ui.CheckboxInputWidget();

	previewButton = new OO.ui.ButtonWidget({
		id: 'batch-preview',
		label: mw.msg('schnark-bandersnatch-batch-preview'),
		icon: 'article'
	});
	diffButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-batch-diff'),
		icon: 'articles'
	});
	revertButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-batch-revert'),
		icon: 'undo',
		flags: 'destructive'
	});
	skipButton = new OO.ui.ButtonWidget({
		id: 'batch-skip',
		label: mw.msg('schnark-bandersnatch-batch-skip'),
		icon: 'close',
		flags: 'destructive'
	});
	saveButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-batch-save'),
		icon: 'check',
		flags: ['progressive', 'primary']
	});

	previewButton.on('click', showPreview);
	diffButton.on('click', showDiff);
	revertButton.on('click', revertText);
	skipButton.on('click', skipEdit);
	saveButton.on('click', saveEdit);
	abortButton.on('click', skipEdit);

	buttonBar = new OO.ui.HorizontalLayout({
		items: [previewButton, diffButton, revertButton, skipButton, saveButton]
	});

	controlFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(summaryInput, {
				label: mw.msg('schnark-bandersnatch-batch-summary'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(minorInput, {
				label: mw.msg('schnark-bandersnatch-batch-minor'),
				align: 'inline'
			}),
			new OO.ui.FieldLayout(botInput, {
				label: mw.msg('schnark-bandersnatch-batch-bot'),
				align: 'inline'
			}).toggle(isBot),
			new OO.ui.FieldLayout(
				new OO.ui.Widget({content: [buttonBar]})
			)
		]
	});

	$container.append(
		$('<h1>').attr('id', 'batch-title'),
		$('<span>').attr('id', 'batch-information'),
		$('<div>').attr({id: 'batch-area'}).html(
			//FIXME: abhängig von Schreibrichtung des Wikis
			'<div class="mw-content-ltr"></div>'
		),
		$('<textarea>').attr({id: 'batch-textbox', rows: 25, cols: 80}),
		controlFieldset.$element
	);

	elements.$title = $container.find('#batch-title');
	elements.$information = $container.find('#batch-information');
	elements.$area = $container.find('#batch-area div');
	elements.$textbox = $container.find('#batch-textbox');
	widgets.summary = summaryInput;
	widgets.minor = minorInput;
	widgets.bot = botInput;
	widgets.previewButton = previewButton;
	widgets.diffButton = diffButton;
	widgets.revertButton = revertButton;
	widgets.skipButton = skipButton;
	widgets.saveButton = saveButton;
	setDisabled(true);
}

global.batchEdit = batchEdit;
global.makeBatchInterface = makeBatchInterface;

})();

(function () {
//Log
var map = {
		success: 'check',
		simulate: 'check',
		error: 'close'
	}, $log;

function addLog ($li, type) {
	var icon = new OO.ui.IconWidget({icon: map[type]});
	$li.addClass('autoedit-' + type).prepend(icon.$element);
	$log.append($li);
}

function makeLog ($container) {
	$log = $('<ul>');
	$container.append($log);
}

global.addLog = addLog;
global.makeLog = makeLog;

})();

(function () {
//Frontend

var widgets = {};

function addCSS () {
	mw.util.addCSS(
		//Batch mode
		'#batch-area {' +
			'border: 1px dotted #36c;' +
		'}' +
		//Log
		'#autoedit-log-container ul {' +
			'list-style: none;' +
		'}' +
		//General
		'#autoedit-abort-container {' +
			'float: right;' +
		'}' +
		global.diffCSS()
	);
}

function isBot () {
	return mw.config.get('wgUserGroups').indexOf('bot') > -1;
}

function readXML () {
	function fileOpen (callback) {
		var $input = $('<input>').attr('type', 'file').hide().appendTo($('body'));
		$input.on('change', function () {
			var file = $input[0].files[0], reader = new FileReader();
			if (file) {
				reader.onload = function (e) {
					callback(e.target.result);
				};
				reader.readAsText(file, 'utf-8');
			}
			$input.remove();
		})[0].click();
	}
	function parseXML (content) {
		var $mediawiki = $($.parseXML(content)), titles = [];
		libs.xmldata = libs.xmldata || {};
		$mediawiki.find('page').each(function () {
			var $page = $(this), title, text;
			title = $page.find('title').text();
			text = $page.find('text').text();
			if (title) {
				libs.xmldata[title] = text;
				titles.push(title);
			}
		});
		widgets.pages.setValue(
			(widgets.pages.getValue() + '\n' + titles.join('\n')).trim()
		);
		widgets.code.setValue('return {text: mw.libs.xmldata[title]};');
	}

	fileOpen(parseXML);
}

function createTitlesPanel (includeSearch) {
	var searchInput, searchButton, searchFieldset,
		xmlButton, xmlFieldset,
		pagesInput, pagesFieldset,
		nextButton, buttonFieldset;

	if (includeSearch) {
		searchInput = new OO.ui.MultilineTextInputWidget({
			id: 'autoedit-search'
		});
		searchButton = new OO.ui.ButtonWidget({
			id: 'autoedit-search-submit',
			label: mw.msg('schnark-bandersnatch-step-1-search-submit'),
			icon: 'search'
		});
		searchFieldset = new OO.ui.FieldsetLayout({
			items: [
				new OO.ui.FieldLayout(searchInput, {
					label: mw.msg('schnark-bandersnatch-step-1-search-query'),
					align: 'top'
				}),
				new OO.ui.FieldLayout(searchButton)
			]
		});

		searchButton.on('click', function () {
			global.search(searchInput.getValue()).then(function (list) {
				pagesInput.setValue(
					(list.join('\n') + '\n' + pagesInput.getValue()).trim()
				);
			}, function (error) {
				pagesInput.setValue(
					mw.msg('schnark-bandersnatch-step-1-search-error', error) + '\n\n' +
					pagesInput.getValue()
				);
			});
		});
	}

	xmlButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-step-1-xml-read'),
		icon: 'markup'
	});
	xmlFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(xmlButton)
		]
	});
	xmlButton.on('click', readXML);

	pagesInput = new OO.ui.MultilineTextInputWidget({
		id: 'autoedit-titleList',
		rows: 10,
		autosize: true,
		maxrows: 30
	});
	pagesFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(pagesInput, {
				label: mw.msg('schnark-bandersnatch-step-1-pages'),
				align: 'top'
			})
		]
	});

	nextButton = new OO.ui.ButtonWidget({
		id: 'autoedit-page1-cont',
		label: mw.msg('schnark-bandersnatch-step-continue'),
		flags: ['progressive', 'primary']
	});
	buttonFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(nextButton)
		]
	});

	widgets.pages = pagesInput;
	widgets.next1 = nextButton;

	return new OO.ui.PanelLayout({
		expanded: false,
		$content: $('<div>').append(
				$('<h2>').text(mw.msg('schnark-bandersnatch-step-1-head')),
				$('<p>').text(
					includeSearch ?
						mw.msg('schnark-bandersnatch-step-1-search-complex') :
						mw.msg('schnark-bandersnatch-step-1-search-simple')
				),
				includeSearch ? $('<div>').addClass('mw-collapsible mw-collapsed').html(global.searchHelp()) : '',
				includeSearch ? searchFieldset.$element : '',
				$('<p>').text(mw.msg('schnark-bandersnatch-step-1-xml')),
				xmlFieldset.$element,
				pagesFieldset.$element,
				buttonFieldset.$element
			)
	});
}

function createCodePanel () {
	var codeInput, codeFieldset,
		tools, $tools,
		testTitle, testText, testButton, testFieldset,
		prevButton, nextButton, buttonBar, buttonFieldset;

	function makeLoadFunction (load, button) {
		return function () {
			button.$element.remove();
			load();
		};
	}

	codeInput = new OO.ui.MultilineTextInputWidget({
		id: 'autoedit-editCode',
		value: 'return {text: oldText};',
		rows: 10,
		autosize: true,
		maxrows: 30,
		classes: ['mw-editfont-monospace']
	});
	codeInput.$element.prepend('<code>function (title, oldText) {</code>');
	codeInput.$element.append('<code>}</code>');
	codeFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(codeInput, {
				label: mw.msg('schnark-bandersnatch-step-2-code'),
				align: 'top'
			})
		]
	});

	tools = [{
		title: mw.msg('schnark-bandersnatch-tool-replace'),
		desc: mw.msg('schnark-bandersnatch-tool-replace-code'),
		load: false
	}, {
		title: mw.msg('schnark-bandersnatch-tool-template'),
		desc: mw.msg('schnark-bandersnatch-tool-template-code'),
		load: function () {
			mw.loader.load('https://de.wikipedia.org/w/index.php?title=' +
				'Benutzer:Schnark/js/Template.js&action=raw&ctype=text/javascript&maxage=604800&smaxage=86400');
		}
	}];

	$tools = $('<table>');
	$tools.append('<tr><th>' + mw.msg('schnark-bandersnatch-step-2-tools-tool') + '</th><th>' +
		mw.msg('schnark-bandersnatch-step-2-tools-use') + '</th><th>' +
		mw.msg('schnark-bandersnatch-step-2-tools-load') + '</td></tr>');
	tools.forEach(function (tool) {
		var $row = $('<tr>'), button;
		if (tool.desc.indexOf('\n') === -1) {
			tool.desc = '<code>' + tool.desc + '</code>';
		} else {
			tool.desc = '<pre>' + tool.desc + '</pre>';
		}
		$row.append('<td>' + tool.title + '</td><td>' + tool.desc + '</td>');
		if (tool.load) {
			button = new OO.ui.ButtonWidget({
				label: mw.msg('schnark-bandersnatch-tool-load'),
				icon: 'puzzle'
			});
			$row.append($('<td>').append(button.$element));
			button.on('click', makeLoadFunction(tool.load, button));
		} else {
			$row.append($('<td>').text(mw.msg('schnark-bandersnatch-tool-noload')));
		}
		$tools.append($row);
	});
	$tools = $('<div>').append(
		$('<h3>').text(mw.msg('schnark-bandersnatch-step-2-tools')),
		$tools
	);

	testTitle = new OO.ui.TextInputWidget({
		value: mw.msg('schnark-bandersnatch-step-2-test-title-default')
	});
	testText = new OO.ui.MultilineTextInputWidget({
		id: 'autoedit-test-text',
		rows: 10,
		autosize: true,
		maxrows: 30
	});
	testButton = new OO.ui.ButtonWidget({
		id: 'autoedit-test',
		label: mw.msg('schnark-bandersnatch-step-2-test-run'),
		icon: 'settings'
	});
	testFieldset = new OO.ui.FieldsetLayout({ //TODO collapsible?
		label: mw.msg('schnark-bandersnatch-step-2-test'),
		items: [
			new OO.ui.FieldLayout(testTitle, {
				label: mw.msg('schnark-bandersnatch-step-2-test-title'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(testText, {
				label: mw.msg('schnark-bandersnatch-step-2-test-text'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(testButton)
		]
	});
	testButton.on('click', function () {
		var f = global.makeFunction(codeInput.getValue()),
			ret = f(testTitle.getValue(), testText.getValue());
		if (ret) {
			testText.setValue(ret.text || ret.error);
		}
	});

	prevButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-step-back')
	});
	nextButton = new OO.ui.ButtonWidget({
		id: 'autoedit-page2-cont',
		label: mw.msg('schnark-bandersnatch-step-continue'),
		flags: ['progressive', 'primary']
	});
	buttonBar = new OO.ui.HorizontalLayout({
		items: [prevButton, nextButton]
	});
	buttonFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(new OO.ui.Widget({content: [buttonBar]}))
		]
	});

	widgets.code = codeInput;
	widgets.prev2 = prevButton;
	widgets.next2 = nextButton;

	return new OO.ui.PanelLayout({
		expanded: false,
		$content: $('<div>').append(
				$('<h2>').text(mw.msg('schnark-bandersnatch-step-2-head')),
				$('<p>').html(mw.msg('schnark-bandersnatch-step-2-text')),
				codeFieldset.$element,
				$tools,
				testFieldset.$element,
				buttonFieldset.$element
			)
	});
}

function createParameterPanel (isBot) {
	var summaryInput, minorInput, botInput, waitInput, maxlagInput, modeInput, parameterFieldset,
		prevButton, nextButton, buttonBar, buttonFieldset;

	summaryInput = new OO.ui.TextInputWidget({
		id: 'autoedit-defaultSummary'
	});

	minorInput = new OO.ui.CheckboxInputWidget({
		selected: true
	});
	botInput = new OO.ui.CheckboxInputWidget({
		selected: isBot
	});

	waitInput = new OO.ui.NumberInputWidget({
		id: 'autoedit-waitSeconds',
		min: 0,
		value: 5
	});

	maxlagInput = new OO.ui.DropdownInputWidget({
		options: [
			{
				data: 'ignore',
				label: mw.msg('schnark-bandersnatch-step-3-maxlag-ignore')
			},
			{
				data: 'default',
				label: mw.msg('schnark-bandersnatch-step-3-maxlag-5x5')
			}
		]
	});

	modeInput = new OO.ui.DropdownInputWidget({
		id: 'autoedit-editType',
		options: [
			{
				data: 'simulate',
				label: mw.msg('schnark-bandersnatch-step-3-mode-simulate')
			},
			{
				data: 'batch',
				label: mw.msg('schnark-bandersnatch-step-3-mode-batch')
			},
			{
				data: 'check',
				label: mw.msg('schnark-bandersnatch-step-3-mode-check')
			},
			{
				data: 'auto',
				label: mw.msg('schnark-bandersnatch-step-3-mode-auto')
			}
		]
	});

	parameterFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(summaryInput, {
				label: mw.msg('schnark-bandersnatch-step-3-summary'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(minorInput, {
				label: mw.msg('schnark-bandersnatch-step-3-minor'),
				align: 'inline'
			}),
			new OO.ui.FieldLayout(botInput, {
				label: mw.msg('schnark-bandersnatch-step-3-bot'),
				align: 'inline'
			}).toggle(isBot),
			new OO.ui.FieldLayout(waitInput, {
				label: mw.msg('schnark-bandersnatch-step-3-wait'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(maxlagInput, {
				label: mw.msg('schnark-bandersnatch-step-3-maxlag'),
				align: 'top'
			}),
			new OO.ui.FieldLayout(modeInput, {
				label: mw.msg('schnark-bandersnatch-step-3-mode'),
				align: 'top'
			})
		]
	});

	prevButton = new OO.ui.ButtonWidget({
		label: mw.msg('schnark-bandersnatch-step-back')
	});
	nextButton = new OO.ui.ButtonWidget({
		id: 'autoedit-run',
		label: mw.msg('schnark-bandersnatch-step-go'),
		icon: 'check',
		flags: ['progressive', 'primary']
	});
	buttonBar = new OO.ui.HorizontalLayout({
		items: [prevButton, nextButton]
	});
	buttonFieldset = new OO.ui.FieldsetLayout({
		items: [
			new OO.ui.FieldLayout(new OO.ui.Widget({content: [buttonBar]}))
		]
	});

	widgets.summary = summaryInput;
	widgets.minor = minorInput;
	widgets.bot = botInput;
	widgets.wait = waitInput;
	widgets.maxlag = maxlagInput;
	widgets.mode = modeInput;
	widgets.prev3 = prevButton;
	widgets.run = nextButton;

	return new OO.ui.PanelLayout({
		expanded: false,
		$content: $('<div>').append(
				$('<h2>').text(mw.msg('schnark-bandersnatch-step-3-head')),
				$('<p>').text(mw.msg('schnark-bandersnatch-step-3-text')),
				parameterFieldset.$element,
				buttonFieldset.$element
			)
	});
}

function makeAbortButton ($container) {
	var button = new OO.ui.ButtonWidget({
		id: 'autoedit-abort',
		label: mw.msg('schnark-bandersnatch-abort'),
		icon: 'close',
		flags: ['primary', 'destructive']
	});
	$container.append(button.$element);
	widgets.abort = button;
}

function makeInterface ($container, type) {
	var $div;
	addCSS();
	$container.empty();
	if (type === 'batch') {
		$div = $('<div>').attr('id', 'autoedit-batch-container');
		global.makeBatchInterface($div, widgets.abort, isBot());
		$container.append($div);
	}
	$div = $('<div>').attr('id', 'autoedit-log-container');
	global.makeLog($div);
	$container.append($div);
	if (type === 'check') {
		$div = $('<div>').attr('id', 'autoedit-check-container');
		global.makeCheckInterface($div, widgets.abort);
		$container.append($div);
	}
}

function initInterface ($container) {
	var panel1 = createTitlesPanel(!!global.search),
		panel2 = createCodePanel(),
		panel3 = createParameterPanel(isBot());

	panel2.toggle(false);
	panel3.toggle(false);

	widgets.next1.on('click', function () {
		panel1.toggle(false);
		panel2.toggle(true);
		window.scroll(0, 0);
	});
	widgets.next2.on('click', function () {
		panel2.toggle(false);
		panel3.toggle(true);
		window.scroll(0, 0);
	});
	widgets.prev2.on('click', function () {
		panel2.toggle(false);
		panel1.toggle(true);
		window.scroll(0, 0);
	});
	widgets.prev3.on('click', function () {
		panel3.toggle(false);
		panel2.toggle(true);
		window.scroll(0, 0);
	});

	widgets.run.on('click', function () {
		run($container);
		window.scroll(0, 0);
	});

	$container.empty().append(
		panel1.$element,
		panel2.$element,
		panel3.$element
	);

	$container.find('.mw-collapsible').makeCollapsible();
}

function getData () {
	return {
		titleList: widgets.pages.getValue(),
		defaultSummary: widgets.summary.getValue(),
		defaultMinor: widgets.minor.isSelected(),
		defaultBot: widgets.bot.isSelected(),
		waitSeconds: widgets.wait.getValue(),
		maxlagType: widgets.maxlag.getValue(),
		editCode: widgets.code.getValue(),
		editType: widgets.mode.getValue()
	};
}

function run ($container) {
	var data = getData(), $span = $('<span>').attr({id: 'autoedit-abort-container'});
	makeAbortButton($span);
	$('#firstHeading').before($span);
	makeInterface($container, data.editType);
	global.editFromForm(data.titleList, data.defaultSummary, data.defaultMinor, data.defaultBot,
		data.waitSeconds, data.maxlagType, data.editCode, data.editType, widgets.abort, function () {
			$('#autoedit-batch-container, #autoedit-check-container').remove();
			widgets.abort.$element.remove();
		}
	);
}

function initL10N (l10n, keep) {
	var i, chain = mw.language.getFallbackLanguageChain();
	keep = $.grep(mw.messages.get(keep), function (val) {
		return val !== null;
	});
	for (i = chain.length - 1; i >= 0; i--) {
		if (chain[i] in l10n) {
			mw.messages.set(l10n[chain[i]]);
		}
	}
	mw.messages.set(keep);
}

function init () {
	initL10N(l10n, ['diff', 'minoreditletter', 'boteditletter', 'article', 'pagehist']);

	if (libs.schnarkDiff) {
		global.diffCSS = libs.schnarkDiff.getCSS;
		global.diff = libs.schnarkDiff.htmlDiff;
	} else {
		global.diffCSS = function () {
			return '';
		};
		global.diff = function () {
			return mw.msg('schnark-bandersnatch-error-diff');
		};
	}
	if (libs['search++']) {
		global.searchHelp = libs['search++'].getHelp;
		global.search = libs['search++'].queryByString;
	}

	document.title = mw.msg('schnark-bandersnatch-title');
	$('#firstHeading').text(mw.msg('schnark-bandersnatch-version', global.version));
	var $container = $('#mw-content-text');
	initInterface($container);
}

global.init = init;

})();

if (libs.qunit) {
	libs.qunit.bandersnatch = global;
}

if (mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgTitle') === 'Bandersnatch') {
	$.when(mw.loader.using([
		'mediawiki.util', 'mediawiki.language', 'user.options', 'jquery.makeCollapsible', 'oojs-ui-core',
		'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-content',
		'oojs-ui.styles.icons-editing-core', 'oojs-ui.styles.icons-editing-advanced'
	]), $.ready).then(global.init);
}

})(jQuery, mediaWiki, mediaWiki.libs);
//</nowiki>