snahp - Add Decode Button

Adds a 'decode' button to code elements. Can be clicked repeatedly.

As of 2021-09-21. See the latest version.

// ==UserScript==
// @name         snahp - Add Decode Button
// @namespace    forum.snahp.it
// @version      0.4.2
// @description  Adds a 'decode' button to code elements. Can be clicked repeatedly.
// @author       Genome
// @homepage     https://fora.snahp.eu/viewtopic.php?p=1161800
// @match        https://fora.snahp.eu/viewtopic.php*
// @match        https://forum.snahp.it/viewtopic.php*
// @icon         https://www.google.com/s2/favicons?domain=fora.snahp.eu
// @run-at       document-idle
// @grant        none
// ==/UserScript==

/*jshint esversion: 6 */

(function() {
	'use strict';

	let base64Rx = /([a-z0-9+\/]{20,}[=]*)/i;

	// Decodes any (likely) base64 strings line by line surgically.
	function base64DecodeByLine(targetText) {
		let textLines = targetText.split("\n");
		let completeLines = [];
		for(let textLine of textLines) {
			if(base64Rx.test(textLine)) {
				let findResult = base64Rx.exec(textLine);
				let b64Text = findResult[1];
				let targetIx = findResult.index;
				let completeLine = textLine.substring(0, targetIx) + atob(b64Text) + textLine.substring(targetIx + b64Text.length);
				completeLines.push(completeLine);
			} else {
				completeLines.push(textLine);
			}
		}
		return completeLines.join("\n");
	}

	// Decodes the innerText of a passed in DOM element.
	function base64DecodeContents(el) {
		if(el && el.innerText.trim() !== "") {
			el.innerText = base64DecodeByLine(el.innerText);
		}
	}

	// Copies the innerText of a given html element.
	function copyContents(el) {
		navigator.clipboard.writeText(el.innerText.trim());
	}

	// Finds hideboxes with no internal <codebox> element (aka they are bare text).
	function findBareUnhideBoxes() {
		let finalElems = []
		for (const el of document.querySelectorAll("dl.hidebox.unhide")) {
			if(el.querySelector('div.codebox') == null) {
				finalElems.push(el);
			}
		}
		return finalElems;
	}

	// Create an <a> element and quickly assign some properties (text, any classes, hardcoded style, and a 'click' action)
	function createButton(buttonText, classList, hardStyle, onClickCallback) {
		let newButton = document.createElement('a');
		newButton.innerText = buttonText;
		if(classList) classList.forEach(cl => newButton.classList.add(cl))
		if(hardStyle) newButton.style = hardStyle;
		if(onClickCallback) newButton.addEventListener("click", onClickCallback);
		return newButton;
	}

	const bareBoxButtonStyle = "margin: 0 8px 0 8px; font-weight: bold;";
	const bareBoxWrapperStyle = "float: right; text-align: right; border: 1px inset white;";

	// Adds a controls div with buttons to hideboxes WITH NO Codebox inside. Aka BareBox. (so they are bare text)
	function addBareBoxButtons() {
		// Find 'dl.hidebox.unhide' boxes with no internal codebox.
		let boxElems = findBareUnhideBoxes();
		for (let boxElem of boxElems) {
			// Create a decode and copy button and make their click actions refer to this specific box.
			let decode = createButton("Decode", ["pointer", "noselect"], bareBoxButtonStyle, () => base64DecodeContents(boxElem));
			let copy = createButton("Copy", ["pointer", "noselect"], bareBoxButtonStyle, () => copyContents(boxElem));
			// Create a wrapper div to hold both and position it next to the BareBox.
			let newControlsWrapper = document.createElement('div');
			newControlsWrapper.append(decode, copy);
			newControlsWrapper.style = bareBoxWrapperStyle;
			boxElem.insertAdjacentElement("afterend", newControlsWrapper);
		}
	}

	// Creates 'Decode' button for Codebox elements.
	function addCodeboxButtons() {
		// Find all the 'copy' buttons attached to code-boxes on the page.
		let codeboxCopyButtons = document.querySelectorAll(".codebox p a:last-child");

		// For each of the code boxes.
		for(let copyButtonEl of codeboxCopyButtons) {
			// Find the related codebox (it's a cousin element).
			let codeboxElement = copyButtonEl.parentNode.parentNode.querySelector('pre code');

			// Create a new button and bind click event to decode.
			let newButton = createButton(
				"Decode", ["pointer", "noselect"], "float: right; margin-right: 8px; margin-left: 8px;",
				() => base64DecodeContents(codeboxElement)
			);

			copyButtonEl.parentNode.appendChild(newButton);
		}
	}

	addCodeboxButtons();
	addBareBoxButtons();

})();