User:Qwerfjkl/scripts/CFDlister.js

// Fork of User:Writ Keeper/Scripts/autoCloser.js //

mw.loader.using(["oojs-ui-core", "oojs-ui-windows", "oojs-ui-widgets"], function {	function escapeRegexp(string) {	    return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); // $& means the whole matched string	}	function parseHTML(html) {	    // Create a temporary div to parse the HTML	    var tempDiv = $(' ').html(html);	    // Find all li elements	    var liElements = tempDiv.find('li');	    // Array to store extracted hrefs	    var hrefs = [];	    let existinghrefRegexp = /^https:\/\/en\.wikipedia.org\/wiki\/(Category:[^?&]+?)$/;	    let nonexistinghrefRegexp = /^https:\/\/en\.wikipedia\.org\/w\/index\.php\?title=(Category:[^&?]+?)&action=edit&redlink=1$/;	    // Iterate through each li element	    liElements.each(function  { // Find all anchor (a) elements within the current li	       let hrefline = []; var anchorElements = $(this).find('a'); // Extract href attribute from each anchor element anchorElements.each(function {	            var href = $(this).attr('href');	            if (href) {	                var existingMatch = existinghrefRegexp.exec(href);	                var nonexistingMatch = nonexistinghrefRegexp.exec(href);	                if (existingMatch) {	                    hrefline.push(decodeURIComponent(existingMatch[1]).replaceAll('_', ' '));	                }	                if (nonexistingMatch) {	                    hrefline.push(decodeURIComponent(nonexistingMatch[1]).replaceAll('_', ' '));	                }	            }	        }); hrefs.push(hrefline); });	   return hrefs;	}	function handlepaste(widget, e) {	    var types, pastedData, parsedData;	    // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)	    if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {	        // Check for 'text/html' in types list	        types = e.clipboardData.types;	        if (((types instanceof DOMStringList) && types.contains("text/html")) || ($.inArray && $.inArray('text/html', types) !== -1)) {	           // Extract data and pass it to callback	            pastedData = e.clipboardData.getData('text/html');	            parsedData = parseHTML(pastedData);	            // Check if it's an empty array	            if (!parsedData || parsedData.length === 0) {	                // Allow the paste event to propagate for plain text or empty array	                return true;	            }	            let confirmed = confirm( 'You have pasted formatted text. Do you want this to be converted into wikitext?' );				if (!confirmed) return true;	           processPaste(widget, pastedData);	            // Stop the data from actually being pasted	            e.stopPropagation;	            e.preventDefault;	            return false;	        }	    }	    // Allow the paste event to propagate for plain text	    return true;	}	function waitForPastedData(widget, savedContent) {	    // If data has been processed by the browser, process it	    if (widget.getValue !== savedContent) {	        // Retrieve pasted content via widget's getValue	        var pastedData = widget.getValue;	        // Restore saved content	        widget.setValue(savedContent);	        // Call callback	        processPaste(widget, pastedData);	    }	    // Else wait 20ms and try again	    else {	        setTimeout(function  { waitForPastedData(widget, savedContent); }, 20);	   }	}	function processPaste(widget, pastedData) {	    // Parse the HTML	    var parsedArray = parseHTML(pastedData);	    let stringOutput = '';	    for (const cats of parsedArray) {		    if (cats.length === 1) stringOutput += `* ${cats[0]}\n`;		    if (cats.length === 2) stringOutput += `* ${cats[0]} to ${cats[1]}\n`;		    if (cats.length === 3) stringOutput += `* ${cats[0]} to ${cats[1]} and ${cats[2]}\n`;		    if (cats.length > 3) {		    	let firstCat = cats.pop(0);		    	let lastCat = cats.pop(0);		    	stringOutput += `* ${firstCat}} to ${cats.join(', ')} and ${lastCat}\n`;		    }		}	    widget.insertContent(stringOutput);	}	// Code from https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.Dialog	// Add interface shell	function CfdDialog( config ) {	    CfdDialog.super.call( this, config );	    this.InputText = config.InputText; this.sectionIndex = config.sectionIndex || null; CfdDialog.static.title = config.dialogTitle; CfdDialog.static.actions = config.dialogActions; }	OO.inheritClass( CfdDialog, OO.ui.ProcessDialog ); CfdDialog.static.name = 'CfdDialog'; CfdDialog.prototype.initialize = function { CfdDialog.super.prototype.initialize.call( this ); this.content = new OO.ui.PanelLayout( { padded: false, expanded: false } ); this.content.$element.append( 'Make any changes necessary: ' ); CfdDialog.prototype.cfdlisterTextBox = new OO.ui.MultilineTextInputWidget( {			autosize: true,			value: this.InputText,			id: "CFD-lister-text",			rows: Math.min((this.InputText.match(/\n/g)||[]).length+2, 10),	       maxrows: 25		} ); let textInputElement = CfdDialog.prototype.cfdlisterTextBox.$element.get(0); let handler = handlepaste.bind(this, CfdDialog.prototype.cfdlisterTextBox); // Modern browsers. Note: 3rd argument is required for Firefox <= 6 if (textInputElement.addEventListener) { textInputElement.addEventListener('paste', handler, false); }		// IE <= 8 else { textInputElement.attachEvent('onpaste', handler); }		mw.loader.using('ext.wikiEditor', function {			mw.addWikiEditor(CfdDialog.prototype.cfdlisterTextBox.$input);		}); CfdDialog.prototype.cfdlisterTextBox.$input.on('input', => this.updateSize ); this.content.$element.append(CfdDialog.prototype.cfdlisterTextBox.$element); this.$body.append( this.content.$element ); };	CfdDialog.prototype.getActionProcess = function ( action ) { var dialog = this; if ( action ) { return new OO.ui.Process( function {	            dialog.close( { text: '\n\n'+CfdDialog.prototype.cfdlisterTextBox.value, sectionIndex: this.sectionIndex } );	        } ); }	   return CfdDialog.super.prototype.getActionProcess.call( this, action ); };		function editDiscussions { var text = localStorage.getItem('CFDNAClist'); if (!text) { mw.notify('Error, no discussions listed yet.', {type:'error'}); return; }		var windowManager = new OO.ui.WindowManager; var cfdDialog = new CfdDialog(			{				InputText: text.trim, // newlines will be stripped here and added back implicitly in the closing call				dialogTitle: 'Edit listed discussions',				dialogActions: [					{ action: 'add', label: 'Save', flags: ['primary', 'progressive'] },					{ label: 'Cancel', flags: ['destructive', 'safe'] }					]			}); windowManager.defaultSize = 'full'; $( document.body ).append( windowManager.$element ); windowManager.addWindows( [ cfdDialog ] ); windowManager.openWindow( cfdDialog ); windowManager.on('closing', (win, closing, data) => {			if (!data) return;			if (!data.text.trim) {				OO.ui.confirm('Are you sure you want to delete all listed discussions?').done((response) => { if (response) { localStorage.setItem('CFDNAClist', ''); localStorage.setItem('CFDNAClist-count', ''); } else mw.notify('Aborted changes to listed discussions.'); });			} else {				localStorage.setItem('CFDNAClist', data.text);				mw.notify('Listed discussions updated.');			}			}); }	var editDiscussionslink = mw.util.addPortletLink( 'p-cactions', '#','Edit discussions', 'pt-cfdnaceditlist', 'Edit listed discussions', null, listDiscussionslink); $( editDiscussionslink ).click( function ( event ) {       event.preventDefault;        editDiscussions;    });

function quickClose(option, editLink, headerElement) { if (typeof editLink !== "undefined") { var regexResults = /title=([^&]+).*&section=[\D]*(\d+)/.exec(editLink.href); if(regexResults === null) {				return false; }			var pageTitle = regexResults[1].replaceAll('_', ' '); // prettify var sectionIndex = regexResults[2]; const params = { action: "parse", format: "json", page: pageTitle, prop: "wikitext|sections", section: sectionIndex };			const api = new mw.Api; api.get(params).done(data => {				sectionTitle = data.parse.sections[0].line.replaceAll('_', ' ');				wikitext = data.parse.wikitext["*"];				const closedRegexp = /<div class="boilerplate cfd vfd xfd-closed mw-archivedtalk"/i;				if ( closedRegexp.test(wikitext) ) { // already closed					mw.notify('Discussion already closed, aborted closure.', {type:'error'});					return;				}				const newWikitext = `\n${wikitext.replace(/====.+====/, `$&\n${option}${mw.config.get('wgUserGroups').includes('sysop') ? '' : ' '} ~\n`)}\n`;				var requestData =					{						action: "edit",						title: pageTitle,						format: "json",						section: sectionIndex,						text: newWikitext,						summary: `/* ${sectionTitle} */ Quick close as ${option} via script`,						notminor: 1,						nocreate: 1,						token: mw.user.tokens.get( 'csrfToken' )					};					$.ajax({ url: mw.util.wikiScript( 'api' ), type: 'POST', dataType: 'json', data: requestData })					.then (function( data ) { if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) { mw.notify( `Discussion closed as ${option}.` ); // Now use wikitext from before, don't bother refetching let result = option; const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *'''(?:Nominator'?s )?rationale?/i; if ( categoryRegex.test(wikitext) ) { // correctly formatted wikitext = wikitext.match(categoryRegex)[1]; } else { alert("This nomination is missing a Nominator's rationale: and so the script cannot recognise the categories nominated. Please manually fix this by adding Nominator's rationale: just before the nominator's rationale."); return; }							// Cleanup wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc|cat)\|(.+?)\}\}/gi, 'Category:$1'); // fix category templates wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above wikitext = wikitext.replace(/(propose|delet|renam|split|(?:up)?merg|container).*?/gi, ''); wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk wikitext = wikitext.replace(//g, ''); // remove br tags wikitext = `* ${pageTitle}\nResult: ${result}\n `; var incorrectOptionRegexp; switch (option) { case 'rename': case 'merge': incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\]$/gim; if ( incorrectOptionRegexp.test(wikitext) ) { mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {type:'error'}); return; }									break; case 'delete': incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\].+\[\[:Category:/gim; if ( incorrectOptionRegexp.test(wikitext) ) { mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {type:'error'}); return; }									break; default: // shouldn't happen unless the user has modified their html mw.notify(`Error handling closure: ${option}. Please click "list discussion" to list it manually.`, {type:'error'}); return; }							if ( wikitext.includes(' ') || wikitext.includes('{{') || wikitext.includes(')? ?(.+?)/i;				if ( resultRegexp.test(wikitext) ) { // match					result = wikitext.match(resultRegexp)[1];				} else {					result = 'RESULT';				}				wikitext = wikitext.replace(new RegExp(resultRegexp.source + '.+', 'i'), ); // remove closure text, unneeded				const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *(?:Nominator'?s )?rationale?/i;				if ( categoryRegex.test(wikitext) ) { // correctly formatted					wikitext = wikitext.match(categoryRegex)[1];				} else {					alert("This nomination is missing a Nominator's rationale: and so the script cannot recognise the categories nominated. Please manually fix this by adding Nominator's rationale': just before the nominator's rationale.");					return;				}				// Cleanup				wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc|cat)\|(.+?)\}\}/gi, 'Category:$1'); // fix category templates wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above wikitext = wikitext.replace(/(propose|delet|renam|split|(?:up)?merg|container).*?/gi, ''); wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk wikitext = wikitext.replace(//g, ''); // remove br tags wikitext = "* "+pageTitle+"\nResult: "+result+"\n "; var windowManager = new OO.ui.WindowManager;

var cfdDialog = new CfdDialog(					{						InputText: wikitext,						sectionIndex: sectionIndex,						dialogTitle: 'List discussion for processing',						dialogActions: [							{ action: 'add', label: 'Add', flags: ['primary', 'progressive'] },							{ label: 'Cancel', flags: ['destructive', 'safe'] }							]					}); windowManager.defaultSize = 'full'; $( document.body ).append( windowManager.$element ); windowManager.addWindows( [ cfdDialog ] ); windowManager.openWindow( cfdDialog ); windowManager.on('closing', addCFD); });		}	}   function addCFD(win, closing, data) {				if ( data == null || data === undefined || data.text ==  || !data.text ) {					return;				}				var wikitext = data.text;				var text = localStorage.getItem('CFDNAClist');				var count = localStorage.getItem('CFDNAClist-count') || 0;				if (text ==  || text == null) {					localStorage.setItem('CFDNAClist', wikitext);				}				else {					localStorage.setItem('CFDNAClist', text+wikitext);				}				localStorage.setItem('CFDNAClist-count', Number(count)+1);				mw.notify('Added discussion');				// double strike through handled sections				if (data.sectionIndex) {					// apply styles to show discussion has been closed (like XFDCloser)					$(`h4:nth-of-type(${data.sectionIndex-1})`).find('.mw-headline').css({'text-decoration': 'line-through', 'text-decoration-style': 'double'});					var startH4 = document.querySelector(`h4:nth-of-type(${data.sectionIndex-1})`); if (startH4) { var elementsBetweenH4 = []; var currentElement = startH4.nextElementSibling; while (currentElement) { if (currentElement.tagName.toLowerCase === 'h4') { break; }					         elementsBetweenH4.push(currentElement); currentElement = currentElement.nextElementSibling; }					     elementsBetweenH4.forEach(function(element) {					          $(element).css('opacity', '50%');					      }); }				}	}	function discussionListerSetup { function createDropdownLink(text, clickHandler) { var link = document.createElement("a"); link.href = "#"; link.innerHTML = text; link.onclick = function (event) { event.preventDefault; clickHandler; };		   return link; }	   var sectionHeaders = $("h4 ~ .mw-editsection"); $('.mw-heading, h1, h2, h3, h4, h5, h6').css('overflow', 'visible'); // allow for dropdown, no idea what damage it could do	   sectionHeaders.each(function (index, element) {	        var editLink = $(element).children("a")[0];	        if (typeof editLink !== "undefined" && /&section=[\D]*(\d+)/.exec(editLink.href)) {	            $(editLink).addClass("sectionEditLink");	            var discussionLister = $("List discussion");				discussionLister.click(listDiscussion);

// Create dropdown elements var dropdownContainer = $(` `);

var dropdownTrigger = $(`One click close`);

var dropdownMenu = $(` `);

var actions = ['delete', 'rename', 'merge']; // needs to correspond with switch statement in quickClose for (var i = 0; i < actions.length; i++) { const action = actions[i]; var menuItem = $(`${action}`); menuItem.click(function {		                quickClose(action, editLink, element);		                dropdownMenu.hide;		            }); // make red on hover menuItem.on( "mouseenter", function {$(this).css('color', 'red')} ).on( "mouseleave", function  {$(this).css('color', 'color: #0645AD')} ) dropdownMenu.append(menuItem); }			   dropdownTrigger.click(function  {		            dropdownMenu.toggle;		        }); // Append elements to the existing element dropdownContainer.append(dropdownTrigger, dropdownMenu); // Close the dropdown if the user clicks outside of it 		      $(document).click(function (event) {	            if (!$(event.target).closest('.dropdown-container').length) {	                dropdownMenu.hide;	            }	          }); let bracket = $(element).find('.mw-editsection-bracket').last $(bracket).before(' | ', discussionLister, " | ", dropdownContainer);

}	   });

}		if (mw.config.get("wgPageName").match('Wikipedia:Categories_for_discussion')) $(document).ready(discussionListerSetup); });

function listPages { var text = localStorage.getItem('CFDNAClist'); var count = localStorage.getItem('CFDNAClist-count'); if (text == '' || text == null) { mw.notify('No discussions to list, aborting'); return; }	text = "\n\nPlease can an admin add the following:"+text+"\n~"; var requestData = {		action: "edit", title: "Wikipedia talk:Categories for discussion/Working", format: "json", //section: "new", //sectiontitle: "NAC request ", appendtext: text, summary: "Add NAC request ("+count+" dicussions listed) via script", notminor: 1, nocreate: 1, redirect: 1, token: mw.user.tokens.get( 'csrfToken' )

};	mw.notify('Editing WT:CFDW...', {tag:'CFDListerEdit'}); $.ajax({		url: mw.util.wikiScript( 'api' ),		type: 'POST',		dataType: 'json',		data: requestData	}) .then (function( data ) {		if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {			mw.notify( 'Discussions listed.', {tag:'CFDListerEdit'});			localStorage.setItem('CFDNAClist', '');			localStorage.setItem('CFDNAClist-count', 0);           window.location.href = "https://en.wikipedia.org/wiki/Wikipedia_talk:Categories_for_discussion/Working#footer"; // redirect		} else {			alert( 'The edit query returned an error. =(' );		}	})	.catch ( function { alert( 'The ajax request failed.' ); }); }

var listDiscussionslink = mw.util.addPortletLink( 'p-cactions', '#','List discussions', 'pt-cfdnaclist', 'List closed discussions for admins to handle'); $( listDiscussionslink ).click( function ( event ) {       event.preventDefault;        listPages;    });

//