Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/**
 * Script to easily make Good Article Reassessment (GAR)
 * nominations.
 */

/* <nowiki> */
/* jshint maxerr: 999 */

var gar = {};
window.gar = gar;

$.when(
	mw.loader.using('ext.gadget.morebits'),
	$.ready
).then(function() {
	if ([0, 1].indexOf(mw.config.get('wgNamespaceNumber')) !== -1) {
		mw.util.addPortletLink('p-cactions', '#', 'GAR', 'gar-portlet', 'Nominate for GAR');
		$('#gar-portlet').click(gar.callback);
	}
});

gar.advert = ' ([[User:SD0001/GAR-helper|GAR-helper]])';

gar.callback = function garCallback(e) {
	if (e) e.preventDefault();

	var Window = new Morebits.simpleWindow(600, 600);
	Window.setTitle( "Nominate article for GAR" );
	Window.setScriptName('GAR-helper');
	Window.addFooterLink('GAR instructions', 'WP:GAR');

	var form = new Morebits.quickForm(gar.evaluate);

	mw.util.addCSS('.quickform * { font-size: 12px; }');

	var field = form.append({
		type: 'field',
		label: 'Details',
		name: 'details'
	});

	field.append({
		type: 'textarea',
		name: 'reason',
		label: 'Rationale:',
		style: 'height: 130px',
		tooltip: 'You can expand the rationale after the page has been saved as well.'
	});

	var previewlink = Morebits.htmlNode('a', 'Preview');
	previewlink.style.cursor = "pointer";
	$(previewlink).click(function() {
		// |result| is defined below
		gar.preview(result);
	});
	form.append( { type: 'div', id: 'garpreview', label: [ previewlink ] } );
	form.append( { type: 'div', id: 'gar-previewbox', style: 'display: none' } );

	form.append({ type: 'submit', label: 'Submit' });

	var result = form.render();
	Window.setContent(result);
	Window.display();
	gar.previewbox = document.getElementById('gar-previewbox');
	result.previewer = new Morebits.wiki.preview(gar.previewbox);

};

gar.initParams = function (form) {
	gar.params = {
		reason: form.reason.value
	};
	var title_obj = mw.Title.newFromText(Morebits.pageNameNorm);
	gar.title = title_obj.getSubjectPage().toText();
	gar.talktitle = title_obj.getTalkPage().toText();

	gar.usersToNotify = [];
};

gar.preview = function(form) {
	gar.initParams(form);

	Morebits.status.init(gar.previewbox);
	$(gar.previewbox).show();

	var tm = new Morebits.taskManager();
	tm.add(gar.tasks.getNumber, []);
	tm.add(gar.tasks.getCreator, []);
	tm.add(gar.tasks.getLastGanNominator, []);
	tm.add(gar.tasks.getOtherEditors, []);
	tm.execute().then(function() {
		gar.garpage = 'Wikipedia:Good article reassessment/' + gar.title + '/' + gar.num;

		var previewText =
			'<i>Preview of ' + gar.garpage + ':</i>\n'
			+ '----\n'
			+ gar.tasks.getNomPageText()
			+ '\n----\n'
			+ '<i>Notifications will be automatically delivered to the following pages. Uncheck to prevent notification:</i>\n\n'
			+ (gar.tasks.getNotificationTargets().length > 0 ?
				gar.tasks.getNotificationTargets()
					.map(p => '*<span class=gar-user-to-notify>[[User talk:' + p + ']]</span>').join('\n') :
				'*<i>None</i>');

		form.previewer.beginRender(previewText, gar.garpage).then(function () {
			$(gar.previewbox).find('.gar-user-to-notify').each(function (_, e) {
				$(e).prepend(
					$('<input>')
						.attr('type', 'checkbox')
						.attr('value', mw.Title.newFromText($(e).text()).getMainText())
						.prop('checked', true)
				);
			});
		});
	}, function () {
		form.previewer.beginRender("Unable to render preview", gar.garpage);
	});
};

gar.evaluate = function(e) {
	var form = e.target;
	gar.initParams(form);

	gar.usersNotToNotify = new Set($(gar.previewbox)
		.find('.gar-user-to-notify input:not(:checked)')
		.get()
		.map(e => e.value));

	Morebits.simpleWindow.setButtonsEnabled(false);
	Morebits.status.init(form);

	var tm = new Morebits.taskManager();
	tm.add(gar.tasks.getNumber, []);
	tm.add(gar.tasks.editTalkPage, [ gar.tasks.getNumber ]);
	tm.add(gar.tasks.createNomPage, [ gar.tasks.editTalkPage ], function () {
		Morebits.status.printUserText(gar.tasks.getNomPageText(),
			'Your nomination text is provided below, with which you can manually create the page [[' + gar.garpage + ']].');
	});
	tm.add(gar.tasks.getCreator, []);
	tm.add(gar.tasks.getLastGanNominator, []);
	tm.add(gar.tasks.getOtherEditors, []);
	tm.add(gar.tasks.notify, [ gar.tasks.getCreator, gar.tasks.getLastGanNominator, gar.tasks.getOtherEditors ]);

	tm.execute().then(function() {
		Morebits.status.actionCompleted('Nomination completed.');
		new Morebits.status('Notifications', ['The script has notified the the reviewer and page creator. ' +
		'Please consider manually notifying other involved editors and WikiProjects using ',
			Morebits.htmlNode('code', '{{subst:GARMessage|' + gar.title + '|GARpage=' + gar.num + '}} ~~~~') ], 'warn');
	});

};

gar.tasks = {

	getNumber: function() {
		var def = $.Deferred();
		var query = {
			action: 'query',
			format: 'json',
			formatversion: '2',
			list: 'allpages',
			apnamespace: '4', // WP
			apprefix: 'Good article reassessment/' + gar.title + '/',
			aplimit: 'max'
		};
		var api = new Morebits.wiki.api('Getting numbering', query);
		api.post().then(function(apiobj) {
			var pages = apiobj.response.query.allpages;
			gar.num = pages.length + 1; // HACK
			apiobj.getStatusElement().info('Next number is ' + gar.num);
			def.resolve();
		}, def.reject);
		return def;
	},

	getNomPageText: function() {
		return '{{subst:GAR/header}}\n' + gar.params.reason.trim() +
			(gar.params.reason.trim().endsWith('~~~~') ? '' : ' ~~~~');
	},

	createNomPage: function() {
		var def = $.Deferred();
		var appendtext = gar.tasks.getNomPageText();
		var pageobj = new Morebits.wiki.page(gar.garpage, 'Creating nomination page');
		pageobj.setAppendText(appendtext);
		pageobj.setCreateOption('createonly');
		pageobj.setWatchlist(true);
		pageobj.setEditSummary('Creating GAR nomination page' + gar.advert);
		pageobj.append(def.resolve, def.reject);
		return def;
	},

	editTalkPage: function() {
		var def = $.Deferred();
		gar.garpage = 'Wikipedia:Good article reassessment/' + gar.title + '/' + gar.num;
		var talkpage = new Morebits.wiki.page(gar.talktitle, 'Editing talk page');
		talkpage.load(function(talkpage) {
			var text = talkpage.getPageText();
			// prepend tag:
			text = '{{subst:GAR}}\n' + text;
			// append transclusion:
			text += '\n\n==GA Reassessment==\n' +
				'{{' + gar.garpage + '}}';
			talkpage.setPageText(text);
			talkpage.setEditSummary('Nominating for good article reassessment' + gar.advert);
			talkpage.save(def.resolve, def.reject);
		}, def.reject);
		return def;
	},

	getCreator: function() {
		var def = $.Deferred();
		var page = new Morebits.wiki.page(gar.title, 'Fetching article creator');
		page.setLookupNonRedirectCreator(true);
		page.lookupCreation(function() {
			var author = page.getCreator();
			page.getStatusElement().info('Got ' + author);
			gar.usersToNotify.push(author);
			def.resolve();
		}, def.reject);
		return def;
	},

	getLastGanNominator: function () {
		return $.get({
			url: 'https://sdzerobot.toolforge.org/gans/credit/' + mw.util.wikiUrlencode(gar.title) + '?raw=1',
			timeout: 4000
		}).then(function (nom) {
			if (nom === '<Unknown>') {
				return;
			}
			gar.usersToNotify.push(nom);
		}).catch(function () {
			return $.Deferred().resolve(); // might be some server issue; don't fail
		});
	},

	getOtherEditors: function() {
		var query = {
			"action": "query",
			"format": "json",
			"formatversion": "2",
			"list": "allpages",
			"aplimit": "max"
		};
		var api_gan = new Morebits.wiki.api('Getting previous GAN reviewers', $.extend(query, {
			"apprefix": gar.title + '/GA',
			"apnamespace": "1"
		})).post();
		var api_gar = new Morebits.wiki.api('Getting previous GAR nominators', $.extend(query, {
			"apprefix": 'Good article reassessment/' + gar.title + '/',
			"apnamespace": "4"
		})).post();

		return $.when(api_gar, api_gan).then(function(result_gan, result_gar) {
			var pages = result_gar.response.query.allpages
				.concat(result_gan.response.query.allpages);
			var defs = pages.map(function(page) {
				var def = $.Deferred();
				var p = new Morebits.wiki.page(page.title, 'Looking up creator of ' + page.title);
				p.lookupCreation(function() {
					gar.usersToNotify.push(p.getCreator());
					p.getStatusElement().info('found ' + p.getCreator());
					def.resolve();
				}, def.reject);
				return def;
			});
			return $.when.apply($, defs);
		});
	},

	getNotificationTargets: function() {
		return Morebits.array.uniq(gar.usersToNotify).filter(function (user) {
			return user !== mw.config.get('wgUserName');
		});
	},

	notify: function() {
		var usersToNotify = gar.tasks.getNotificationTargets().filter(name => !gar.usersNotToNotify.has(name));
		var appendtext = '\n\n' +
			'{{subst:GARMessage|' + gar.title + '|GARpage=' + gar.num + '}} ~~~~';
		var editsummary = '[[' + gar.title + ']] listed for good article reassessment' + gar.advert;
		var defs = usersToNotify.map(function(user) {
			var def = $.Deferred();
			var usertalk = new Morebits.wiki.page('User talk:' + user, 'Notifying ' + user);
			usertalk.setAppendText(appendtext);
			usertalk.setEditSummary(editsummary);
			usertalk.append(def.resolve, def.reject);
			return def;
		});
		return $.when.apply($, defs);
	}

};
/* </nowiki> */