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/artikel-statistik]] <nowiki>
/*global mediaWiki*/

(function ($, mw, libs) {
"use strict";

var l10n = {
//jscs:disable maximumLineLength
		en: {
			'jan': 'jan',
			'feb': 'feb',
			'mar': 'mar',
			'apr': 'apr',
			'may': 'may',
			'jun': 'jun',
			'jul': 'jul',
			'aug': 'aug',
			'sep': 'sep',
			'oct': 'oct',
			'nov': 'nov',
			'dec': 'dec',
			'schnark-artikel-statistik-date-format': '$1 $2 $3 at $4:$5 (UTC)',
			'schnark-artikel-statistik-tab': 'Article statistics',
			'schnark-artikel-statistik-tab-tooltip': 'Analyzes the wikitext to identify the author of every word.',
			'schnark-artikel-statistik-show-details': 'Show details',
			'schnark-artikel-statistik-status-error': 'An internal error occurred!',
			'schnark-artikel-statistik-status-diff': 'Loading Schnark’s diff.js',
			'schnark-artikel-statistik-status-history': 'Getting history',
			'schnark-artikel-statistik-status-analyzing': 'Analyzing revisions ($1/$2$3)',
			'schnark-artikel-statistik-status-more': '+',
			'schnark-artikel-statistik-history-created': 'Created by $1 on $2.',
			'schnark-artikel-statistik-history-revisions': '$1 revisions by $2 different authors.<br>$3 minor revisions ($4 %).',
			'schnark-artikel-statistik-authors-title': 'Authors:',
			'schnark-artikel-statistik-authors-entry': '$1: $2',
			'schnark-artikel-statistik-authors-info': '$1 characters ($2 %)',
			'schnark-artikel-statistik-legend': 'Legend:',
			'schnark-artikel-statistik-legend-deleted': 'deleted',
			'schnark-artikel-statistik-legend-editwar': 'edit war',
			'schnark-artikel-statistik-legend-deleted-editwar': 'edit war (deleted)',
			'schnark-artikel-statistik-no-comment': '(no comment)',
			'schnark-artikel-statistik-info-created': 'Created by $1 on $2: $3',
			'schnark-artikel-statistik-info-restored': 'Restored by $1 on $2: $3',
			'schnark-artikel-statistik-info-deleted': 'Deleted by $1 on $2: $3',
			'schnark-artikel-statistik-info-moved': 'Moved by $1 on $2: $3',
			'schnark-artikel-statistik-info-more-changes': '{{PLURAL:$1|One more change|$1 more changes}}'
		},
		de: {
			'jan': 'Jan.',
			'feb': 'Feb.',
			'mar': 'Mär.',
			'apr': 'Apr.',
			'may': 'Mai',
			'jun': 'Jun.',
			'jul': 'Jul.',
			'aug': 'Aug.',
			'sep': 'Sep.',
			'oct': 'Okt.',
			'nov': 'Nov.',
			'dec': 'Dez.',
			'schnark-artikel-statistik-date-format': '$1. $2 $3 um $4:$5 (UTC)',
			'schnark-artikel-statistik-tab': 'Artikel-Statistik',
			'schnark-artikel-statistik-tab-tooltip': 'Analysiert den Wikitext um den Autor zu jedem Wort zu bestimmen',
			'schnark-artikel-statistik-show-details': 'Details anzeigen',
			'schnark-artikel-statistik-status-error': 'Es trat ein interner Fehler auf!',
			'schnark-artikel-statistik-status-diff': 'Lade Schnarks diff.js',
			'schnark-artikel-statistik-status-history': 'Lade Versionsgeschichte',
			'schnark-artikel-statistik-status-analyzing': 'Analysiere Versionen ($1/$2$3)',
			'schnark-artikel-statistik-history-created': 'Erstellt von $1 am $2.',
			'schnark-artikel-statistik-history-revisions': '$1 Versionen von $2 verschiedenen Autoren.<br>$3 kleine Änderungen ($4 %).',
			'schnark-artikel-statistik-authors-title': 'Autoren:',
			'schnark-artikel-statistik-authors-info': '$1 Zeichen ($2 %)',
			'schnark-artikel-statistik-legend': 'Legende:',
			'schnark-artikel-statistik-legend-deleted': 'gelöscht',
			'schnark-artikel-statistik-legend-editwar': 'umstritten',
			'schnark-artikel-statistik-legend-deleted-editwar': 'umstritten und gelöscht',
			'schnark-artikel-statistik-no-comment': '(keine Zusammenfassung)',
			'schnark-artikel-statistik-info-created': 'Erstellt von $1 am $2: $3',
			'schnark-artikel-statistik-info-restored': 'Wiederhergestellt von $1 am $2: $3',
			'schnark-artikel-statistik-info-deleted': 'Gelöscht von $1 am $2: $3',
			'schnark-artikel-statistik-info-moved': 'Verschoben von $1 am $2: $3',
			'schnark-artikel-statistik-info-more-changes': '{{PLURAL:$1|Eine weitere Änderung|$1 weitere Änderungen}}'
		},
		'de-at': {
			'jan': 'Jän.'
		}
//jscs:enable maximumLineLength
	}, config = {
		onAllPages: false, //show on all pages or only in NS 0?

		removeChunks: true,
		histCount: 50,
		histPrefetch: 100,
		longHistory: 3,
		shortText: 5,
		timeForShort: 10000000, //ddhhmmss
		timeForLong: 600000000, //mddhhmmss
		allSlots: true,

		css: {
			del: 'text-decoration: line-through; display: none;',
			ew: 'font-style: italic;',
			delEw: 'display: inline;',
			authors: [
				['', ''],
				['background-color: #FF8080;', 'border-left: 1px solid #FF0000;'], //first author: block, repeated block
				['background-color: #00BFFF;', 'border-left: 1px solid #0000FF;'],
				['background-color: #80FF80;', 'border-left: 1px solid #00FF00;'],
				['background-color: #FFFF00;', 'border-left: 1px solid #808000;'],
				['background-color: #FF80FF;', 'border-left: 1px solid #FF00FF;'],
				['background-color: #FF8000;', 'border-left: 1px solid #FF0000;'],
				['background-color: #8FBC8F;', 'border-left: 1px solid #408040;']
			]
		}
	}, historyData = {
	/*
	array of objects
		author, date, id, minor, text, comment
	*/
		hist: [],
		done: false,
		pause: false
	},
/*
array of objects
	text: text of the chunk
	history: its history in an array of objects
		type: + for inserted, - for deleted, ~: for moved
		author, date, id, comment
	visible: currently visible
*/
	allChunks;

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 whileAsync (opts) {
	function work () {
		var t = opts.test(), begin = new Date();
		while (t) {
			opts.loop();
			if ((new Date() - begin) > 500) {
				break;
			}
			t = opts.test();
		}
		if (t) {
			setTimeout(work, 10);
		} else {
			opts.end();
		}
	}
	work();
}

function eachAsync (array, opts) {
	var i = 0, l = array.length;
	whileAsync({
		test: function () {
			return i < l;
		},
		loop: function () {
			opts.loop(i, array[i]);
			i++;
		},
		end: opts.end
	});
}

function getCSS () {
	var i, css = '';
	css += '#infoText {font-family: monospace, "Courier";}';
	css += '.deleted {' + config.css.del + '}';
	css += '.editWar {' + config.css.ew + '}';
	css += '.deleted.editWar {' + config.css.delEw + '}';
	for (i = 0; i < config.css.authors.length; i++) {
		css += '.author-' + (i - 1) + ' {' + config.css.authors[i][0] + '}' +
			'.author-' + (i - 1) + ' + .author-' + (i - 1) + '{' + config.css.authors[i][1] + '}';
	}

	css += '#artikel-statistik-legend {list-style: none;}';

	css += '#artikel-statistik-tooltip {position: fixed; top: 3em; left: 0.5em; background-color: #eaf3ff;' +
		'width: 10.75em; max-height: 30em; overflow-y: auto; padding: 3px; z-index: 2;' +
		'border: 1px dashed #36c; display: none; font-size:' + $('#mw-content-text').css('font-size') + '}';
	css += '#artikel-statistik-tooltip.tooltip-hover, #artikel-statistik-tooltip.tooltip-fixed {display: block;}';
	css += '#artikel-statistik-tooltip.tooltip-fixed {border-style: solid;}';
	css += '#artikel-statistik-tooltip-title {text-align: right;}';
	css += '#artikel-statistik-tooltip-close {display: none; font-weight: bold; cursor: pointer;}';
	css += '#artikel-statistik-tooltip.tooltip-fixed #artikel-statistik-tooltip-close {display: inline;}';
	return css;
}

function getLinkUser (a) {
	if (a.indexOf('>') > -1) {
		a = a.split('>');
		return mw.util.getUrl(a[0] + ':User:' + a[1]);
	}
	return mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[2] + ':' + a);
}
function getLinkOldVersion (id) {
	return mw.config.get('wgScript') + '?oldid=' + id;
}
function getLinkDiff (id) {
	return mw.config.get('wgScript') + '?oldid=' + id + '&diff=prev';
}

function formatDate (timestamp) {
	var fields = /^(....)-(..)-(..)T(..):(..):(..)Z$/.exec(timestamp);
	if (fields) {
		return mw.msg('schnark-artikel-statistik-date-format',
			Number(fields[3]),
			mw.msg(['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'][fields[2] - 1]),
			fields[1],
			Number(fields[4]),
			fields[5]
		);
	}
	return timestamp;
}

function loadDiffjs (callback) {
	var load = true;
	function callback2 (/*schnarkDiff*/) {
		load = false;
		mw.hook('userjs.load-script.diff-core').remove(callback2);
		callback(/*schnarkDiff*/);
	}
	mw.hook('userjs.load-script.diff-core').add(callback2);
	if (load) {
		showInfo(mw.msg('schnark-artikel-statistik-status-diff'));
		//</nowiki>[[Benutzer:Schnark/js/diff.js/core.js]]<nowiki>
		mw.loader.load('https://de.wikipedia.org/w/index.php?title=' +
			'Benutzer:Schnark/js/diff.js/core.js&action=raw&ctype=text/javascript');
	}
}

function median (arr) {
	arr.sort(function (a, b) {
		return a - b;
	});
	var l = arr.length;
	return (l % 2 === 0) ? arr[l / 2] : (arr[(l - 1) / 2] + arr[(l + 1) / 2]) / 2;
}

function showInfo (text) {
	$('#infoStatus').html(text);
}

function collectContent (slots) {
	var content = [slots.main.content || ''];
	$.each(slots, function (name, slot) {
		if (name !== 'main') {
			content.push(name + '\n\n' + slot.content);
		}
	});
	return content.join('\n\n\n');
}

function getHistory (callback, queryCont) {
	if (historyData.pause) {
		window.setTimeout(function () {
			getHistory(callback, queryCont);
		}, 1000);
		return;
	}
	var data = {
		action: 'query', prop: 'revisions',
		titles: mw.config.get('wgPageName'),
		rvlimit: config.histCount, rvdir: 'newer',
		rvprop: 'ids|flags|timestamp|user|content|parsedcomment',
		rvslots: config.allSlots ? '*' : 'main',
		format: 'json', formatversion: 2
	}, oldid = mw.config.get('wgRevisionId'), toqc = typeof queryCont;
	if (oldid && oldid !== mw.config.get('wgCurRevisionId')) {
		data.rvendid = oldid;
	}
	if (toqc !== 'function') {
		$.extend(data, queryCont);
	} else {
		data['continue'] = '';
	}
	$.getJSON(mw.util.wikiScript('api'), data).always(function (json) {
		if (!json || !json.query || !json.query.pages || !json.query.pages[0]) {
			return showInfo('<span class="error">' + mw.msg('schnark-artikel-statistik-status-error') + '</span>');
		}
		var i, rev = json.query.pages[0].revisions;
		for (i = 0; i < rev.length; i++) {
			historyData.hist.push({
				author: rev[i].user || '127.0.0.1',
				date: rev[i].timestamp,
				id: rev[i].revid,
				minor: rev[i].minor,
				text: collectContent(rev[i].slots),
				comment: rev[i].parsedcomment || mw.msg('schnark-artikel-statistik-no-comment')
			});
		}
		if (toqc === 'function') {
			queryCont(); //abused for a function call on first result
		}
		if (json['continue']) {
			getHistory(callback, json['continue']);
		} else {
			historyData.done = true;
			callback();
		}
	});
}

function showHistory () {
	var h = historyData.hist, authors = [], minor = 0, i, html;
	for (i = 0; i < h.length; i++) {
		if (authors.indexOf(h[i].author) === -1) {
			authors.push(h[i].author);
		}
		if (h[i].minor) {
			minor++;
		}
	}
	html = '<p>';
	html += mw.msg('schnark-artikel-statistik-history-created',
		mw.html.element('a', {href: getLinkUser(h[0].author)}, h[0].author),
		mw.html.element('a', {href: getLinkOldVersion(h[0].id)}, formatDate(h[0].date))
	);
	html += '</p><p>';
	html += mw.msg('schnark-artikel-statistik-history-revisions',
		h.length, authors.length, minor, Math.round(100 * minor / h.length));
	html += '</p>';
	$('#infoHistory').html(html);
	mw.hook('wikipage.content').fire($('#infoHistory'));
}

//updates the chunks
function compareToNew (n, author, date, id, comment) {
	n = n.replace(/\s*$/, '');
	if (!allChunks) {
		allChunks = [{text: n, history: [{type: '+', author: author, date: date, id: id, comment: comment}], visible: true}];
		return;
	}
	var o = '', i, j, l, diff,
		movedFrom = [], //array of ['moved text', chunk]
		chunks = [], //new chunk array
		chunkPos = 0, diffPos = 0, //positions in (old) chunks / diff
		oldText, newText, //old/new text
		oShorterN, //old text shorter or equal than new text?
		changed, //for =, -: visibility changed?
		newChunk, //new chunk
		movedText = ''; //whole text of a moved block, '' if no we are not in such a block
	for (i = 0; i < allChunks.length; i++) {
		o += allChunks[i].text;
	}
	diff = libs.schnarkDiff.diff(o, n);

	while (diffPos < diff.length) {
		if (diff[diffPos][0] === '') { //empty, ignore
			diffPos++;
			continue;
		}
		switch (diff[diffPos][1]) {
		case '=':
		case '-':
			oldText = allChunks[chunkPos].text;
			newText = diff[diffPos][0];
			oShorterN = oldText.length <= newText.length;
			changed = (diff[diffPos][1] === '=') ? !allChunks[chunkPos].visible : allChunks[chunkPos].visible;
			newChunk = {};
			newChunk.text = allChunks[chunkPos].text;
			newChunk.visible = allChunks[chunkPos].visible;
			newChunk.history = [];
			for (i = 0; i < allChunks[chunkPos].history.length; i++) {
				newChunk.history.push(allChunks[chunkPos].history[i]);
			}
			if (!oShorterN) {
				newChunk.text = newText;
			}
			if (changed) {
				newChunk.history.push({type: (diff[diffPos][1] === '=') ? '+' : '-',
					author: author, date: date, id: id, comment: comment});
				newChunk.visible = diff[diffPos][1] === '=';
			}
			chunks.push(newChunk);
			if (oShorterN) {
				newText = newText.slice(oldText.length);
				diff[diffPos][0] = newText;
				if (newText.length === 0) {
					diffPos++;
				}
				chunkPos++;
			} else {
				oldText = oldText.slice(newText.length);
				allChunks[chunkPos].text = oldText;
				diffPos++;
			}
			break;
		case '+':
			chunks.push({text: diff[diffPos][0], history: [{type: '+', author: author, date: date, id: id, comment: comment}],
				visible: true});
			diffPos++;
			break;
		case ':':
			oldText = allChunks[chunkPos].text;
			newText = diff[diffPos][0];
			oShorterN = oldText.length <= newText.length;
			newChunk = {};
			newChunk.text = allChunks[chunkPos].text;
			newChunk.visible = allChunks[chunkPos].visible;
			newChunk.history = [];
			for (i = 0; i < allChunks[chunkPos].history.length; i++) {
				newChunk.history.push(allChunks[chunkPos].history[i]);
			}
			if (movedText === '') {
				movedText = newText;
			}
			if (!oShorterN) {
				newChunk.text = newText;
			}
			newChunk.history.push({type: (newChunk.visible) ? '~' : '+',
				author: author, date: date, id: id, comment: comment});
			newChunk.visible = true;
			movedFrom.push([movedText, newChunk]);
			if (oShorterN) {
				newText = newText.slice(oldText.length);
				diff[diffPos][0] = newText;
				if (newText.length === 0) {
					diffPos++;
					movedText = '';
				}
				chunkPos++;
			} else {
				oldText = oldText.slice(newText.length);
				allChunks[chunkPos].text = oldText;
				diffPos++;
				movedText = '';
			}
			break;
		case '.':
			chunks.push(diff[diffPos][0]);
			diffPos++;
		}
	}

	allChunks = [];
	for (i = 0; i < chunks.length; i++) {
		if (typeof chunks[i] === 'string') {
			l = 0;
			forj: for (j = 0; j < movedFrom.length; j++) {
				if (movedFrom[j][0] === chunks[i]) {
					l += movedFrom[j][1].text.length;
					allChunks.push(movedFrom[j][1]);
					movedFrom[j][0] = '';
					if (l >= chunks[i].length) {
						break forj;
					}
				}
			}
		} else {
			allChunks.push(chunks[i]);
		}
	}
}

//remove deleted chunks that seem not to be needed
function removeChunks (date) {
	var i, chunks = [], chunk, l;
	if (!config.removeChunks) {
		return;
	}
	date = date.replace(/\D+/g, '');
	for (i = 0; i < allChunks.length; i++) {
		chunk = allChunks[i];
		if (chunk.visible) { //visible: keep
			chunks.push(chunk);
			continue;
		}
		l = chunk.history.length;
		if (l >= config.longHistory) { //long history: keep
			chunks.push(chunk);
			continue;
		}
		if (
			chunk.text.length <= config.shortText &&
			(date - chunk.history[l - 1].date.replace(/\D+/g, '') > config.timeForShort)
		) { //short text, removed some time ago: omit
			continue;
		}
		if (date - chunk.history[l - 1].date.replace(/\D+/g, '') > config.timeForLong) { //text removed some time ago: omit
			continue;
		}
		chunks.push(chunk);
	}
	allChunks = chunks;
}

function analyzeRevisions (callback) {
	var i = 0;
	whileAsync({
		test: function () {
			return !historyData.done || i < historyData.hist.length;
		},
		loop: function () {
			if (i === historyData.hist.length) {
				return; //wait for next revisions to arrive
			}
			analyzeRevision(i);
			i++;
		},
		end: callback
	});
}

function analyzeRevision (i) {
	var l = historyData.hist.length, h = historyData.hist[i];
	historyData.pause = l - i > config.histPrefetch;
	showInfo(mw.msg('schnark-artikel-statistik-status-analyzing',
		i + 1,
		l,
		historyData.done ? '' : mw.msg('schnark-artikel-statistik-status-more')
	));
	compareToNew(h.text, h.author, h.date, h.id, h.comment);
	h.text = ''; //remove from memory
	if (i > 0 && i < l - 1) {
		removeChunks(historyData.hist[i + 1].date);
	}
}

function prepareShowResults () {
	var stylesheet = mw.util.addCSS('.detail{display:none;}');
	showInfo(
		mw.html.element('input', {type: 'checkbox', id: 'showDetails'}) +
		mw.html.element('label', {'for': 'showDetails'}, mw.msg('schnark-artikel-statistik-show-details'))
	);
	$('#showDetails').on('change', function () {
		stylesheet.disabled = !stylesheet.disabled;
	});
}

function showAuthors (authors, authorsHash, l) {
	var authorList = [], i;
	for (i = 0; i < authors.length; i++) {
		authorList.push(mw.html.element('li', {'class': i >= 10 ? 'detail' : false}, new mw.html.Raw(
			mw.msg('schnark-artikel-statistik-authors-entry',
				mw.html.element('a', {href: getLinkUser(authors[i])}, authors[i]),
				mw.html.element('span', {'class': 'author-' + i},
					mw.msg('schnark-artikel-statistik-authors-info',
						authorsHash[authors[i]],
						Math.round(authorsHash[authors[i]] / l * 100)
					)
				)
			)
		)));
	}
	$('#infoAuthors').html(
		mw.html.element('p', {}, new mw.html.Raw(mw.msg('schnark-artikel-statistik-authors-title'))) +
		mw.html.element('ol', {}, new mw.html.Raw(authorList.join(''))) +
		mw.html.element('p', {}, new mw.html.Raw(mw.msg('schnark-artikel-statistik-legend'))) +
		mw.html.element('ul', {id: 'artikel-statistik-legend'}, new mw.html.Raw(
			'<li class="deleted">' + mw.msg('schnark-artikel-statistik-legend-deleted') +
			'</li><li class="editWar">' + mw.msg('schnark-artikel-statistik-legend-editwar') +
			'</li><li class="deleted editWar">' + mw.msg('schnark-artikel-statistik-legend-deleted-editwar') + '</li>'
		))
	);
	mw.hook('wikipage.content').fire($('#infoAuthors'));
}

function getTextChunksForDisplay (authors, changes) {
	var text = [], i, j, h, info;
	/*array of objects
		text		text
		author		initial author (index in authors)
		info		history as html
		editWar	long history? (>= 2 * median)
		visible		visible?
	*/

	function formatInfo (action, author, date, id, comment) {
		var map = {
			'': 'created',
			'+': 'restored',
			'-': 'deleted',
			'~': 'moved'
		};
		return mw.msg('schnark-artikel-statistik-info-' + map[action],
			mw.html.element('a', {href: getLinkUser(author)}, author),
			mw.html.element('a', {href: getLinkDiff(id)}, formatDate(date)),
			comment
		);
	}

	for (i = 0; i < allChunks.length; i++) {
		info = '<p>';
		h = allChunks[i].history;
		info = formatInfo('', h[0].author, h[0].date, h[0].id, h[0].comment);
		info += '</p>';
		if (h.length > 1) {
			info += '<p>' + mw.msg('schnark-artikel-statistik-info-more-changes', h.length - 1) +
				'<span class="detail">:</span></p>';
			info += '<ul class="detail">';
			for (j = 1; j < h.length; j++) {
				info += '<li>' + formatInfo(h[j].type, h[j].author, h[j].date, h[j].id, h[j].comment) + '</li>';
			}
			info += '</ul>';
		}
		text.push({
			text: allChunks[i].text,
			author: authors.indexOf(h[0].author),
			info: info,
			editWar: h.length >= changes,
			visible: allChunks[i].visible
		});
	}
	return text;
}

function reallyShowTextChunks (text) {
	function mouseout () {
		$('#artikel-statistik-tooltip').removeClass('tooltip-hover');
	}
	function mousein (e) {
		var $tooltip = $('#artikel-statistik-tooltip');
		if (!$tooltip.hasClass('tooltip-fixed')) {
			$tooltip.addClass('tooltip-hover').find('#artikel-statistik-tooltip-inner').html(e.data.html);
		}
	}
	function mouseclick () {
		$('#artikel-statistik-tooltip').toggleClass('tooltip-fixed');
	}

	$('<div>', {id: 'artikel-statistik-tooltip'}).prependTo($('body'))
		.html('<div id="artikel-statistik-tooltip-title"><span id="artikel-statistik-tooltip-close">×</span></div>' +
			'<div id="artikel-statistik-tooltip-inner"></div>');
	$('#artikel-statistik-tooltip-close').on('click', function () {
		$('#artikel-statistik-tooltip').removeClass('tooltip-fixed');
	});

	var $infoText = $('<div>'), $el;
	eachAsync(text, {
		loop: function (i, textI) {
			$el = $('<span>');
			$el.addClass('author-' + textI.author);
			if (textI.editWar) {
				$el.addClass('editWar');
			}
			if (!textI.visible) {
				$el.addClass('deleted');
			}
			$el.html(mw.html.escape(textI.text).replace(/ {2}/g, ' &nbsp;').replace(/\n/g, '<br>'));
			$el.on('mouseover', {html: textI.info}, mousein).on('mouseout', mouseout).on('click', mouseclick);
			$infoText.append($el);
		},
		end: function () {
			$('#infoText').append($infoText);
		}
	});
}

function showResult () {
	var authors = [], authorsHash = {}, changes = [], c = allChunks, i, a, l = 0, text;

	prepareShowResults();

	for (i = 0; i < c.length; i++) {
		changes.push(c[i].history.length);
		if (!c[i].visible) {
			continue;
		}
		a = c[i].history[0].author;
		if (!(a in authorsHash)) {
			authors.push(a);
			authorsHash[a] = 0;
		}
		authorsHash[a] += c[i].text.length;
		l += c[i].text.length;
	}
	authors.sort(function (a, b) {
		return authorsHash[b] - authorsHash[a];
	});
	changes = median(changes) * 2;

	showAuthors(authors, authorsHash, l);
	text = getTextChunksForDisplay(authors, changes);
	reallyShowTextChunks(text);
}

function doit () {
	mw.loader.load(['mediawiki.jqueryMsg']); //load early
	mw.util.addCSS(getCSS());
	$('#ca-schnark-articleStat').remove();
	$('#mw-content-text').html(
		'<div id="infoStatus"></div><div id="infoHistory"></div><div id="infoAuthors"></div><div id="infoText"></div>'
	);
	loadDiffjs(function () {
		showInfo(mw.msg('schnark-artikel-statistik-status-history'));
		getHistory(
			showHistory, function () {
				mw.loader.using(['mediawiki.jqueryMsg']).then(function () {
					analyzeRevisions(
						showResult
					);
				});
			}
		);
	});
}

function getPortletId (ids) {
	var i;
	for (i = 0; i < ids.length; i++) {
		if ($('#' + ids[i]).length) {
			return ids[i];
		}
	}
	mw.log.warn('No id for portlet found!');
	return ids[0];
}

function getUrl () {
	var href = location.href;
	href = href.replace(/#.*/, '');
	href += href.indexOf('?') > 0 ? '&' : '?';
	return href + 'schnark-artikel-statistik=1';
}

function init () {
	if (mw.util.getParamValue('schnark-artikel-statistik')) {
		doit();
		return;
	}
	if (mw.config.get('wgNamespaceNumber') > 0 && !config.onAllPages) {
		return;
	}
	$(mw.util.addPortletLink(getPortletId(['p-cactions', 'p-pageactions', 'p-pagemisc']), getUrl(),
		mw.msg('schnark-artikel-statistik-tab'), 'ca-schnark-articleStat',
		mw.msg('schnark-artikel-statistik-tab-tooltip')
	)).on('click', function (e) {
		e.preventDefault();
		doit();
	});
}

mw.hook('userjs.load-script.artikelStatistik').fire(config);

if (mw.config.get('wgNamespaceNumber') >= 0) {
	mw.loader.using(['mediawiki.util', 'mediawiki.language']).then(function () {
		initL10N(l10n, ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']);
		$(init);
	});
}

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