User:Ahecht/Scripts/draft-sorter.js

Source: Wikipedia, the free encyclopedia.
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.
//jshint maxerr:512
//jshint esnext:false
//jshint esversion:8

//Based on [[User:Enterprisey/draft-sorter.js]] <nowiki>
( function ( $, mw ) { mw.loader.using( ["mediawiki.api", "jquery.chosen", "oojs-ui-core"], function () {
	mw.loader.load( "mediawiki.ui.input", "text/css" );

	if ( mw.config.get( "wgNamespaceNumber" ) !== 118 ) { 
		if ( mw.util.getParamValue('draftsorttrigger') ) {
			// "Next draft" was clicked, but we ended up on a non-draft page
			nextDraft();
			return;
		} else {
			return;
		}
	}

	var portletLink = mw.util.addPortletLink("p-cactions", "#", "Sort draft", "pt-draftsort", "Manage WikiProject tags");
	$( portletLink ).click( function ( e ) {
		e.preventDefault();

		// If it's already there, don't duplicate
		if ( $( "#draft-sorter-wrapper" ).length ) { return; }
		
		// Configure defaults
		//var templateCache = mw.config.get("wgFormattedNamespaces")[2]+":"+mw.config.get("wgUserName")+"/Scripts/draft-sorter.json";
		var templateCache = "Wikipedia:WikiProject Articles for creation/WikiProject templates.json";
		
		// Define the form
		var form = $( "<div>" )
			.attr( "id", "draft-sorter-wrapper" )
			.css( { "background-image": "url(https://upload.wikimedia.org/wikipedia/commons/e/e2/OOjs_UI_icon_tag-ltr-progressive.svg)",
					"background-repeat": "no-repeat",
					"background-position-y": "center",
					"background-size": "50px",
					"min-height": "50px",
					"margin": "1em auto",
					"border": "thin solid #BBB",
					"padding": "0.5em 50px",
					"display": "inline-block",
					"border-radius": "0.25em"
			} ).append( $( "<span>" )
				.text( "Loading form..." )
				.css( "color", "gray" )
			);
		// Add the form to the page
		form.insertAfter( "#contentSub" );

		var select = $( "<select>" )
			.attr( "id", "draft-sorter-form" )
			.attr( "multiple", "multiple" );
		
		var submitButton = new OO.ui.ButtonWidget ()
				.setLabel( "Submit" )
				.setFlags( [ 'primary', 'progressive' ] )
				.on("click", function ( e ) { submit(); } );
		
		var cancelButton = new OO.ui.ButtonWidget ()
				.setLabel( "Cancel" )
				.setFlags( ["destructive"] )
				.on("click", function( e ) {
					$( "#draft-sorter-wrapper" ).remove();
					window.location.replace( window.location.href.replace("draftsorttrigger=y","") );
				} );

		var nextButton = new OO.ui.ButtonWidget ()
			.setIcon( "next" )
			.setLabel( "Skip" )
			.on("click", function ( e ) { nextDraft(); } );

		// Determine what templates are already on the talk page
		var existingProjects = [];
		var wikiprojects = {};

		new mw.Api().get( {
				action: "query",
				titles: "Draft talk:" + mw.config.get( "wgTitle" ),
				generator: "templates",
				redirects: "1",
				gtllimit: "max",
		} ).done (function (data) {
			if (data && data.query && data.query.pages) {
				$.each(data.query.pages, function (i) {
					var item = data.query.pages[i].title.match(/^Template:(WikiProject\s[^\/]*)$/i);
					if (item && item[1] && item[1] != "WikiProject banner shell") {
						existingProjects.push(item[1]);
					}
				} );
			}
			console.log( "Project templates found on talk page: ");
			console.log( existingProjects );
			fetchJSONList(templateCache).then( (cachedList) => {
				wikiprojects = cachedList;
				constructForm();
			} );
		} ).fail (function() {
			console.log("Retrieving project templates from talk page failed.");
			fetchJSONList(templateCache).then( (cachedList) => {
				wikiprojects = cachedList;
				constructForm();
			} );
		});
		
		predicts = [];
		
		async function fetchJSONList(listName) {
			var parsedList = {}, listData;
			
			var query = {
				action:'parse',
				prop:'wikitext',
				page: listName,
				formatversion: '2',
				origin: '*'
			};
			
			try {
				listData = await new mw.Api().get( query );
			} catch (jsonerror) {
				console.warn("Unable to fetch contents of " + listName + ":");
				console.log(jsonerror);
			}
			
			if (listData && listData.parse && listData.parse.wikitext) {
				try {
					parsedList = JSON.parse(listData.parse.wikitext);
				} catch (jsonerror) {
					console.warn("Error parsing JSON list " + listName + ":");
					console.log(jsonerror);
				}
			}
		
			return parsedList;
		}
		
		function nextDraft() {
			// Special:RandomInCategory isn't random, use toolforge instead
			if (nextButton) {
				nextButton.setLabel( "Loading..." ).setDisabled( true );
			}
			window.location.href = "https://randomincategory.toolforge.org/Pending_AfC_submissions?draftsorttrigger=y&cmnamespace=118&cmtype=page&returntype=subject&server=" + mw.config.get("wgServerName");
		}
		
		function showPredicts() {
			$( "#draft-sorter-status" ).append( "<li>Suggested categories from <a href=\"https://www.mediawiki.org/wiki/ORES#Topic_routing\">ORES</a>:<ul id=\"draft-sorter-suggest\"></ul></li>" );
			predicts.forEach( function(item) { 
				var addLink = $( "<a>" )
					.text("add")
					.click( function() {
						$( select ).val( 
							$( select ).val().concat( [ "WikiProject " + item ] )
						).trigger("chosen:updated");
					} );
				var singularItem = item.replace(/s$/, '');
				if( !existingProjects.includes( "WikiProject " + item ) 
					&& wikiprojects[item]
				) { //Prediction matches a WikiProject and doesn't already exist
					$( "#draft-sorter-suggest" ).append( $( "<li>" )
						.append( item + " (" )
						.append( addLink )
						.append( ")" )
					);
				} else if( singularItem != item
					&& !existingProjects.includes( "WikiProject " + singularItem ) 
					&& wikiprojects[singularItem]
				) { //Singular form of prediction matches a WikiProject and doesn't exist
					addLink.click( function() {
						$( select ).val( 
							$( select ).val().concat( [ "WikiProject " + singularItem ] )
						).trigger("chosen:updated");
					} );
					$( "#draft-sorter-suggest" ).append( $( "<li>" )
						.append( singularItem + " (" )
						.append( addLink )
						.append( ")" )
					);
				} else { //Prediction doesn't match a WikiProject or already exists
					$( "#draft-sorter-suggest" ).append( 
						$( "<li>" ).append( item  )
					);
				}
			} );
			return;
		}
		
		function getPredicts() {
			var lang = mw.config.get("wgServerName").split(".wikipedia.org");
			if (lang.length == 1) return;
			
			const liftWingExternalEndpoint = "https://api.wikimedia.org/service/lw/inference/v1/models/";
			let headers = new Headers({
			    "Content-Type": "application/json",
				"User-Agent": "draft-sorter (https://en.wikipedia.org/wiki/User:Ahecht/Scripts/draft-sorter.js)"
			});
			var revID = mw.config.get( "wgCurRevisionId" );
			var model = (lang[0] == "en") ? "enwiki-drafttopic" : "outlink-topic-model";
			var postBody = JSON.stringify({
				"rev_id":  revID,
				"lang": lang[0],
				"page_title": mw.config.get("wgPageName")
			});

			fetch(liftWingExternalEndpoint + model + ":predict", {
				method: "POST",
				headers: new Headers({
				    "Content-Type": "application/json",
					"User-Agent": "draft-sorter (https://en.wikipedia.org/wiki/User:Ahecht/Scripts/draft-sorter.js)"
					}),
				body: postBody
			}).then(response => response.json()).then(data => {
				var prediction = [];
				var dbName = mw.config.get("wgDBname");
				
				if(data && data[dbName] && data[dbName].scores &&
					data[dbName].scores[revID] &&
					data[dbName].scores[revID].drafttopic &&
					data[dbName].scores[revID].drafttopic.score &&
					data[dbName].scores[revID].drafttopic.score.prediction) {
					prediction = data[dbName].scores[revID].drafttopic.score.prediction;
				} else if (data && data.prediction && data.prediction.results) {
					data.prediction.results.forEach( p => {
						if (p && p.topic) prediction.push(p.topic);
					} );
				} 
				
				if (prediction.length) {
					console.log("Got ORES response! Raw predictions:");
					console.log(prediction);
					
					prediction.forEach( function (item) {
						var last = item.split(".")[item.split(".").length-1];
						var penultimate = item.split(".")[item.split(".").length-2];
						if ( last.substr(-1) == "*" ) {
							// Filter out redundant starred predictions
							if (prediction.find(element => (
								element.split(".")[element.split(".").length-1] != last &&
								element.split(".")[element.split(".").length-2] == penultimate
							) ) ) {
								console.log("Prediction \"" + last + "\" excluded.");
								last = null;
							} else {
								last = penultimate;
							}
						}
						
						if ( wikiprojects[last] ) {
							// WikiProject found, no need to try splitting
							predicts.push(last);
						} else if ( last ) {
							// Can't find wikiProject, try splitting
							var splitLast = last.split(/( & | and )/);
							for (i=0;i<=splitLast.length;i+=2) {
								splitLast[i] = splitLast[i].charAt(0).toUpperCase()
									+ splitLast[i].slice(1);
								predicts.push( splitLast[i] );
							}
						}
					} );
					console.log("Filtered predictions:");
					console.log(predicts);
					showPredicts();
				} else {
					console.warn("Error finding predictions in ORES response:");
					console.warn(data);
				}
			} ).catch( e => console.warn("Error retrieving ORES data: " + e) );

			return;
		}

		// Construct the form
		function constructForm() {
			mw.loader.load( "oojs-ui.styles.icons-movement"); 
			
			Object.keys(wikiprojects).sort().forEach( function(name) {
				select.append( $( "<option>" )
					.attr( "value", wikiprojects[name] )
					.text( name ) );
			} );
			form.hide();
			form.empty();
			form.append( $( "<span>" )
				.text( "Tag WikiProjects: " )
				.css( {
					"font-size": "115%",
					"font-weight": "bold"
				} )
			);
			form.append( select );
			form.append( "&#32;&#32;" );
			form.append( submitButton.$element );
			form.append( cancelButton.$element );
			form.append( nextButton.$element );
			form.append ( $( "<ul>" )
				.attr( "id", "draft-sorter-status" )
			);
			form.show();
			$( select )
				.val( existingProjects )
				.chosen( {"placeholder_text_multiple": "Select some WikiProjects"} )
				.on("change", function(evt, params) { //Make existing projects undeletable
					$( "#draft-sorter-status" ).empty();
					if ( predicts.length > 0 ) { showPredicts(); }
					if ( params.deselected && existingProjects.includes(params.deselected) ) {
						$( select ).val( $( select ).val().concat([params.deselected]) ).trigger("chosen:updated");
						$( "#draft-sorter-status" ).prepend( $( "<li>" )
							.text( "Draft Sorter cannot remove existing WikiProjects." )
							.addClass( "error" )
						);
					}
				} );

			// Add completed form to the page
			$( '#draft-sorter-wrapper' ).replaceWith(form);
			getPredicts();
			return;
		}

		// The submission function
		function submit() {
			$( "#draft-sorter-form" )
				.attr("disabled", true)
				.trigger("chosen:updated");
			submitButton
				.setLabel( "Submitting..." )
				.setDisabled( true );
			cancelButton
				.setLabel ( "Close" );
				
			var newTags = [];

			$( "#draft-sorter-form" ).val().forEach( function (element) {
				if ( !existingProjects.includes(element) ) {
					newTags.push(element);
				}
			} );

			console.log( newTags.length + " new tag(s): " + newTags.join(", ") );
			var statusList = $( "#draft-sorter-status" )
				.html( "<li>Saving " + newTags.length + " new tags.</li>" );
			var showStatus = function ( status ) {
				return $( "<li>" )
					.text( status )
					.appendTo( statusList );
			};
			var newText = "";
			newTags.forEach( function ( element ) {
					newText += "{{" + element + "|importance=|class=draft}}\n";
			} );

			function editTalk(text, prefix) {
				var params = {
					action: "edit",
					title: "Draft talk:" + mw.config.get( "wgTitle" ),
					summary: "Tagging draft: +" + newTags.join(", +") +
						" ([[User:Ahecht/Scripts/draft-sorter|draft-sorter]])",
				};
				params[prefix + "text"] = text;

				new mw.Api().postWithEditToken( params ).done( function ( data ) {
					if ( data && data.edit && data.edit.result && data.edit.result === "Success" ) {
						showStatus( "Edit saved successfully! (" )
							.append( $( "<a>" )
								.text( "reload" )
								.attr( "href", "#" )
								.click( function () {
									window.location.replace( 
										window.location.href.replace("draftsorttrigger=y","")
									);
								} )
							).append( ")" );
						submitButton.setLabel( "Submitted" );
						nextButton.setLabel( "Next draft" ).setFlags( [ 'progressive' ] );
					} else {
						showStatus( "Couldn't save due to error: " + JSON.stringify( data ) );
					}
				} ).fail( function ( error ) {
					showStatus( "Couldn't save due to error: " + JSON.stringify( error ) );
				} );
				return;
			}

			new mw.Api().get( {
				action: "query",
				titles: "Draft talk:" + mw.config.get( 'wgTitle' ),
				prop: "templates",
				tltemplates: "Template:WikiProject_banner_shell"
			} ).done (function (data) {
				var bannerShellUsed = Object.entries(data.query.pages)[0][1].templates;
				if(typeof(bannerShellUsed) == "object" && bannerShellUsed.length > 0) {
					api.get( {
						action: "parse",
						page: "Draft talk:" + mw.config.get( 'wgTitle' ),
						prop: "wikitext",
						section: "0"
					} ).done (function (data) {
						var talkText = data.parse.wikitext["*"];
						if (typeof(talkText) == "string") {
							var pattern = /(\{\{\s*(?:Wiki[ _]?Project[ _]?banners?[ _]?shell(?:\/redirect)?|(?:(?:WP)?[ _]?Banner|(?:Wiki)?Project|Scope)[ _]?shell|Multiple[ _]wikiprojects|WikiProject[ _]?Banners?|WPBS?)\s*\|\s*)/im;
							if (talkText.search(pattern) >= 0) {
								newText = talkText.replace( pattern, ("$1" + newText) );
								editTalk(newText,"");
							} else {
								console.log("Banner shell on talk page, but not found in wikitext: " + talkText);
								editTalk(newText,"prepend");
							}
						} else {
							console.log("typeof(talkText) = " + typeof(talkText));
							editTalk(newText,"prepend");
						}
					} ).fail (function (error) {
						console.warn( "Couldn't retrieve talk page text due to error: " + JSON.stringify( error ) );
						editTalk(newText,"prepend");
					} );
				} else if(newTags.length > 2) {
					console.log("typeof(bannerShellUsed) = " + typeof(bannerShellUsed) );
					newText = "{{WikiProject banner shell|\n" + newText + "}}";
					editTalk(newText,"prepend");
				} else {
					console.log("typeof(bannerShellUsed) = " + typeof(bannerShellUsed) + "; newTags.length = " + newTags.length);
					editTalk(newText,"prepend");
				}
			} ).fail( function ( error ) {
				console.warn( "Couldn't retrieve templates on talk page due to error: " + JSON.stringify( error ) );
				editTalk(newText,"prepend");
			} );
			return;
		}
	} );
	if (mw.util.getParamValue('draftsorttrigger')) {
		$( portletLink ).trigger("click");
	}
} ) }( jQuery, mediaWiki ) );
//</nowiki>