User:SD0001/GAR-helper.js

This is an old revision of this page, as edited by SD0001 (talk | contribs) at 04:43, 14 July 2020 (tweak summaries, don't notify self). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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);
	}
	loadTaskManager();
});

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

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

	var Window = new Morebits.simpleWindow(600, 450);
	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 typefield = form.append({
		type: 'field',
		label: 'GAR type',
		id: 'gartype_fieldset'
	});

	typefield.append({
		type: 'radio',
		name: 'gartype',
		list: [
			{
				label: 'Community reassessment',
				value: 'garc',
				checked: true
			},
			{
				label: 'Individual reassessment',
				value: 'gari'
			}
		]
	});

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

	field.append({
		type: 'checkbox',
		list: [
			{
				label: 'Notify article creator',
				value: 'notify',
				name: 'notify',
				tooltip: "A notification template will be placed on the creator's talk page if this is checked.",
				checked: true
			}
		]
	});

	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.'
	});

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

	var result = form.render();
	Window.setContent(result);
	Window.display();

};

gar.evaluate = function(e) {
	var form = e.target;
	gar.params = {
		type: form.gartype.value,
		notify: form.notify.checked,
		reason: form.reason.value
	};
	gar.garc = gar.params.type === 'garc';
	gar.gari = gar.params.type === 'gari';

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

	var title_obj = new mw.Title.newFromText(Morebits.pageNameNorm);
	gar.title = title_obj.getSubjectPage().toText();
	gar.talktitle = title_obj.getTalkPage().toText();

	gar.usersToNotify = [];

	var tm = new Morebits.taskManagerXXX();
	tm.add(gar.tasks.getNumber, []);
	tm.add(gar.tasks.createNomPage, [ gar.tasks.getNumber ]);
	tm.add(gar.tasks.editTalkPage, [ gar.tasks.createNomPage ]);
	tm.add(gar.tasks.getCreator, []);
	tm.add(gar.tasks.getOtherEditors, []);
	tm.add(gar.tasks.notify, [ gar.tasks.getCreator, 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 + '|' + (gar.garc ? 'GAR' : '') + 'page=' + gar.num + '}} ~~~~') ], 'warn');
	});

};

gar.tasks = {

	getNumber: function() {
		var def = $.Deferred();
		var query = {
			action: 'query',
			format: 'json',
			formatversion: '2',
			list: 'allpages',
		};
		if (gar.garc) {
			query.apnamespace = '4', // WP
			query.apprefix = 'Good article reassessment/' + gar.title + '/';
		} else {
			query.apnamespace = '1', // Talk
			query.apprefix = gar.title + '/GA'; // name without namespace
		}
		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
			def.resolve();
		}, def.reject);
		return def;
	},

	createNomPage: function() {
		var def = $.Deferred();
		var appendtext;
		if (gar.garc) {
			gar.garpage = 'Wikipedia:Good article reassessment/' + gar.title + '/' + gar.num;
			appendtext =
				'{{subst:GAR/header}}\n' +
				gar.params.reason;
		} else {
			gar.garpage = gar.talktitle + '/GA' + gar.num;
			appendtext =
				'==GA Reassessment==\n' +
				'{{subst:GAR/subst|{{subst:PAGENAME}}}}\n' +
				gar.params.reason;
		}
		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();
		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:
			if (gar.garc) {
				text += '\n\n==GA Reassessment==\n' +
					'{{' + gar.garpage + '}}';
			} else {
				text += '\n\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;
	},

	getOtherEditors: function() {
		var def = $.Deferred();
		var query = {
			"action": "query",
			"format": "json",
			"formatversion": "2",
			"list": "allpages",
		};
		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();

		$.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 lookupsDone = 0;
			pages.forEach(function(page) {
				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());
					if (++lookupsDone === pages.length) { // Hack: #911
						def.resolve();
					}
				});
			});
		});
		return def;
	},

	notify: function() {
		var def = $.Deferred();
		var users = Morebits.array.uniq(gar.usersToNotify).filter(user => {
			return user !== mw.config.get('wgUserName');
		});
		var appendtext = '\n\n' +
			(gar.garc ?
			'{{subst:GARMessage|' + gar.title + '|GARpage=' + gar.num + '}} ~~~~' :
			'{{subst:GARMessage|' + gar.title + '|page=' + gar.num + '}} ~~~~');
		var editsummary = '[[' + gar.title + ']] listed for good article reassessment' + gar.advert;
		var notificationsDone = 0;
		users.forEach(function(user, idx) {
			var usertalk = new Morebits.wiki.page('User talk:' + user, 'Notifying ' + user);
			usertalk.setAppendText(appendtext);
			usertalk.setEditSummary(editsummary);
			usertalk.append(function() {
				if (++notificationsDone === users.length) { // Hack: till .append() is promisified (#911)
					def.resolve();
				}
			});
		});
		return def;
	}

};

function loadTaskManager() {
	Morebits.taskManagerXXX = function() { // named so to avoid naming conflict with real one
		this.taskDependencyMap = new Map();
		this.deferreds = new Map();
		this.allDeferreds = []; // Hack: IE doesn't support Map.prototype.values

		/**
		 * Register a task along with its dependencies (tasks which should have
		 * finished execution before we can begin this one).
		 * Each task is a function that must return a promise.
		 * @param {function} func - a task
		 * @param {function[]} deps - its dependencies
		 */
		this.add = function(func, deps) {
			this.taskDependencyMap.set(func, deps);
			var deferred = $.Deferred();
			this.deferreds.set(func, deferred);
			this.allDeferreds.push(deferred);
		};

		/**
		 * Run all the tasks in correct sequence. Multiple tasks may be run at once.
		 */
		this.execute = function() {
			var self = this; // proxy for `this` for use inside functions where `this` is something else
			this.taskDependencyMap.forEach(function(deps, task) {
				var depsPromisesArray = deps.map(function(dep) {
					return self.deferreds.get(dep);
				});

				$.when.apply(null, depsPromisesArray).then(function() {
					console.log('Executing ' + task.name);
					task().then(function() {
						console.log(task.name + ' is resolved');
						self.deferreds.get(task).resolve();
					});
				});

			});

			return $.when.apply(null, this.allDeferreds); // resolved when eveything is done!
		};
	};
}

/* </nowiki> */