User:Nardog/MoveHistoryClassic-core.js

mw.loader.using([	'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'mediawiki.widgets',	'jquery.tablesorter', 'oojs-ui.styles.icons-interactions',	'mediawiki.interface.helpers.styles' ], function moveHistoryCore {	mw.util.addCSS('.movehistory .oo-ui-radioOptionWidget{display:inline-table} .movehistory .oo-ui-radioOptionWidget{margin-right:8px} .movehistory .mw-widget-dateInputWidget{width:8em} .movehistory > table{margin-top:0}');	let $div = $(' ').addClass('movehistory');	let $status = $(' ').addClass('small'), $trail = $(' '), $table, $tbody;	let button = new OO.ui.ButtonInputWidget({ label: 'Search', flags: ['primary', 'progressive'], type: 'submit' });	let directionInput = new OO.ui.RadioSelectInputWidget({ options: [ { data: 'newer', label: 'Oldest first' }, { data: 'older', label: 'Newest first' } ]	});	let sinceInput = new mw.widgets.DateInputWidget({ placeholderLabel: 'Since', displayFormat: 'YYYY-MM-DD' });	let untilInput = new mw.widgets.DateInputWidget({ placeholderLabel: 'Until', displayFormat: 'YYYY-MM-DD' });	let form = new OO.ui.FormLayout({ items: [ button, directionInput, new OO.ui.HorizontalLayout({ items: [sinceInput, untilInput] }) ],		classes: ['oo-ui-horizontalLayout'] });	let inputs = [button, directionInput, sinceInput, untilInput];	let setDisabledAll = disabled => {		inputs.forEach(input => { input.setDisabled(disabled); });	};	let currentName = mw.config.get('wgPageName').replace(/_/g, ' ');	let api, rvcontinue, ascending, isComplete, lastDate;	let modified, prevDir, prevSince, prevUntil;	let loadRevs = => {		if (modified) {			reset;		} else if (isComplete) {			loadMoves;			return;		}		setDisabledAll(true);		let dir = directionInput.getValue;		prevDir = dir;		ascending = dir === 'newer';		let since = sinceInput.getValue;		prevSince = since;		since = (since || '2005-06-25') + 'T00:00:00Z';		let until = untilInput.getValue;		prevUntil = until;		until = until ? until + 'T23:59:59Z' : undefined;		$status.text(`Loading history${lastDate ? (ascending ? ' after ' : ' before ') + lastDate : ''}...`);		if (!api) api = new mw.Api({ ajax: { headers: { 'Api-User-Agent': 'MoveHistory (https://en.wikipedia.org/wiki/User:Nardog/MoveHistory)' } } });		api.get({ action: 'query', titles: currentName, prop: 'revisions', rvstart: ascending ? since : until, rvend: ascending ? until : since, rvdir: dir, rvprop: 'sha1|timestamp|user|comment', rvlimit: 'max', rvcontinue: rvcontinue, formatversion: 2 }).always((response, errorObj) => { let errorMsg = ((errorObj || {}).error || {}).info; if (!response || typeof response === 'string' || errorMsg) { button.setDisabled(false); $status.text('Error retrieving revisions' + (errorMsg ? ': ' + errorMsg : '')); return; }			processRevs(((((response || {}).query || {}).pages || [])[0] || {}).revisions || []); rvcontinue = ((response || {}).continue || {}).rvcontinue; if (!rvcontinue) isComplete = response.batchcomplete; loadMoves; });	};	let lastRev, candidates = [], revCount = 0;	let processRevs = revs => {		revCount += revs.length;		if (lastRev) revs.unshift(lastRev);		let increment = ascending ? -1 : 1;		revs.forEach((rev, i) => { if (!(rev.comment && rev.user && rev.sha1)) return; let comp = revs[i + increment]; if (!comp || comp.sha1 !== rev.sha1) return; let matches = rev.comment.match(/\[\[:?([^\]]+)\]\].+?\[\[:?([^\]]+)\]\]/); if (matches) rev.matches = matches.slice(1); candidates.push(rev); });		lastRev = revs.pop;		lastDate = lastRev.timestamp;	};	let titles = new Set, processed = new Set, missing = new Set;	let loadMoves = => {		let rev = candidates.shift;		let titlesArg = [...titles].filter(t => !processed.has(t)).join('|') || undefined;		if (!rev && !titlesArg) {			finish;			return;		}		let args = {			action: 'query',			titles: titlesArg,			formatversion: 2		};		if (rev) {			$status.text(`Seeing if there was a move at ${rev.timestamp}...`);			let date = Date.parse(rev.timestamp) / 1000;			Object.assign(args, { list: 'logevents', letype: 'move', lestart: date + 60, leend: date, leprop: 'details|title|user|parsedcomment', lelimit: 'max' });		}		api.get(args).always((response, errorObj) => { let errorMsg = ((errorObj || {}).error || {}).info; if (!response || typeof response === 'string' || errorMsg) { button.setDisabled(false); $status.text('Error retrieving moves' + (errorMsg ? ': ' + errorMsg : '')); return; }			(((response || {}).query || {}).pages || []).forEach(page => {				processed.add(page.title);				if (page.missing) missing.add(page.title);			}); (((response || {}).query || {}).logevents || []).reverse.some(le => {				if (le.user !== rev.user || !rev.comment.includes(le.title)) return;				let target = ((le || {}).params || {}).target_title;				if (!target || !rev.comment.includes(target)) return;				if (rev.matches && [le.title, target].some(s => !rev.matches.includes(s))) return;				addRow(rev.timestamp, le.title, target, le.user, le.parsedcomment);				return true;			}); loadMoves; });	};	let arrow = document.dir === 'rtl' ? ' ← ' : ' → ', lastName, count = 0;	let addRow = (date, from, to, user, comment) => {		if (!count) {			lastName = ascending ? from : to;			$trail.append(makeLink(lastName));		}		if (ascending) {			if (lastName !== from) $trail.append(' ... ', makeLink(from));			$trail.append(arrow, makeLink(to));			lastName = to;		} else {			if (lastName !== to) $trail.prepend(makeLink(to), ' ... ');			$trail.prepend(makeLink(from), arrow);			lastName = from;		}		if (!$table) {			$tbody = $(' ');			$table = $(' ').addClass('wikitable sortable').append( $(' ').append(					$(' ').text('Date'),					$(' ').text('From'),					$(' ').text('To'),					$(' ').text('Performer'),					$(' ').text('Comment')				).wrap(' ').parent, $tbody );		}		$(' ').append( $(' ').append(				$('', { href: mw.util.getUrl(currentName, { action: 'history', offset: Number(date.replace(/\D/g, '')) + 1 }), text: date })			),			$(' ').append(makeLink(from)), $(' ').append(makeLink(to)), $(' ').append(				$('', { href: mw.util.getUrl('User:' + user), text: user }),				' ',				$(' ').addClass('mw-changeslist-links').append( $('', {						href: mw.util.getUrl('User talk:' + user),						text: 'talk',					}).wrap(' ').parent, $('', {						href: mw.util.getUrl('Special:Contributions/' + user),						text: 'contribs'					}).wrap(' ').parent )			),			$(' ').append(comment) ).appendTo($tbody);		$table.insertAfter($trail);		count++;	};	let blueLinks = {};	let makeLink = name => {		if (name === currentName) {			return name;		} else if (missing.has(name)) {			return $('', { class: 'new', href: mw.util.getUrl(name, { action: 'edit', redlink: 1 }), text: name });		} else {			let $link = $('', { href: mw.util.getUrl(name, { redirect: 'no' }), text: name });			if (blueLinks[name]) {				blueLinks[name] = blueLinks[name].add($link);			} else {				blueLinks[name] = $link;			}			titles.add(name);			return $link;		}	};	let i = 0, lastCount = 0, prevLabel, prevDisabled;	let finish = => {		missing.forEach(name => { if (!blueLinks[name]) return; blueLinks[name].attr({				class: 'new',				href: mw.util.getUrl(name, { action: 'edit', redlink: 1 })			}); delete blueLinks[name]; });		setDisabledAll(false);		$status.text(`Found ${count} move${count === 1 ? '' : 's'} in ${revCount.toLocaleString} revisions${isComplete ? '' : (ascending ? ' until ' : ' since ') + lastDate}${count ? ':' : ''}`);		if (isComplete) {			button.setLabel(count ? 'No more results' : 'No results').setDisabled(true);		} else {			if (++i >= 4 || count - lastCount) {				button.setLabel('Continue');				i = 0;			} else {				loadRevs;				return;			}			lastCount = count;		}		prevLabel = button.getLabel;		prevDisabled = button.isDisabled;		if ($table) {			if ($table.hasClass('jquery-tablesorter'))				$table.remove.insertAfter($trail);			$table.tablesorter;		}	};	let reset = => {		rvcontinue = undefined;		isComplete = false;		lastRev = undefined;		lastDate = undefined;		candidates = [];		revCount = 0;		count = 0;		lastCount = 0;		$trail.empty;		if ($table) $table.detach.find('tr:not(:first-child)').remove;		modified = false;	};	let updateForm =  => {		let since = sinceInput.getValue, until = untilInput.getValue;		let invalid = since && until && since > until;		sinceInput.setValidityFlag(!invalid);		invalid = invalid || until && until < '2005-06-25'; untilInput.setValidityFlag(!invalid); if (!prevLabel) { button.setDisabled(invalid); return; }		if (directionInput.getValue === prevDir &&			since === prevSince && until === prevUntil		) { button.setLabel(prevLabel).setDisabled(invalid || prevDisabled); modified = false; } else { button.setLabel('Search').setDisabled(invalid); modified = true; }	};	directionInput.on('change', updateForm); sinceInput.on('change', updateForm); untilInput.on('change', updateForm); button.on('click', loadRevs); $div.append(form.$element, $status, $trail); $(' ').text('Move history').prependTo('#mw-content-text').after($div); });