User:Andrybak/Scripts/Unsigned generator.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
(function() {
	'use strict';

	const USERSCRIPT_NAME = 'Unsigned generator';
	const VERSION = 2;
	const locale = {
		portletText: "Unsigned gen", // short to keep the "Tools" menu narrow
		portletTooltip: "Generate template for unsigned discussion messages",
		template: "subst:Unsigned", // see [[Template:Unsigned]]
		templateIp: "subst:Unsigned IP", // see [[Template:Unsigned IP]]
		prefixText: "Unsigned wikitext: ",
		copyButtonText: "Copy",
		errorTimestamp: "Cannot find the timestamp of the edit. Aborting.",
		errorAuthor: "Cannot find the author of the edit. Aborting.",
	}
	// <nowiki>
	const LOG_PREFIX = `[${USERSCRIPT_NAME} v${VERSION}]:`;
	const mw = window.mw; // a hack to trick my JS editor into believing that `mw` exists
	const USERSCRIPT_OUTPUT_ID = 'userscript-unsigned-generator';

	function error(...toLog) {
		console.error(LOG_PREFIX, ...toLog);
	}

	function warn(...toLog) {
		console.warn(LOG_PREFIX, ...toLog);
	}

	function info(...toLog) {
		console.info(LOG_PREFIX, ...toLog);
	}

	function debug(...toLog) {
		console.debug(LOG_PREFIX, ...toLog);
	}

	function notify(notificationMessage) {
		mw.notify(notificationMessage, {
			title: USERSCRIPT_NAME
		});
	}

	function errorAndNotify(errorMessage, exception) {
		error(errorMessage, exception);
		notify(errorMessage);
	}

	function createTimestampWikitext(timestamp) {
		// https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time_format_like_in_signatures
		return '{{' + `subst:#time:H:i, j xg Y "(UTC)"|${timestamp}}}`;
	}

	function createWikitext(template, user, timestamp) {
		const timestampWikitext = createTimestampWikitext(timestamp);
		return `{{${template}|${user}|${timestampWikitext}}}`;
	}

	/*
	 * Adapted from [[User:Enterprisey/diff-permalink.js]]
	 * https://en.wikipedia.org/wiki/User:Enterprisey/diff-permalink.js
	 */
	function showWikitextAboveDiff(wikitext) {
		const wikitextInput = document.createElement('input');
		wikitextInput.id = USERSCRIPT_OUTPUT_ID;
		wikitextInput.value = wikitext;
		wikitextInput.style.fontFamily = 'monospace';
		wikitextInput.setAttribute('size', wikitext.length);

		const copyButton = document.createElement('button');
		copyButton.textContent = locale.copyButtonText;
		copyButton.style.padding = '0.5em';
		copyButton.style.cursor = 'pointer';
		copyButton.style.marginLeft = '0.5em';
		copyButton.onclick = () => {
			document.getElementById(USERSCRIPT_OUTPUT_ID).select();
			document.execCommand('copy');
		};

		const container = document.createElement('div');
		container.appendChild(document.createTextNode(locale.prefixText));
		container.appendChild(wikitextInput);
		container.appendChild(copyButton);
		document.getElementById('bodyContent').prepend(container);
	}

	/*
	 * The main function of the script.
	 */
	function runPortlet() {
		/*
		 * Reference documentation about keys and values in mw.config:
		 * https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config
		 */
		const diffTimestampElement = document.querySelector('#mw-diff-ntitle1 .mw-diff-timestamp');
		if (!diffTimestampElement) {
			errorAndNotify(locale.errorTimestamp);
			return;
		}
		const mwUserlink = document.querySelector('#mw-diff-ntitle2 .mw-userlink');
		if (!mwUserlink) {
			errorAndNotify(locale.errorAuthor);
			return;
		}
		const template = mwUserlink.classList.contains('mw-anonuserlink') ? locale.templateIp : locale.template;
		const usernameOrIp = mwUserlink.innerText;
		const isoTimestamp = diffTimestampElement.getAttribute('data-timestamp');
		const wikitext = createWikitext(template, usernameOrIp, isoTimestamp);
		info(wikitext);
		showWikitextAboveDiff(wikitext);
	}

	function wait(message) {
		info(message);
		setTimeout(lazyLoadUnsignedGenerator, 200);
	}

	/*
	 * Infrastructure to ensure the script can run.
	 */
	function lazyLoadUnsignedGenerator() {
		if (mw.config.get('wgDiffNewId') == null && mw.config.get('wgDiffOldId') == null) {
			/*
			 * TODO: Not supported is the use case of the talk page being
			 *       created with an unsigned message.
             *       It's harder in such cases to get to the diff view via the GUI.
			 */
			info('Not a diff view. Aborting.');
			return;
		}
		const namespaceId = mw.config.get("wgNamespaceNumber");
		if (namespaceId % 2 == 0 && namespaceId != 4) {
			// not a talk page and not project namespace
			info('Not a discussion namespace. Aborting.');
			return;
		}
		if (!mw.loader.using) {
			wait('Function mw.loader.using is no loaded yet. Waiting...');
			return;
		}
		debug('Loading...');
		mw.loader.using(
			['mediawiki.util'],
			() => {
				const link = mw.util.addPortletLink('p-cactions', '#', locale.portletText, 'ca-unsigned-generator', locale.portletTooltip);
				if (!link) {
					info('Cannot create portlet link (mw.util.addPortletLink). Assuming unsupported skin. Aborting.');
					return;
				}
				link.onclick = event => {
					mw.loader.using('mediawiki.api', runPortlet);
				};
			},
			(e) => {
				error('Cannot add portlet link', e);
			}
		);
	}

	if (document.readyState !== 'loading') {
		lazyLoadUnsignedGenerator();
	} else {
		warn('Cannot load yet. Setting up a listener...');
		document.addEventListener('DOMContentLoaded', lazyLoadUnsignedGenerator);
	}
	// </nowiki>
})();