User:Erutuon/scripts/cleanup.js

/*	A script that makes it easier to create scripts that perform repetitive and tedious editing tasks. It inserts buttons above the text box, if certain conditions are fulfilled. Pressing the button executes the function. A variable tracks how many times a change is made, then the function adds an edit summary if the change was done; if not, it keeps the original edit summary.

/* globals $, CleanupButtons, mw */ // "use strict";

var mwValues       = mw.config.values; var action			= mwValues.wgAction; var namespaceNumber	= mwValues.wgNamespaceNumber; var contentModel 	= mwValues.wgPageContentModel; var pageName       = mwValues.wgPageName;

// mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/load.js&action=raw&ctype=text/javascript');

// Notify about footnote errors (such as undefined named footnotes). var citeErrors = document.getElementsByClassName("mw-ext-cite-error"); if (citeErrors.length > 0) mw.notify(Array.from(citeErrors).map(element => element.innerText).join("\n"));

if ( action === "edit" && contentModel === "wikitext"	// not edit conflict	&& document.getElementsByClassName("mw-twocolconflict-page").length === 0 ) {	var wikitext = $("#wpTextbox1").val;

var startsAndEndsWithWhitespace = /^\s+.+?\s+$/; // Notify about template errors (such as more than one value for a parameter). var previewNote = document.getElementsByClassName("previewnote"); if (previewNote.length > 0) { var notes = previewNote[0].children; for (const note of notes) { const innerText = note.innerText; if (innerText.includes("Template:")) mw.notify(innerText); }	}	var match; var regex = /\{\{lang\|([^|]+)\|([^}]+)\}\}/g; var langs = []; var nocat_langs = []; while ((match = regex.exec(wikitext)) !== null) { if (match[2] && match[2].includes("nocat")) { if (!nocat_langs.includes(match[1])) nocat_langs.push(match[1]); }		else if (!langs.includes(match[1])) { langs.push(match[1]); }	}	var failed_cat = []; for (var lang of nocat_langs) { if (!langs.includes(lang)) failed_cat.push(lang); }	if (failed_cat.length > 0) mw.notify(`Categorization might be failing for the following languages: ${failed_cat.join(", ")}.`,			{ autoHide: false }); var notifyReplacements = function(count) {		if ( typeof count === "number" ) mw.notify(`${ count === 0 && "No" || count } replacement${ count !== 1 && "s" || "" } made.`); else console.log("The function notifyReplacements failed because its argument is not a number."); };	var addSummary = function(count, summary) {		if ( typeof count !== "number" ) console.log("Error: count argument to addSummary is not a number."); if ( count > 0 ) {			$("#wpSummary").val(				function(index, content)				{					var afterSectionName = content.match(/^(?:\/\*[^\*]+\*\/)?\s*(.*?)$/);					var scriptMention = ", with the help of JavaScript";					var addition;					if ( afterSectionName && afterSectionName[1].length > 0 )					{						if ( content.includes(scriptMention) )						{							content = content.replace(scriptMention, "");							addition = " and " + summary;						}						else							addition = "; " + summary;					}					else						addition = summary;					if ( ( !afterSectionName || !content.includes(summary) ) )						content += addition;					if ( content.includes(summary) && !content.includes(scriptMention) )						content += scriptMention;					return content;				}			); }	};	var cleanupFunctions = [ // Template: {			condition: false, textBoxIncludes: "", button: { text: "blah", },			minorEdit: false, func: function(content) {					return content; }		},		{			textBoxIncludes: /^#/m, button: { text: "ordered to unordered", },			minorEdit: true, func: function(content) {					var origContent = content; content = content.replace(/^#/gm, '*'); if (content !== origContent) addSummary(1, "ordered list not appropriate here (MOS:LIST)"); return content; }		},		{			textBoxIncludes: /^[\*#;:]+(?=[^\s\*#;:])/m, button: { text: "spaces after list syntax", },			func: function(content) {					content = content.replace(/^([\*#;:]+)\s*(?=[^\s\*#;:])/gm,						'$1 '); return content; }		},		{			textBoxIncludes: /binomial|sectio/, condition: function (wikitext) { return /{{[Ss]peciesbox/.test(wikitext) && (wikitext.includes("binomial") || wikitext.includes("sectio")); },			button: { text: "fix ", },			minorEdit: true, func: function(content) {					var taxon; var pageNameWithSpaces = pageName.replace(/_/g, " "); var origContent = content; content = content.replace(						/{{speciesbox(?:[^{}]+|{{[^}]+}})+}}/g,						function (template) {							return template								.replace(/\|}}$/, "}}")								.replace( /(\n*\|\s*)([\w_]+)(\s*=\s*)(.*?)(?=\s*[|}])/g, function (wholeMatch, before, paramName, between, paramValue) { // console.log([ wholeMatch, before, paramName, between, paramValue ].map(str => '"' + str + '"').join("\t")); switch (paramName) { case "binomial": case "taxon": // include taxon because of code below paramName = "taxon"; paramValue = paramValue.replace(/''/g, ""); taxon = paramValue; break; case "binomial_authority": paramName = "authority"; break; case "subgenus": case "sectio": case "subsectio": case "series": paramName = "parent"; break; case "name": { // Delete unnecessary |name= parameter. if (paramValue.replace(/''/g, "") === pageNameWithSpaces) return ""; break; }											default: return wholeMatch; }										return before + paramName + between + paramValue; });						});					// mw.notify([ "taxon", taxon, "pageName", pageName.replace(/_/g, " ") ].join(', ')); var summary = "fix parameters of Template:speciesbox"; if (taxon === pageNameWithSpaces) { var oldContent = content; content = content.replace(/\n*/g, ""); if (content !== oldContent) summary += " and remove unnecessary Template:italic title"; }					if (content === origContent) return; addSummary(1, summary); return content; }		},		{			textBoxIncludes: /\{\{[Tt]axobox\s*[\||\}\}]/, button: { text: " to ", },			func: function(content) {					var disallowedParams = new Set(["classis", "divisio",						"familia", "genus", "ordo", "regnum", "species",						"subfamilia", "tribus", "unranked_classis",						"unranked_divisio", "unranked_ordo"]); content = content.replace(						/\{\{[Tt]axobox(?:[^\{\}]+|\{\{[^}]+\}\})+\}\}/g,						function (template) {							var whitespace = /^\s+$/;							var taxon;							template = template.replace( /\|\s*([^=\|\}]+?)(\s*=\s*)((?:\[\^\+\]\]|\^\+\]|\{\{[^\}]+\}\}|[^\|\}\[\]]+)+)/g, function (wholeMatch, paramName, between, paramValue) { if (disallowedParams.has(paramName)) return ""; else { var ref = ""; if (paramName === "name") { // Remove italics and compare with page name. if (paramValue.trim.replace(/''/g, "")													=== pageName.replace(/_/g, " ")) return ""; }										if (paramName === "binomial") { paramName = "taxon"; paramValue = paramValue.replace(												/(.+?)/g, '$1'); // Remove italics. } else if (paramName === "binomial_authority") paramName = "authority"; else if (paramName === "subgenus" || paramName === "sectio"												|| paramName === "subsectio" || paramName === "series") paramName = "parent"; else if (paramName === "synonyms" && paramValue.includes(']+)?>.+?<\/ref>/g; var refs = []; var refMatch; while ((refMatch = refRegex.exec(ref))) { refs.push(refMatch[0]); }												ref = refs.join(""); }											paramValue = paramValue .split(/(?:,?\s*\n*)+/) .map(item => whitespace.test(item) ? "" : "\n* " + item) .join("") .trim; }										if (paramName === "taxon") taxon = paramValue; if (!startsAndEndsWithWhitespace.test(between)) between = " = "; return "| " + paramName + between + paramValue + ref; }								});							template = template								.replace(/[Tt]axobox/, "speciesbox")								.replace(/\|\}\}$/, "}}");							return template;						}); content = content.replace(/\n*/, ""); addSummary(1, "switched to Template:speciesbox"); return content; }		},		{			textBoxIncludes: /<ref|[^\[]\[[^\[]+\](?!\])/i,			button: {				text: "specific-source templates",			},			func:				function(content)				{					var count1 = 0, count2 = 0;					var regexes = {};					regexes.BONAP = /https?:\/\/bonap\.net\/(?:MapGallery|[Nn][Aa][Pp][Aa]\/TaxonMaps\/Genus)\/([cC]ounty|[sS]tate)\/(\w+)(?:%20(.*?)\.png)?/g;					regexes.PLANTS = /https?:\/\/plants\.usda\.gov\/(?:core|java)\/profile\?symbol=([a-zA-Z]+\d*)/;					regexes.eFloras = /https?:\/\/(?:www\.)?efloras\.org\/florataxon\.aspx\?flora_id=(\d+)&taxon_id=(\d+)/;					regexes.wildflowerDotOrg = /https?:\/\/(?:www\.)?wildflower\.org\/plants\/result\.php\?id_plant=([a-zA-Z]+\d*)/;					regexes.FEIS = /https?:\/\/(?:www\.)?fs\.fed\.us\/database\/feis\/[a-z]+\/([a-z]+)\/[a-z]+\/all\.html/;					regexes.ThePlantList = /http:\/\/(?:www\.)?theplantlist\.org\/tpl1\.1\/record\/([a-z0-9-]+)/;					regexes.MissouriPlants = /https?:\/\/(?:www\.)?missouriplants\.com\/(\w+?)(opp|alt)\/([A-Za-z]+)_([A-Za-z]+)_page\.html/;					regexes.CalPhotos = /https?:\/\/(?:www\.)?calphotos\.berkeley\.edu\/cgi\/img_query\?(?:query_src=photos_index&)?where-taxon=([\w-]+)(?:\+|%20)([\w-]+)/;					regexes.Tropicos = /http:\/\/(?:www\.)?tropicos\.org\/[Nn]ame\/(\d+)\/?(?:\?projectid=(\d+))?/;					regexes.EOL = /https?:\/\/(?:www\.)?eol\.org\/pages\/(\d+)(?:\/overview)?/;					regexes.ITIS = /https?:\/\/(?:www\.)?itis\.gov\/servlet\/SingleRpt\/SingleRpt\?search_topic=TSN&search_value=(\d+)/;					regexes.GoBotany = /https?:\/\/(?:www\.)?gobotany\.newenglandwild\.org\/(?:genus|species)\/(\w+)(?:\/(\w+))?/;					regexes.GoOrchids = /https?:\/\/(?:www\.)?goorchids\.northamericanorchidcenter\.org\/\w+\/(\w+)(?:\/(\w+))/;					regexes.IllinoisWildflowers = /https?:\/\/(?:www\.)?illinoiswildflowers\.info\/([a-z\._\/]+)\.htm/;					regexes.MinnesotaWildflowers = /https?:\/\/(?:www\.)?minnesotawildflowers\.info\/([a-z\.\/-]+)/;					regexes.WCSP = /https?:\/\/(?:www\.)?apps\.kew\.org\/wcsp\/namedetail\.do\?name_id=(\d+)/;					regexes.KansasWildflowers = /https?:\/\/(?:www\.)?kswildflower\.org\/([a-z]+)_details\.php\?[a-z]+ID=(\d+)/;					// http://ucjeps.berkeley.edu/cgi-bin/get_IJM.pl?tid=80590					// http://ucjeps.berkeley.edu/eflora/eflora_display.php?tid=80590					// cgi-bin/get_IJM eflora/eflora					regexes.Jepson = /https?:\/\/(?:www\.)?ucjeps\.berkeley\.edu\/(?:eflora\/eflora_display\.php|cgi-bin\/get_IJM\.pl)\?(tid|key)=(\d+)/;					//http://ucjeps.berkeley.edu/cgi-bin/get_JM_treatment.pl?8738,8858,8875					regexes.JepsonManual = /https?:\/\/(?:www\.)?ucjeps\.berkeley\.edu\/cgi-bin\/get_JM_treatment\.pl\?([\d,]+)/;					// http://www.calflora.org/cgi-bin/species_query.cgi?where-taxon=Sambucus+racemosa+var.+melanocarpa					// http://www.calflora.org/cgi-bin/species_query.cgi?where-calrecnum=8838					regexes.Calflora = /https?:\/\/(?:www\.)?calflora\.org\/cgi-bin\/species_query\.cgi\?(?:where-taxon=([A-Za-z.+-]+)|where-calrecnum=(\d+))/;					regexes.Gymnosperm = /https?:\/\/(?:www\.)?conifers\.org\/([a-z][a-z])\/([A-Z][a-z]+)(?:_([a-z]+)(?:_([a-z]+))?)?\.php/;					regexes.IPNI = /https?:\/\/(?:www\.)?ipni\.org\/ipni\/idPlantNameSearch\.do\?id=([\d-]+)/;					// https://michiganflora.net/species.aspx?id=2796					// https://michiganflora.net/genus.aspx?id=Viola					// https://michiganflora.net/family.aspx?id=Violaceae					regexes.MichiganFlora = /https?:\/\/(?:www\.)?michiganflora\.net\/(family|genus|species)\.aspx\?id=(\w+)/;					regexes.ConnecticutPlants = /https?:\/\/(?:www\.)?ct-botanical-society\.org\/Plants\/view\/(\d+)/;					regexes.FloraOfWisconsin = /https?:\/\/(?:www\.)?wisflora\.herbarium\.wisc\.edu\/taxa\/index\.php\?taxon=(\d+)/;					var paramValueRegex = /\s*=\s*([^|]+[^|\s])/;					var pageNameWithSpaces = pageName.replace(/_/g, " ");					content = content.replace(						/(]+)?>)([^\0]*?)(<\/ref>)/gi,						function (wholeMatch, openRefTag, contents, closeRefTag) {							if (!(contents.includes("http://") | contents.includes("https://"))) {								return wholeMatch;							}							var match, taxon, accessDate;							++count1;							taxon = contents.match(/title\s*=\s*([^\|\}]*[^\s\|\}])/)								|| contents.match(/(.+?)/); // only species and below							taxon = taxon ? taxon[1] : pageNameWithSpaces;							accessDate = contents.match(/(?:[Rr]etrieved|[Aa]ccessed|access-?date\s*=)\s*([A-Za-z\d-\/, ]+)/)								|| contents.match(/([A-Z][a-z]+ \d\d?,? \d\d\d\d)/);							var accessDateParam = accessDate ? ` |access-date=${accessDate[1]}` : '';							if (contents.includes("bonap.net")) {								match = regexes.BONAP.exec(contents);								const year = contents.match(/201\d/);								if (match && match[1]) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("plants.usda.gov")) { // ever another capitalization?								match = regexes.PLANTS.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("efloras.org")) {								match = regexes.eFloras.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("kswildflower.org")) {								match = regexes.KansasWildflowers.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("wildflower.org")) {								match = regexes.wildflowerDotOrg.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("fs.fed.us/database/feis")) {								match = regexes.FEIS.exec(contents);								var genusSpecies = taxon.split(/\s+/);								if (genusSpecies.length !== 2)									genusSpecies = pageNameWithSpaces.split(' ');								if (match && genusSpecies.length === 2) {									var params = new Map;									for (let i = 0, paramName1 = "first", paramName2 = "last";											contents.includes(paramName1);											++i, paramName1 = "first" + i, paramName2 = "last" + i) {										const index1 = contents.indexOf(paramName1);										if (index1 !== -1) {											const value = contents.substring(index1 + paramName1.length).match(paramValueRegex);											if (value)												params.set(paramName1, value[1]);										}										const index2 = contents.indexOf(paramName2);										if (index2 !== -1) {											const value = contents.substring(index2 + paramName2.length).match(paramValueRegex);											if (value)												params.set(paramName2, value[1]);										}									}									{										const index = contents.indexOf("date");										if (index !== -1) {											const dateMatch = contents.substring(index + "date".length).match(paramValueRegex);											if (dateMatch)												params.set("date", dateMatch[1]);										}									}									var printedParams = [];									for (const [ key, value ] of params) {										printedParams.push(` |${key}=${value}`);									}									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("theplantlist.org")) {								match = regexes.ThePlantList.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("tropicos.org")) {								match = regexes.Tropicos.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("eol.org")) {								match = regexes.EOL.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("itis.gov")) {								match = regexes.ITIS.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("ipni.org")) {								match = regexes.IPNI.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("gobotany.newenglandwild.org")) {								match = regexes.GoBotany.exec(contents);								if (match) {									match[1] = match[1].replace(/^./, (char) => char.toUpperCase);									if (match[2]) {										return `${openRefTag}${closeRefTag}`;									}									else {										return `${openRefTag}${closeRefTag}`;									}								}							}							else if (contents.includes("goorchids.northamericanorchidcenter.org")) {								match = regexes.GoOrchids.exec(contents);								if (match) {									match[1] = match[1].replace(/^./, (char) => char.toUpperCase); // genus									if (match[2]) {										return `${openRefTag}${closeRefTag}`;									}									else {										return `${openRefTag}${closeRefTag}`;									}								}							}							else if (contents.includes("illinoiswildflowers.info")) {								match = regexes.IllinoisWildflowers.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("minnesotawildflowers.info")) {								match = regexes.MinnesotaWildflowers.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("pfaf.org")) {								// No regex needed.								return `${openRefTag}${closeRefTag}`;							}							else if (contents.includes("apps.kew.org/wcsp")) {								match = regexes.WCSP.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("ucjeps.berkeley.edu/cgi-bin/get_IJM")								 || contents.includes("ucjeps.berkeley.edu/eflora/eflora")) {								match = regexes.Jepson.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("ucjeps.berkeley.edu/cgi-bin/get_JM_treatment")) {								match = regexes.JepsonManual.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("calflora.org")) {								match = regexes.Calflora.exec(contents);								if (match) {									return `${openRefTag}${closeRefTag}`;								}							}							else if (contents.includes("conifers.org")) {								match = regexes.Gymnosperm.exec(contents);								var families = {									ar: "Araucariaceae", cu: "Cupressaceae",									cy: "Cycadaceae", ep: "Ephedraceae",									gi: "Ginkgoaceae", gn: "Gnetaceae",									pi: "Pinaceae", po: "Podocarpaceae",									sc: "Sciadopityaceae", ta: "Taxaceae",									we: "Welwitschiaceae", 								};								if (match) {									if (families[match[1]]) return `${openRefTag}${closeRefTag}`; else mw.notify(`Family ${match[1]} in ${wholeMatch} not recognized.`); }							}							else if (contents.includes("michiganflora.net")) { match = regexes.MichiganFlora.exec(contents); if (match) { let params; switch (match[1]) { case "family": case "genus": params = ` |${match[1]}=${match[2]}`; break; case "species": { params = ` |id=${match[2]}`; let genusAndSpecies; if (taxon) { genusAndSpecies = taxon.split(" "); params += ` |genus=${genusAndSpecies[0]} |species=${genusAndSpecies[1]}`; }											break; }									}									return `${openRefTag}${closeRefTag}`; }							}							else if (contents.includes("ct-botanical-society.org")) { match = regexes.ConnecticutPlants.exec(contents); if (match) { return `${openRefTag}${closeRefTag}`; }							}							else if (contents.includes("missouriplants.com")) { match = regexes.MissouriPlants.exec(contents); // captures: color, leaf, genus, species if (match) { return `${openRefTag}${closeRefTag}`; }							}							else if (contents.includes("wisflora.herbarium.wisc.edu")) { match = regexes.FloraOfWisconsin.exec(contents); if (match) { return `${openRefTag}${closeRefTag}`; }							}							--count1; return wholeMatch; });					// Look over external links.					content = content.replace( /(^|[^\[])\[([^\[][^ ]+)(\s+[^\]]+)\](?=[^\]]|$)/g, function (wholeMatch, before, URL, text) { var match; ++count2; if (URL.includes("missouriplants.com")) { match = regexes.MissouriPlants.exec(URL); // captures: color, leaf, genus, species if (match) { return `${before}`; }							}							else if (URL.includes("calphotos.berkeley.edu")) { match = regexes.CalPhotos.exec(URL); // captures: genus, species if (match) { return `${before}`; }							}							else if (URL.includes("kswildflower.org")) { match = regexes.KansasWildflowers.exec(URL); if (match) { return `${before}'; }							}							else if (URL.includes("illinoiswildflowers.info")) { match = regexes.IllinoisWildflowers.exec(URL); if (match) { return `${before}`; }							}							else if (URL.includes("minnesotawildflowers.info")) { match = regexes.MinnesotaWildflowers.exec(URL); if (match) { return `${before}`; }							}							else if (URL.includes("wildflower.org")) { match = regexes.wildflowerDotOrg.exec(URL); if (match) { return `${before}`; }							}							else if (URL.includes("ucjeps.berkeley.edu/cgi-bin/get_JM_treatment")) { match = regexes.JepsonManual.exec(URL); if (match) { return `${before}`; }							}							else if (URL.includes("ucjeps.berkeley.edu")) { // See also more specific condition above. match = regexes.Jepson.exec(URL); if (match && match[1] !== "key") { return `${before}`; }							}							else if (URL.includes("calflora.org")) { match = regexes.Calflora.exec(URL); if (match) { return `${before}`; }							}							else if (URL.includes("gobotany.newenglandwild.org")) { match = regexes.GoBotany.exec(URL); if (match) { match[1] = match[1].replace(/^./, (char) => char.toUpperCase); if (match[2]) { return `${before}`; }									else { return `${before}`; }								}							}							else if (URL.includes("goorchids.northamericanorchidcenter.org")) { match = regexes.GoOrchids.exec(URL); if (match) { match[1] = match[1].replace(/^./, (char) => char.toUpperCase); // genus if (match[2]) { return `${before}`; }									else { return `${before}`; }								}							}							--count2; return wholeMatch; });					var whatWasDone = [];					if (count1 > 0) whatWasDone.push("citation" + (count1 === 1 ? "" : "s"));					if (count2 > 0) whatWasDone.push("external link" + (count2 === 1 ? "" : "s"));					addSummary(count1 + count2, `templatized ${whatWasDone.join(" and ")}`);					if (count1 === 0 && count2 === 0)						return;					return content;				}		},		{			textBoxIncludes: "access_date",			button: {				text: "|access_date= &rarr; |access-date=",			},			minorEdit: true,			func:				function(content)				{					content = content.replace(/access_date/g, "access-date");					addSummary(1, "|access_date= &rarr; |access-date=");					return content;				}		},		{			condition: false,			textBoxIncludes: /(^|\D)\d\d-\d\d-\d\d\d\d(?=\D|$)/,			button: {				text: "MM-DD-YYYY &rarr; YYYY-MM-DD",			},			minorEdit: false,			func:				function(content)				{					var count = 0;					content = content.replace( /(^|\D)(\d\d-\d\d)-(\d\d\d\d)(?=\D|$)/, function (wholeMatch, before, monthDay, year) { ++count; return before + year + "-" + monthDay; });					mw.notify(`${count} numerical date${count === 1 ? "" : "s"} fixed.`);					return content;				}		},		{			textBoxIncludes: /\{\{[Cc]onvert\|[^}]+\|\s*abbr\s*=\s*on/,			button: {				text: "undefined undefined &rarr; undefined undefined",			},			minorEdit: true,			func:				function(content)				{					content = content.replace(/\{\{[Cc]onvert(\|[^}]+)\|\s*abbr\s*=\s*on\s*/g, "{{cvt$1");					addSummary(1, "Template:convert with |abbr=on &rarr; Template:cvt for brevity");					return content;				}		},		{			// condition: namespaceNumber === 0,			textBoxIncludes: /\[\[([^\|\]]+)\|\1[^\]]+\]\]/,			button: {				text: "fix piped links",			},			minorEdit: true,			func:				function(content)				{					let count = 0;					content = content.replace( /\[\[([^|]+)\|(\1)([^\]]*)\]\]/gi, function (wholeMatch, _, linkText, suffix) { ++count; return `${linkText}${suffix}`; });					addSummary(count, `fixed piped link${count !== 1 ? 's' : ''}`);					return content;				}		},		{			textBoxIncludes: /\[\[([^\|]+)\|\1\]\]/,			button: {				text: "fix piped links",			},			minorEdit: true,			func:				function(content)				{					var count = 0;					content = content.replace( /\[\[([^\|]+)\|\1\]\]/g, function(wholeMatch, target) {							count++; return "" + target + ""; }					);					notifyReplacements(count);					addSummary(count, "fixed piped links");					return content;				}		},		{			textBoxIncludes: /[‹›⟨⟩]/,			button: {				text: "add ⟨⟩",			},			minorEdit: true,			func:				function(content)				{					var count = 0;					content = content.replace( /⟨([^⟩]+)⟩|‹([^›]+)›/g, function(wholematch, insideBrackets1, insideBrackets2) {							count++; return "$⟨" + ( insideBrackets1⟩$"; }					);					notifyReplacements(count);					addSummary(count, "replaced literal angle brackets with Template:angbr");					return content;				}		},		{			textBoxIncludes: "{{IPA|",			button: {				text: "' &rarr; ˈ",			},			minorEdit: true,			func:				function(content)				{					var count = 0;					content = content.replace( /{{IPA\|([^\}\n]+)}}/g, function(wholematch, transcription) {							if ( transcription.includes("'") ) {								count++; return "{{IPA|" + transcription.replace(/'/g, "ˈ") + "}}"; }							else return wholematch; }					);					notifyReplacements(count);					addSummary(count, "replaced apostrophe with length mark in IPA transcriptions");					return content;				}		},		{			textBoxIncludes: /(\/[^\/]+\/|\^\+\])(?=[\s\.\,\:\;\)\&\-<])/, button: { text: "add ", },			minorEdit: true, func: function(content) {					var count = 0; var escaped = []; var i = 0; var escape = function(text, regexString) {						var regex = new RegExp(regexString, "g"); text = text.replace(							regex,							function(match)							{								escaped[i] = match;								var replacement = "%%" + i + "%%";								i += 1;								return replacement;							}						); return text; };					content = escape(content, "(?:https?:)\\/\\/[^\\s\\|]+"); content = escape(content, "\n]+>"); content = escape(content, "\\{\\{IPA(?:[^\\{\\}\n]+|\\{\\{[^\\}\\n]+\\}\\})+\\}\\}"); var numberRegEx = /^\d+$/; content = content.replace(						/(?:\/[^\/\|%\n]+\/|\%\n]+\])(?=[\s\.\,\:\;\)\&\-<%])/g,						function(wholematch)						{							count++;							if ( wholematch.includes("http") || wholematch.includes("...")								|| wholematch.match(numberRegEx))							{								count--;								return wholematch;							}							else								return "";						}					);					content = content.replace(						/%%(\d+)%%/g,						function(wholematch, number) {							number = Number(number);							return escaped[number];						}					);					content = content.replace(						/%%(\d+)%%/g,						function(wholematch, number) {							number = Number(number);							return escaped[number];						}					);					notifyReplacements(count);					addSummary(count, "added [[Template:IPA");					return content;				}		},		{			textBoxIncludes: /[\/\[]\{\{IPA|\{\{IPA\|[^\}]*\{\{IPA ?link/,			button: {				text: "clean up IPA templates",			},			minorEdit: true,			watch: false,			func:				function(content)				{					var count1 = 0, count2 = 0, count3 = 0;					content = content.replace(						/([\/\[])\{\{IPA\|\[\[([^\}]+?)\]\]\|*\}\}([\/\]])/g, function(wholematch, openingBracket, transcription, closingBracket) {							count2++; var template; if ( openingBracket === "/" ) template = "IPAslink"; else if ( openingBracket === "[" ) template = "IPAblink"; if ( template ) return ""; else {								count2--; return wholematch; }						}					);					content = content.replace( /([\/\[])\{\{IPA\|([^\}]+?)\|*\}\}([\/\]])/g, function(wholematch, openingBracket, transcription, closingBracket) {							count1++; return ""; }					);					/*					content = content.replace( /([\/\[])\{\{IPA\|/g, function(wholematch, openingBracket) {							count++; return "{{IPA|" + openingBracket; }					);					*/					content = content.replace( /(?:\{\{IPA\|)?([\/\[])\{\{IPA ?link\|([^\}]+)\}\}([\/\]])(?:\}\})?/g, function(wholematch, openingBracket, transcription, closingBracket) {							count2++; var template; if ( openingBracket === "/" ) template = "IPAslink"; else if ( openingBracket === "[" ) template = "IPAblink"; if ( template ) return ""; else {								count2--; return wholematch; }						}					);					content = content.replace(/\{\{IPA\|((?:[^\{\}]+|\{\{[^\}]+\}\})+)\}\}/g, function (wholeMatch, innards) { return "{{IPA|" + innards.replace(/\{\{IPA ?link\|/g,									function (wholeMatch) {										++count3;										return "{{IPAplink|";									}) + "}}";						});					addSummary(count1, "moved brackets inside Template:IPA");					addSummary(count2, "replaced Template:IPA link with Template:IPAslink or Template:IPAblink");					addSummary(count3, "{{IPA link}} → {{IPAplink}} inside {{IPA}} for cleaner HTML");					notifyReplacements(count1 + count2 + count3);					return content;				}		},		{			textBoxIncludes: /[ʦʣʧʤʨʥ]/,			button: {				text: "update deprecated IPA",			},			minorEdit: true,			func:				function(content)				{					var count = 0;					var update = new Map([ [ "ʧ", "t͡ʃ" ], [ "ʤ", "d͡ʒ" ], [ "ʦ", "t͡s" ], [ "ʣ", "d͡z" ], [ "ʨ", "t͡ɕ" ], [ "ʥ", "d͡ʑ" ] ]);					update.forEach( function(value, key) {							var regex = new RegExp(key, "g"); content = content.replace(								regex,								function(wholematch)								{									count++;									return value;								}							); }					);					addSummary(count, "updated deprecated IPA");					return content;				}		},		// From User:Erutuon/footnoteCleanup.js.		{			condition: [ 0, 12 ].includes(namespaceNumber),			button: {				text: "clean up footnotes",				id: "footnote-cleanup"			},			minorEdit: true,			func:				function(content)				{					var oldContent = content;					var escaped = [];					var i = 0;					var replacements = [];					var count = 0;					var escape = function(text, regexString)					{						var regex = new RegExp(regexString, "g");						text = text.replace( regex, function(match) {								escaped[i] = match; var replacement = "%%" + i + "%%"; i += 1; return replacement; }						);						return text;					};					var puncRegex = /((?:%%\d+%%)+)([\.\,\;\:\"]{1,3})/g;					var reorder = function(match, capture1, capture2)					{						count += 1;						var replacement = capture2 + capture1;						replacements.push(replacement);						return replacement;					};					var fixPunctuationPlacement = function(text)					{						while ( puncRegex.test(text) )							text = text.replace(								/\s*((?:%%\d+%%)+)\s*([\.\,\;\:\"]{1,3})/g, reorder );						return text;					};					/*	Escape various things:						ref tags				*/					content = escape( content, "]+\\/>" );					content = escape( content, "]*>[^<]+" );					// citation needed					content = escape( content, "\\{\\{(?:[Cc]itation needed|[Cc]n|[Ff]act|[Cc]b|[Cc]tn|[Rr]ef\\?)\\|[^\}]+\\}\\}" );					// "dubious"					content = escape( content, "\\{\\{(?:[Dd]ubious)\\|[^\}]+\\}\\}" );					content = fixPunctuationPlacement(content);					// footnote templates					/*	Handles up to one level of nested templates.						Any more, and there may be problems.			*/					content = escape( content, "\\{\\{(?:sfn|efn|rfn)\\|(?:[^\\}]*?(?:\\{\\{[^\\}]+\\}\\})?)+\\}\\}" );					if ( i > 0 )						mw.notify(i + " refs or tagging templates found.");					content = fixPunctuationPlacement(content);					if ( count === 0 )						mw.notify("No misplaced refs or tagging templates were found.");					else						mw.notify(count + " correction" + ( ( count > 1 && "s" ) || "" ) + " made: " + replacements.join);					/*	Unescape the various things escaped above.						This has to be done twice, since escaping was done twice.	*/					content = content.replace( /%%(\d+)%%/g, function(wholematch, number) { number = Number(number); return escaped[number]; }					);					content = content.replace( /%%(\d+)%%/g, function(wholematch, number) { number = Number(number); return escaped[number]; }					);					addSummary(count, "made sure refs are after punctuation as prescribed by WP:REFPUNC");					return content;				}		},		// From User:Erutuon/scripts/imageSize.js.		{			textBoxIncludes: /[=|]\s*\d+px/,			button: {				text: "convert pixel to scaling size",			},			minorEdit: true,			watch: false,			func:				function(content)				{					var count1 = 0;					var count2 = 0;					// earlier regex: /\[\[(?:[Ff]ile|[Ii]mage)(?:[^\]]+|\[+[^\]]+\]+)+\]\]/g					// improved version: /\[\[(?:[Ff]ile|[Ii]mage)(?:[^\[\]\n]+|\[+[^\[\[\n]+\]+)+\]\]/g					// version less prone to backtracking errors: /\[\[(?:[Ff]ile|[Ii]mage).+/g					content = content.replace( /\[\[(?:[Ff]ile|[Ii]mage).+/g, function(wholematch) {							var convertedPixels = false; wholematch = wholematch.replace(								/\|\s*(\d+)px\s*(?=[\|\]])/g,								function(match, number)								{									// Convert string to number.									number = Number(number);									if ( number > 99 )									{										// Convert to upright value.										number = number / 220;										// Round to nearest hundredth.										number = Math.floor( number * 100 ) / 100;										count1++;										if ( number === 1 )											return "";										else {											convertedPixels = true;											return "|upright=" + number;										}									}									// Do not convert pixel value if it is less than 100 pixels.									else										return match;								}							); if ( convertedPixels && !wholematch.includes("thumb") ) wholematch = wholematch.replace(									/\]\]/g,									function									{										count2++;										return "|frameless]]";									}								); return wholematch; }					);					content = content.replace( /\{\{(?:(?:[Aa]utomatic )?[Tt]axo|[Ss]pecies)box(?:[^\{\}]+|\{\{[^}]+\}\})+\}\}/g, function (template) { var taxon; template = template.replace(								/\|\s*([^=\|\}]+?)(\s*=\s*)((?:\[\^\+\]\]|\^\+\]|\{\{[^\}]+\}\}|[^\|\}\[\]]+)+)/g,								function (wholeMatch, paramName, between, paramValue) {									if (!paramName.includes("width"))										return wholeMatch;									var match = paramValue.match(/(\d+)px/);									// Convert string to number.									var number = Number(match[1]);									if ( number > 99 )									{										// Convert to upright value.										number = number / 220;										// Round to nearest hundredth.										number = Math.floor( number * 100 ) / 100;										count1++;										if ( number === 1 )											return ""; // Parameter is unnecessary.										else											paramValue = number + "\n";									}									if (!startsAndEndsWithWhitespace.test(between))										between = " = ";									paramName = paramName.replace("width", "upright");									return "| " + paramName + between + paramValue; })								.replace(/\|\}\}$/, "}}");							return template;						}); notifyReplacements(count1); var summary = "converted image sizes in pixels to scaling values, as prescribed by WP:IMGSIZE"; if ( count2 > 0 ) summary += ", changing images to frameless as needed"; addSummary(count1, summary); return content; }		},	];

$.when(		$.getScript("//en.wiktionary.org/w/index.php?title=User:Erutuon/scripts/CleanupButtons.js&action=raw&ctype=text/javascript"),		$.ready	).done(function {		const buttons = new CleanupButtons;

for ( const buttonInfo of cleanupFunctions ) if (CleanupButtons.evaluateConditions(buttonInfo.condition, buttonInfo.textBoxIncludes)) buttons.addButton(buttonInfo); }); }

//