Jump to content

User:Ahecht/Scripts/draft-sorter.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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" );
	var api = new mw.Api( { userAgent: 'draft-sorter/0.0.1' } );

	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(/media/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 = {};

		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 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) { 
				function addWithLink(p) {
					$( "#draft-sorter-suggest" ).append(
						$( "<li>" ).text( item + " (" ).append(
							$( "<a>" ).text("add").click(
								function() {
									$( select ).val( 
										$( select ).val().concat( [ "WikiProject " + p ] )
									).trigger("chosen:updated");
								}
							)
						).append( ")" )
					);
				}
				
				var singularItem = item.replace(/s$/, '');
				if( !existingProjects.includes( "WikiProject " + item ) 
					&& wikiprojects[item]
				) { //Prediction matches a WikiProject and doesn't already exist
					addWithLink(item);
				} else if( singularItem != item
					&& !existingProjects.includes( "WikiProject " + singularItem ) 
					&& wikiprojects[singularItem]
				) { //Singular form of prediction matches a WikiProject and doesn't exist
					addWithLink(singularItem);
				} 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", section: "0",
					title: "Draft talk:" + mw.config.get( "wgTitle" ),
					summary: "Tagging draft: +" + newTags.join(", +") +
						" ([[User:Ahecht/Scripts/draft-sorter|draft-sorter]])",
				};
				params[prefix + "text"] = text;

				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;
			}

			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*[a-z1]+\s*=[^\{\}]*)*\s*(?:\\n)*?)/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>