Benutzer:Schnark/js/wikiblame.js

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

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

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/wikiblame]] <nowiki>
/*global mediaWiki, OO*/
(function ($, mw, libs) {
"use strict";

var l10n = {
//jscs:disable maximumLineLength
	en: {
		'wikiblame': 'Wikiblame',
		'wikiblame-tooltip': 'Find the revision in which a certain text was added',
		//Fehler
		'wikiblame-invalid-regexp': 'The regular expression contains an error!',
		'wikiblame-api-error': 'An error occurred while querying the API!',
		'wikiblame-wrong-revisions': 'The selected revisions don\'t match!',
		'wikiblame-in-first-revision': 'The text is already present in the first selected revision!',
		'wikiblame-not-in-first-revision': 'The text is already missing in the first selected revision!',
		'wikiblame-in-last-revision': 'The text is already present in the last selected revision!',
		'wikiblame-not-in-last-revision': 'The text is already missing in the last selected revision!',
		'wikiblame-not-found': 'The text wasn\'t found!',
		//Fortschritt
		'wikiblame-get-history': 'Loading history …',
		'wikiblame-test-revision': 'Examining revision $1 …',
		'wikiblame-load-diff': 'Loading diff …',
		//Dialog
		'wikiblame-dialog-search-label': 'Search for:',
		'wikiblame-dialog-search-help': 'The text or regular expression that should be searched for',
		'wikiblame-dialog-regexp-label': 'regular expression',
		'wikiblame-dialog-regexp-help': 'Interprete search text as regular expression',
		'wikiblame-dialog-source-label': 'Search source code',
		'wikiblame-dialog-source-help': 'Search the source code instead of the parsed HTML',
		'wikiblame-dialog-invert-label': 'Find deletion',
		'wikiblame-dialog-invert-help': 'Search for the deletion of the search text instead of its insertion',
		'wikiblame-dialog-later-label': 'later revisions',
		'wikiblame-dialog-later-help': 'Search the revisions after the currently shown, instead of all before this one',
		'wikiblame-dialog-linear-label': 'linear search',
		'wikiblame-dialog-linear-help': 'Slow search, that will always find the next matching diff',
		'wikiblame-dialog-down-label': 'start with newer revisions',
		'wikiblame-dialog-down-help': 'Start with newer revisions in the linear search',
		'wikiblame-dialog-go': 'Search',
		'wikiblame-dialog-done': 'Close'
	},
	de: {
		'wikiblame-tooltip': 'Findet die Version, in der ein bestimmter Text eingefügt wurde',
		'wikiblame-invalid-regexp': 'Der reguläre Ausdruck enthält einen Fehler!',
		'wikiblame-api-error': 'Bei der API-Anfrage trat ein Fehler auf!',
		'wikiblame-wrong-revisions': 'Die gewählten Versionen passen nicht zusammen!',
		'wikiblame-in-first-revision': 'Der Text befindet sich schon in der ersten untersuchten Version!',
		'wikiblame-not-in-first-revision': 'Der Text fehlt schon in der ersten untersuchten Version!',
		'wikiblame-in-last-revision': 'Der Text befindet sich schon in der letzten untersuchten Version!',
		'wikiblame-not-in-last-revision': 'Der Text fehlt schon in der letzten untersuchten Version!',
		'wikiblame-not-found': 'Der Text wurde nicht gefunden!',
		'wikiblame-get-history': 'Lade Versionsgeschichte …',
		'wikiblame-test-revision': 'Untersuche Version $1 …',
		'wikiblame-load-diff': 'Lade Versionsunterschied …',
		'wikiblame-dialog-search-label': 'Suche nach:',
		'wikiblame-dialog-search-help': 'Der Text oder reguläre Ausdruck, nach dem gesucht werden soll',
		'wikiblame-dialog-regexp-label': 'regulärer Ausdruck',
		'wikiblame-dialog-regexp-help': 'Suchtext als regulären Ausdruck interpretieren',
		'wikiblame-dialog-source-label': 'Quelltext durchsuchen',
		'wikiblame-dialog-source-help': 'Durchsucht den Quelltext anstatt des geparsten HTML',
		'wikiblame-dialog-invert-label': 'Löschung finden',
		'wikiblame-dialog-invert-help': 'Sucht nach der Löschung des Suchtextes statt nach der Einfügung',
		'wikiblame-dialog-later-label': 'spätere Versionen',
		'wikiblame-dialog-later-help': 'Durchsucht die Versionen ab der aktuell gezeigten, statt die früheren bis zu dieser',
		'wikiblame-dialog-linear-label': 'lineare Suche',
		'wikiblame-dialog-linear-help': 'Langsame Suchvariante, die immer die nächste passende Änderung findet',
		'wikiblame-dialog-down-label': 'bei neueren Versionen beginnen',
		'wikiblame-dialog-down-help': 'Bei der linearen Suche mit den neuesten Versionen beginnen',
		'wikiblame-dialog-go': 'Suche',
		'wikiblame-dialog-done': 'Schließen'
	},
	'de-ch': {
		'wikiblame-dialog-done': 'Schliessen'
	}
//jscs:enable maximumLineLength
}, cachedHistory = {},
cachedContent = {
	0: {
		0: $.Deferred().resolve('')
	},
	1: {
		0: $.Deferred().resolve('')
	}
};

function initL10N (l10n) {
	var i, chain = mw.language.getFallbackLanguageChain();
	for (i = chain.length - 1; i >= 0; i--) {
		if (chain[i] in l10n) {
			mw.messages.set(l10n[chain[i]]);
		}
	}
}

function getHistory (page) {
	if (!(page in cachedHistory)) {
		cachedHistory[page] = $.Deferred();
		retrieveHistory(page, cachedHistory[page]);
	}
	return cachedHistory[page].promise();
}

function retrieveHistory (page, deferred) {
	var hist = [0];
	function next (cont) {
		var data = {
			action: 'query',
			prop: 'revisions',
			titles: page,
			rvprop: 'ids',
			rvlimit: 'max',
			rvdir: 'newer',
			'continue': '',
			format: 'json',
			formatversion: 2
		};
		if (cont) {
			$.extend(data, cont);
		}
		$.getJSON(mw.util.wikiScript('api'), data).then(function (json) {
			if (
				!json || !json.query || !json.query.pages ||
				!json.query.pages[0] || !json.query.pages[0].revisions
			) {
				deferred.reject(mw.msg('wikiblame-api-error'));
				return;
			}
			var revs = json.query.pages[0].revisions, i;
			for (i = 0; i < revs.length; i++) {
				hist.push(revs[i].revid);
			}
			if (json['continue']) {
				next(json['continue']);
			} else {
				deferred.resolve(hist);
			}
		}, function () {
			deferred.reject(mw.msg('wikiblame-api-error'));
		});
	}
	next();
}

function getContent (revision, parsed) {
	if (!(revision in cachedContent[parsed ? 1 : 0])) {
		cachedContent[parsed ? 1 : 0][revision] = $.Deferred();
		retrieveContent(revision, parsed, cachedContent[parsed ? 1 : 0][revision]);
	}
	return cachedContent[parsed ? 1 : 0][revision].promise();
}

function retrieveContent (revision, parsed, deferred) {
	var prop = parsed ? 'text' : 'wikitext',
		data = {
			action: 'parse',
			oldid: revision,
			prop: prop,
			format: 'json',
			formatversion: 2
		};
	$.getJSON(mw.util.wikiScript('api'), data).then(function (json) {
		if (!json || !json.parse) {
			deferred.reject(mw.msg('wikiblame-api-error'));
			return;
		}
		var content = json.parse[prop] || '';
		if (parsed) {
			content = $('<div>').html(content).text();
		}
		deferred.resolve(content);
	}, function () {
		deferred.reject(mw.msg('wikiblame-api-error'));
	});
}

function blame (page, min, max, search, parsed, invert, linear, down) {
	var history, d = $.Deferred();

	function start () {
		if (min === 0 && invert) {
			min = history[1];
		}
		if (max === 0) {
			max = history[history.length - 1];
		}
		if (linear) {
			blameLinear(min, max, down);
		} else {
			d.notify(mw.msg('wikiblame-test-revision', min));
			testRevision(min).then(function (result) {
				if (result) {
					d.reject(mw.msg(invert ? 'wikiblame-not-in-first-revision' :
						'wikiblame-in-first-revision'));
				} else {
					d.notify(mw.msg('wikiblame-test-revision', max));
					testRevision(max).then(function (result) {
						if (!result) {
							d.reject(mw.msg(invert ? 'wikiblame-in-last-revision' :
								'wikiblame-not-in-last-revision'));
						} else {
							blameBinary(min, max);
						}
					}, d.reject);
				}
			}, d.reject);
		}
	}

	function testRevision (revision) {
		return getContent(revision, parsed).then(function (content) {
			var result = (typeof search === 'string') ? (content.indexOf(search) > -1) :
				(search.test(content));
			if (invert) {
				result = !result;
			}
			return result;
		});
	}

	function blameBinary (min, max) {
		var i = history.indexOf(min), j = history.indexOf(max), middle;
		if (i === -1 || j === -1 || i >= j) {
			d.reject(mw.msg('wikiblame-wrong-revisions'));
		} else if (i + 1 === j) {
			d.resolve([min, max]);
		} else {
			middle = history[Math.floor((i + j) / 2)];
			d.notify(mw.msg('wikiblame-test-revision', middle));
			testRevision(middle).then(function (result) {
				if (result) {
					blameBinary(min, middle);
				} else {
					blameBinary(middle, max);
				}
			}, d.reject);
		}
	}

	function blameLinear (min, max, found) {
		var i = history.indexOf(min), j = history.indexOf(max), revision = down ? max : min;
		if (i === -1 || j === -1 || i > j) {
			d.reject(mw.msg('wikiblame-not-found'));
			return;
		}
		d.notify(mw.msg('wikiblame-test-revision', revision));
		testRevision(revision).then(function (result) {
			if (down) {
				if (result || !found) {
					blameLinear(min, history[j - 1], result);
				} else {
					d.resolve([max, history[j + 1]]);
				}
			} else {
				if (!result || found) {
					blameLinear(history[i + 1], max, result);
				} else {
					d.resolve([history[i - 1], min]);
				}
			}
		}, d.reject);
	}

	d.notify(mw.msg('wikiblame-get-history'));
	getHistory(page).then(function (h) {
		history = h;
		start();
	}, d.reject);
	return d.promise();
}

function wikiblame (search, regexp, source, invert, rev, later, linear, down) {
	if (regexp) {
		try {
			search = new RegExp(search);
		} catch (e) {
			return $.Deferred().reject(mw.msg('wikiblame-invalid-regexp')).promise();
		}
	}
	var min = 0, max = 0;
	if (later) {
		min = rev;
	} else {
		max = rev;
	}
	return blame(mw.config.get('wgPageName'), min, max, search, !source, invert, linear, down);
}

function getWikiBlameDialog () {

	function makeCheckbox (id, $overlay) {
		var checkbox, field;
		checkbox = new OO.ui.CheckboxInputWidget();
		field = new OO.ui.FieldLayout(checkbox, {
			label: mw.msg('wikiblame-dialog-' + id + '-label'),
			help: mw.msg('wikiblame-dialog-' + id + '-help'),
			align: 'inline',
			$overlay: $overlay
		});
		return {field: field, element: checkbox};
	}

	function WikiBlameDialog (config) {
		WikiBlameDialog.parent.apply(this, arguments);
		this.rev = config.rev;
	}

	OO.inheritClass(WikiBlameDialog, OO.ui.ProcessDialog);

	WikiBlameDialog.static.name = 'schnark-wikiblame';
	WikiBlameDialog.static.title = mw.msg('wikiblame');
	WikiBlameDialog.static.size = 'large';
	WikiBlameDialog.static.actions = [
		{
			action: 'go',
			label: mw.msg('wikiblame-dialog-go'),
			flags: ['progressive', 'primary']
		}, {
			action: 'done',
			label: mw.msg('wikiblame-dialog-done'),
			flags: ['safe', 'close']
		}
	];

	WikiBlameDialog.prototype.getBodyHeight = function () {
		return this.panel.$element.outerHeight(true) + 50;
	};

	WikiBlameDialog.prototype.getActionProcess = function (action) {
		if (action === 'go') {
			return new OO.ui.Process(function () {
				var actions = this.getActions();
				actions.setAbilities({done: false});
				actions.get({actions: 'go'})[0].pushPending();
				this.runSearch().always(function () {
					actions.setAbilities({done: true});
					actions.get({actions: 'go'})[0].popPending();
				});
			}, this);
		} else if (action === 'done') {
			return new OO.ui.Process(function () {
				this.close();
			}, this);
		}
		return WikiBlameDialog.parent.prototype.getActionProcess.apply(this, arguments);
	};

	WikiBlameDialog.prototype.onLinearInputChange = function () {
		this.downInput.setDisabled(!this.linearInput.isSelected());
	};

	WikiBlameDialog.prototype.initialize = function () {
		var fieldset, checkbox;
		WikiBlameDialog.parent.prototype.initialize.apply(this, arguments);

		this.panel = new OO.ui.PanelLayout({padded: true, expanded: false});

		fieldset = new OO.ui.FieldsetLayout();
		this.panel.$element.append(fieldset.$element);
		this.$body.append(this.panel.$element);

		this.searchInput = new OO.ui.TextInputWidget();
		fieldset.addItems([
			new OO.ui.FieldLayout(this.searchInput, {
				label: mw.msg('wikiblame-dialog-search-label'),
				help: mw.msg('wikiblame-dialog-search-help'),
				align: 'top',
				$overlay: this.$overlay
			})
		]);

		checkbox = makeCheckbox('regexp', this.$overlay);
		this.regexpInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		checkbox = makeCheckbox('source', this.$overlay);
		this.sourceInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		checkbox = makeCheckbox('invert', this.$overlay);
		this.invertInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		checkbox = makeCheckbox('later', this.$overlay);
		this.laterInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		checkbox = makeCheckbox('linear', this.$overlay);
		this.linearInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		checkbox = makeCheckbox('down', this.$overlay);
		this.downInput = checkbox.element;
		fieldset.addItems([checkbox.field]);

		this.progressBar = new OO.ui.ProgressBarWidget();
		this.progressBarField = new OO.ui.FieldLayout(
			this.progressBar,
			{label: '', align: 'top'}
		);
		this.progressBarField.toggle(false);
		fieldset.addItems([this.progressBarField]);

		this.linearInput.connect(this, {change: 'onLinearInputChange'});
		this.onLinearInputChange();
		this.laterInput.setDisabled(this.rev === '0');
	};

	WikiBlameDialog.prototype.getReadyProcess = function () {
		return WikiBlameDialog.parent.prototype.getReadyProcess.apply(this, arguments)
			.next(function () {
				this.searchInput.focus();
			}, this);
	};

	WikiBlameDialog.prototype.getDataFromInputElements = function () {
		var data = {
			search: this.searchInput.getValue(),
			regexp: this.regexpInput.isSelected(),
			source: this.sourceInput.isSelected(),
			invert: this.invertInput.isSelected(),
			later: this.laterInput.isSelected(),
			linear: this.linearInput.isSelected(),
			down: this.downInput.isSelected()
		}, rev = this.rev.split('|');
		data.rev = (rev.length > 1 && (data.earlier !== data.linear)) ? Number(rev[1]) : Number(rev[0]);
		return data;
	};

	WikiBlameDialog.prototype.runSearch = function () {
		var data = this.getDataFromInputElements();
		this.progressBar.setProgress(false);
		this.progressBarField.toggle(true);
		return wikiblame(
			data.search, data.regexp, data.source, data.invert,
			data.rev, data.later, data.linear, data.down
		).then(function (data) {
			var url = data[0] !== 0 ?
				mw.util.getUrl(mw.config.get('wgPageName'), {diff: data[1], oldid: data[0]}) :
				mw.util.getUrl(mw.config.get('wgPageName'), {oldid: data[1]});
			this.progressBar.setProgress(100);
			this.progressBarField.setLabel(mw.msg('wikiblame-load-diff'));
			location.href = url;
		}.bind(this), function (error) {
			this.progressBar.setProgress(100);
			this.progressBarField.setLabel(error);
		}.bind(this), function (notice) {
			this.progressBarField.setLabel(notice);
		}.bind(this));
	};

	return WikiBlameDialog;
}

function showDialog (rev) {
	var WikiBlameDialog, dialog, windowManager;
	WikiBlameDialog = getWikiBlameDialog();
	dialog = new WikiBlameDialog({rev: rev});
	windowManager = new OO.ui.WindowManager();
	$('body').append(windowManager.$element);
	windowManager.addWindows([dialog]);
	windowManager.openWindow(dialog);
}

function run (e) {
	e.preventDefault();
	mw.loader.using(['oojs-ui-core', 'oojs-ui-windows']).then(function () {
		var id1 = mw.util.getParamValue('oldid'), id2 = mw.util.getParamValue('diff'), id = '0';
		if (id1) {
			id = id1;
			if (id2 && !(/\D/.test(id2)) && Number(id1) < Number(id2)) {
				id += '|' + id2;
			}
		}
		showDialog(id);
	});
}

function init () {
	$(mw.util.addPortletLink('p-tb', '#', mw.msg('wikiblame'),
		't-wikiblame-js', mw.msg('wikiblame-tooltip'))).on('click', run);
}

mw.loader.using('mediawiki.language').then(function () {
	initL10N(l10n);
});

libs.wikiblame = blame;

if (mw.config.get('wgNamespaceNumber') >= 0) {
	mw.loader.using(['mediawiki.util', 'mediawiki.language']).then(init);
}

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