User:SD0001/AFC-submit-wizard.js

/** * MediaWiki:AFC-submit-wizard.js * * JavaScript used for submitting drafts to AfC. * Used on FAMEPedia:Articles for creation/Submitting. * Loaded via Snippets/Load JS and CSS by URL. * * Author: User:SD0001 * Licence: MIT */

/* jshint maxerr: 999 */ /* globals mw, $, OO */ /* */

$.when(	$.ready,	mw.loader.using([ 'mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.widgets', 'oojs-ui-core', 'oojs-ui-widgets' ]) ).then(function {

if (mw.config.get('wgPageName') !== 'FAMEPedia:Articles_for_creation/Submitting') { return; }	if (mw.util.getParamValue('withJS') !== 'MediaWiki:AFC-submit-wizard.js') { return; }

$('#firstHeading').text('Submitting your draft ...'); document.title = 'Submitting your draft ...';

var apiOptions = { parameters: { format: 'json', formatversion: '2' },		ajax: { headers: { 'Api-User-Agent': 'MediaWiki:AFC-submit-wizard.js' }		}	};

// Two different API objects so that aborts on the lookupApi don't stop the final // evaluate process var lookupApi = new mw.Api(apiOptions), submitApi = new mw.Api(apiOptions);

var draftLayout, talkTagsLayout, shortdescLayout, topicsLayout, submitLayout, draftInput, talkTagsInput, shortdescInput, topicsInput, submitButton, mainStatusLayout, mainStatusArea, talkStatusLayout, talkStatusArea, rawClassLayout, rawClass;

// global var pagetext, talktext, oresTopics;

// Create the UI	var fieldset = new OO.ui.FieldsetLayout({		label: 'Submit your draft for review at Articles for Creation (AfC)',		classes: ['container'],		items: [			draftLayout = new OO.ui.FieldLayout(draftInput = new mw.widgets.TitleInputWidget({				value: (mw.util.getParamValue('draft') || '').replace(/_/g, ' '),				placeholder: 'Enter the draft title, begins with "Draft:" or "User:"'			}), { label: 'Draft title', align: 'top', help: 'This should be pre-filled if you clicked the link while on the draft page', helpInline: true }),

rawClassLayout = new OO.ui.FieldLayout(rawClass = new OO.ui.RadioSelectInputWidget, {				label: 'Choose the most appropriate category',				help: 'For biographies about scholars, choose one of the two biography categories rather than one associated to their field',				align: 'inline'			}),

shortdescLayout = new OO.ui.FieldLayout(shortdescInput = new OO.ui.TextInputWidget({ placeholder: 'Briefly describe the subject in 2–5 words (eg. "British astronomer", "Cricket stadium in India")', maxLength: 100 }), {				label: 'Short description',				align: 'top',				help: 'Try not to exceed 40 characters',				helpInline: true			}),

talkTagsLayout = new OO.ui.FieldLayout(talkTagsInput = new OO.ui.MenuTagMultiselectWidget({ placeholder: 'Start typing to search for tags ...', tagLimit: 10, autocomplete: false, $overlay: $(' ').addClass('projectTagOverlay').css({					'position': 'absolute',					'z-index': '110'				}).appendTo('body') }), {				label: 'WikiProject classification tags',				align: 'top',				help: 'Adding the 1–4 most applicable WikiProjects is plenty. For example, if you add the Physics tag, you do not need to also add the Science tag.',				helpInline: true			}),

// This is shown only if the ORES topic lookup fails, or is inconclusive topicsLayout = new OO.ui.FieldLayout(topicsInput = new OO.ui.MenuTagMultiselectWidget({ placeholder: 'Start typing to search for topics ...', tagLimit: 10, autocomplete: false, // XXX: doesn't seem to work options: ["biography", "women", "food-and-drink", "internet-culture", "linguistics", "literature", "books", "entertainment", "films", "media", "music", "radio", "software", "television", "video-games", "performing-arts", "philosophy-and-religion", "sports", "architecture", "comics-and-anime", "fashion", "visual-arts", "geographical", "africa", "central-africa", "eastern-africa", "northern-africa", "southern-africa", "western-africa", "central-america", "north-america", "south-america", "asia", "central-asia", "east-asia", "north-asia", "south-asia", "southeast-asia", "west-asia", "eastern-europe", "europe", "northern-europe", "southern-europe", "western-europe", "oceania", "business-and-economics", "education", "history", "military-and-warfare", "politics-and-government", "society", "transportation", "biology", "chemistry", "computing", "earth-and-environment", "engineering", "libraries-and-information", "mathematics", "medicine-and-health", "physics", "stem", "space", "technology"].map(function (e) { return { data: e,						label: e					}; })			}), {				label: 'Topic classifiers', align: 'top', help: 'Pick the topic areas that are relevant', helpInline: true }),

submitLayout = new OO.ui.FieldLayout(submitButton = new OO.ui.ButtonWidget({ label: 'Submit', flags: ['progressive', 'primary'], })),

]	});

// Load a JSON page from the wiki function getJSONPage(page) { return $.getJSON('https://en.famepedia.org/w/index.php?title=' + encodeURIComponent(page) + '&action=raw&ctype=text/json'); }

var topicOptionsLoaded = $.Deferred; getJSONPage('FAMEPedia:WikiProject Articles for creation/AfC topic map.json').then(function(optionsJson) {		var options = [];		$.each(optionsJson, function(code, info) { options.push({				label: info.label,				data: code			}); });		rawClass.setOptions(options);		rawClass.setValue('o');

// put allowed option codes in promise resolution: topicOptionsLoaded.resolve(options.map(function(op) { return op.data; }));	});

topicsLayout.toggle(false);

var asUser = mw.util.getParamValue('username'); if (asUser && asUser !== mw.config.get('wgUserName')) { fieldset.addItems([			new OO.ui.FieldLayout(new OO.ui.MessageWidget({				type: 'notice',				inline: true,				label: 'Submitting as User:' + asUser			}))		], /* position */ 6); }

$('.mw-ui-button').parent.replaceWith(fieldset.$element);

// populate talk page tags for multi-select widget var talkTagOptionsLoaded = $.Deferred; getJSONPage('FAMEPedia:WikiProject Articles for creation/WikiProject templates.json').then(function (data) {		talkTagsInput.addOptions(Object.keys(data).map(function (k) {			return {				data: data[k],				label: k			};		}));		talkTagOptionsLoaded.resolve;	});

// Get mapping of infoboxes with relevant WikiProjects var ibxmapLoaded = $.Deferred; getJSONPage('FAMEPedia:WikiProject Articles for creation/Infobox WikiProject map.json').then(function (data) {		ibxmapLoaded.resolve(data);	});

submitButton.$element.on('click', evaluate); draftInput.on('change', onDraftInputChange);

if (mw.util.getParamValue('draft')) { onDraftInputChange; }

// The default font size in monobook and modern are too small at 10px mw.util.addCSS('.skin-modern .projectTagOverlay, .skin-monobook .projectTagOverlay { font-size: 130%; }');

function onDraftInputChange { lookupApi.abort; // abort older API requests

var drafttitle = draftInput.getValue.trim; if (!drafttitle) { // empty return; }

console.log('draft input changed: "' + draftInput.getValue + '"');

// re-initialize draftLayout.setErrors([]); draftLayout.setWarnings([]); oresTopics = null; talktext = null; pagetext = null;

talkTagOptionsLoaded.then(function {			talkTagsInput.setValue([]);		});

lookupApi.get({			"action": "query",			"prop": "revisions|description|info",			"titles": drafttitle,			"rvprop": "content",			"rvslots": "main"		}).then(function (json) {			console.log(json);			var page = json.query.pages[0];			var preNormalizedTitle = json.query.normalized && json.query.normalized[0] &&				json.query.normalized[0].from;			console.log('page.title: "' + page.title + '"');			if (draftInput.getValue !== (preNormalizedTitle || page.title)) {				return; // user must have changed the title already			}			if (!page || page.invalid) {				draftLayout.setErrors(['Please check draft title. This title is invalid.']);				return;			}			if (page.missing) {				draftLayout.setErrors(['Please check draft title. No such draft exists.']);				return;			}			pagetext = page.revisions[0].slots.main.content;

// Show no refs warning if (!/ /.test(pagetext) && !/\{\{[Ss]fn\}\}/.test(pagetext)) { draftLayout.setWarnings([					new OO.ui.HtmlSnippet('This draft doesn\'t appear to contain any references. Please add references, without this it will almost certainly be declined. See help on adding references.')				]); }			// set main category var topicMatch = pagetext.match(/\{\{AFC topic\|(.*?)\}\}/); if (topicMatch) { topicOptionsLoaded.then(function(allowedCodes) {					var topic = topicMatch[1];					console.log(topic);					console.log(allowedCodes);					// if the code found in the template is an invalid one, keep the default to "other",					// rather than the first item in the list					if (allowedCodes.indexOf(topic) !== -1) {						rawClass.setValue(topic);					} else {						rawClass.setValue('o');					}				}); } else { rawClass.setValue('o'); }

// set shortdesc in form shortdescInput.setValue(page.description || '');

// guess wikiproject tags from infoboxes on the page $.when(ibxmapLoaded, talkTagOptionsLoaded).then(function (ibxmap) {				var infoboxRgx = /\{\{([Ii]nfobox [^|}]*)/g,					wikiprojects = [],					match;				while (match = infoboxRgx.exec(pagetext)) { // jshint ignore:line					var ibx = match[1].trim;					if (ibxmap[ibx]) {						wikiprojects = wikiprojects.concat(ibxmap[ibx]);					}				}				console.log('wikiprojects from infobox: ', wikiprojects);				console.log('setValue1:', talkTagsInput.getValue.concat(wikiprojects));				talkTagsInput.setValue(talkTagsInput.getValue.concat(wikiprojects));			});

// fill ORES topics getOresTopics(page.lastrevid).then(function (topics) {				console.log('ORES topics: ', topics);				if (!topics || !topics.length) { // unexpected API response or API returns unsorted					topicsLayout.toggle(true);				} else {					topicsLayout.toggle(false);					oresTopics = topics;				}			}, function {				topicsLayout.toggle(true);			});

});

var titleObj = mw.Title.newFromText(drafttitle); if (!titleObj || titleObj.isTalkPage) { return; }		var talkpagename = titleObj.getTalkPage.toText; console.log(talkpagename); lookupApi.get({			"action": "query",			"prop": "revisions",			"titles": talkpagename,			"rvprop": "content",			"rvslots": "main",		}).then(function (json) {			var talkpage = json.query.pages[0];			if (!talkpage || talkpage.missing) {				return;			}			talktext = talkpage.revisions[0].slots.main.content;			console.log(talktext);

var existingWikiProjects = extractWikiProjectTagsFromText(talktext); var existingTags = existingWikiProjects.map(function (e) {				return e.name;			}); talkTagOptionsLoaded.then(function {				console.log('setValue2:', talkTagsInput.getValue.concat(existingTags));				talkTagsInput.setValue(talkTagsInput.getValue.concat(existingTags));			});

console.log(existingTags);

});

}

function getOresTopics(revid) { return $.get('https://ores.wikimedia.org/v3/scores/enwiki/?models=drafttopic&revids=' + revid).then(function (json) {

// null is returned if at any point something in the API output is unexpected // ES2020 has optional chaining, but of course on MediaWiki we're still stuck with ES5 return json && json.enwiki && json.enwiki.scores && json.enwiki.scores[revid] && json.enwiki.scores[revid].drafttopic && json.enwiki.scores[revid].drafttopic.score && (json.enwiki.scores[revid].drafttopic.score.prediction instanceof Array) && json.enwiki.scores[revid].drafttopic.score.prediction.map(function (topic, idx, topics) {					// Remove Asia.Asia* if Asia.South-Asia is present (example)					if (topic.slice(-1) === '*') {						var metatopic = topic.split('.').slice(0, -1).join('.');						for (var i = 0; i < topics.length; i++) {							if (topics[i] !== topic && topics[i].startsWith(metatopic)) {								return;							}						}						return metatopic.split('.').pop;					}					return topic.split('.').pop;				}) .filter(function (e) {					return e; // filter out undefined from above				}) .map(function (topic) {					// convert topic string to normalised form					return topic						.replace(/[A-Z]/g, function (match) { return match[0].toLowerCase; })						.replace(/ /g, '-')						.replace(/&/g, 'and');				}); });	}

function extractWikiProjectTagsFromText(text) { if (!text) { return []; }

// this is best-effort, no guaranteed accuracy var existingTags = []; var rgx = /\{\{(WikiProject [^|}]*).*?\}\}/g; var match; while (match = rgx.exec(text)) { // jshint ignore:line var tag = match[1].trim; if (tag === 'WikiProject banner shell') { continue; }			existingTags.push({				wikitext: match[0],				name: tag			}); }		return existingTags; }

function evaluate {

if (!mainStatusLayout || !mainStatusLayout.isElementAttached) { fieldset.addItems([				mainStatusLayout = new OO.ui.FieldLayout(mainStatusArea = new OO.ui.MessageWidget({					type: 'notice',					label: 'Processing ...'				}))			]); }

var draft = draftInput.getValue; console.log(draft);

submitApi.get({			"action": "query",			"prop": "revisions",			"titles": draft,			"rvprop": "content",			"rvslots": "main"		}).then(function (json) {			var page = json.query.pages[0];			if (!page || page.invalid || page.missing) {				draftLayout.setErrors(['Please check draft title. No such draft exists.']);				fieldset.removeItems([mainStatusLayout]);				return;			}			var text = page.revisions[0].slots.main.content;

var header = '';

// add shortdesc if (shortdescInput.getValue) { text = text.replace(/\{\{[Ss]hort description\|.*?\}\}\n*/g, ''); header += '\n'; }

// draft topics console.log(topicsInput);

if (topicsLayout.isVisible) { oresTopics = topicsInput.getValue; }			if (oresTopics.length) { text = text.replace(/\{\{[Dd]raft topics\|.*?\}\}\n*/g, ''); header += '\n'; }

text = text.replace(/\{\{AFC topic\|(.*?)\}\}/g, ''); header += '\n';

// put AFC submission template header += '' + (mw.util.getParamValue('username')\n';

// insert everything to the top text = header + text;

console.log(text);

mainStatusArea.setType('notice'); mainStatusArea.setLabel('Saving draft page ...');

// saving draft page submitApi.postWithEditToken({				"action": "edit",				"title": draft,				"text": text,				"summary": 'Submitting using AFC-submit-wizard)' }).then(function (data) { if (data.edit && data.edit.result === 'Success') { mainStatusArea.setType('success'); mainStatusArea.setLabel('Submission succeeded. Redirecting you to the draft page ...');

setTimeout(function {						location.href = mw.util.getUrl(draft);					}, 1000); } else { return $.Deferred.reject('unexpected-result'); }			}).catch(function (err) { mainStatusArea.setType('error'); mainStatusArea.setLabel('An error occurred (' + err + '). Please try again or refer to the help desk.'); });

fieldset.addItems([				talkStatusLayout = new OO.ui.FieldLayout(talkStatusArea = new OO.ui.MessageWidget({					type: 'notice',					label: 'Saving draft talk page ...'				}))			]);

// Process text of the talk page var alreadyExistingWikiProjects = extractWikiProjectTagsFromText(talktext); var alreadyExistingTags = alreadyExistingWikiProjects.map(function (e) {				return e.name;			}); var tagsToAdd = talkTagsInput.getValue.filter(function (tag) {				return alreadyExistingTags.indexOf(tag) === -1;			}); var tagsToRemove = alreadyExistingTags.filter(function (tag) {				return talkTagsInput.getValue.indexOf(tag) === -1;			});

tagsToRemove.forEach(function (tag) {				talktext = talktext.replace(new RegExp('\\{\\{\\s*' + tag + '\\s*(\\|.*?)?\\}\\}\\n?'), '');			});

var tagsToAddText = tagsToAdd.map(function (tag) {				return ;			}).join('\n') + (tagsToAdd.length ? '\n' : );

talktext = tagsToAddText + (talktext || '');

submitApi.postWithEditToken({				"action": "edit",				"title": new mw.Title(draft).getTalkPage.toText,				"text": talktext,				"summary": 'Adding WikiProject tags using AFC-submit-wizard)' }).then(function (data) { if (data.edit && data.edit.result === 'Success') { talkStatusArea.setType('success'); talkStatusArea.setLabel('Successfully added WikiProject tags to talk page'); } else { return $.Deferred.reject('unexpected-result'); }			}).catch(function (err) { talkStatusArea.setType('error'); talkStatusArea.setLabel('An error occurred in editing the talk page (' + err + ').'); });

}).catch(function (err) { mainStatusArea.setType('error'); mainStatusArea.setLabel('An error occurred (' + err + '). Please try again or refer to the help desk.'); });

}

});

/* */