snahp - Add Decode Button

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

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();

})();