User:Erutuon/scripts/CleanupButtons.js

Note – after saving, you may have to bypass your browser’s cache to see the changes.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

/*
	A framework for inserting 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, or the original content
	is saved and compared to the modified version.
	
	An edit summary is added if the change was done. If two operations have been
	performed, their edit summaries are combined in a grammatical way.
*/

/* jshint eqeqeq: true, varstmt: true, unused: true, undef: true */
/* globals $, mw */

(function () {

"use strict";

const $textBox = $("#wpTextbox1");
const $minorEditCheck = $("#wpMinoredit");
const $editForm = $("#editform");
const cleanupButtonWrapperId = "wikitext-cleanup-button-wrapper";
const cleanupButtonClass = "wikitext-cleanup-button";

window.importStylesheetURI("//en.wiktionary.org/w/index.php?title=User:Erutuon/styles/wikitext-cleanup.css&action=raw&ctype=text/css");


// Template for button object, the first argument to cleanupButtons.addButton
// method.
const cleanupButtonTemplate = {
	condition: false,
	textBoxIncludes: "",
	button: {
		text: "",
	},
	minorEdit: false,
	func:
		function (content)
		{
			const oldContent = content;
			let count = 0;
			
			content = content.replace(
				//g,
				function(wholeMatch)
				{
					++count;
					
					return wholeMatch;
				}
			);
				
			CleanupButtons.notifyReplacements(count);
			CleanupButtons.addSummary(count, "");
			CleanupButtons.addSummary(content !== oldContent, "");
			
			return content;
		}
};

function newCleanupButton(text, id) {
	if ( typeof text === "function" ) {
		text = text($textBox.val());
		
		if ( typeof text !== "string" )
			throw new TypeError("'text' function should return a string.");
	}
	
	return $("<div>").addClass(cleanupButtonClass).attr("id", id).html(text);
}

const CleanupButtons = function () {};
window.CleanupButtons = CleanupButtons;

CleanupButtons.prototype = {
	get $wrapper () {
		let wrapper = $("#" + cleanupButtonWrapperId);
		if (wrapper.length === 0)
				wrapper = $("<div>").attr("id", cleanupButtonWrapperId).insertBefore($editForm);
		return wrapper;
	},
	get $buttons () {
		return this.$wrapper.children();
	},
};

CleanupButtons.prototype.createCallback = function(func, minorEdit) {
	return function () {
		const oldContent = $textBox.val();
		
		try {
			const newContent = func(oldContent);
			if ( typeof newContent === "string" ) {
				$textBox.val(newContent);
				
				if ( minorEdit === true && newContent !== oldContent )
					$minorEditCheck.prop("checked", true);
			} else {
				console.error("Callback did not return a string:", newContent);
			}
		} catch (e) {
			console.error("Error in callback:", e);
		}
	};
};

CleanupButtons.prototype.addButton = function ({ func, minorEdit, funcName, button: { text, id} }) {
	const buttonCount = this.$buttons.length;
	text = text || "button" + (buttonCount + 1);
	id = id || "button-id-" + (buttonCount + 1);
	
	const button = newCleanupButton(text, id);
	
	if ( typeof func !== "function" )
		throw new TypeError("Expected function");
	
	button.click( this.createCallback(func, minorEdit) );
	
	this.$wrapper.append(button);
};

CleanupButtons.evaluateConditions = function (...conditions) {
	// Conditions may be boolean, undefined, null, strings, or RegExp.
	if ($textBox.length === 0) {
		console.info("edit box (#wpTextbox1) not found");
		return false;
	}
	const textBoxContent = $textBox.val();
	
	if (conditions.length === 0)
		return false; // ???
	
	// finalResolution is true if all conditions evaluate to true or null or
	// undefined and at least one condition evaluated to true.
	let resolvedToTrue = false;
	return conditions.every((condition, index) => {
		let resolution;
		switch (typeof condition) {
			case "boolean": case "undefined":
				resolution = condition;
				break;
			case "string":
				resolution = textBoxContent.includes(condition);
				break;
			case "function":
				resolution = condition($("#wpTextbox1").val());
				break;
			case "object":
				if (condition instanceof RegExp) {
					resolution = condition.test(textBoxContent);
					break;
				} else if (condition === null) {
					resolution = undefined;
					break;
				} /* falls through */
			default:
				return console.error("Unrecognized type of condition number "
					+ (index + 1) + ": " + typeof condition);
		}
		
		if (resolution === true)
			resolvedToTrue = true;
		
		return resolution === undefined || resolution === true;
	}) && resolvedToTrue;
};

CleanupButtons.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.");
};

CleanupButtons.addSummary = function(count, summary) {
	let changed;
	
	switch (typeof count) {
		case "number":
			changed = count > 0;
			break;
		case "boolean":
			changed = count;
			break;
		default:
			console.log("Type of count was not recognized: " + typeof count + ".");
	}
	
	if ( changed ) {
		$("#wpSummary").val(
			function(index, content) {
				const afterSectionName = content.match(/^(?:\/\*[^*]+\*\/)?\s*(.*?)$/);
				
				const scriptMention = ", with the help of [[User:Erutuon/scripts/cleanup.js|JavaScript]]";
				
				let 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)
				|| /\[\[[^|\]]+\.js\|/.test(summary) ) )
					content += scriptMention;
				
				return content;
			}
		);
	}
	else
		console.log("No edit summary added.");
};

})();