User:Daniel Quinlan/Scripts/Vanilla.js

"use strict";

mw.loader.using(['mediawiki.util', 'user.options']).then(function {	const SIGNATURE_NAMESPACES = new Set([1, 3, 4, 5, 7, 9, 11, 13, 15, 101, 119, 711, 829]);	const SIGNATURE_ELEMENTS = new Set(['B', 'BDI', 'BIG', 'BR', 'CODE', 'EM', 'FONT', 'I', 'IMG', 'INS', 'KBD', 'SMALL', 'S', 'SAMP', 'SPAN', 'STRONG', 'SUB', 'SUP', 'U']);	const SIGNATURE_SPAN_CLASSES = new Set([null, 'FTTCmt', 'ext-discussiontools-init-replylink-buttons', 'nickname', 'nowrap', 'vcard']);	const SERVER_PREFIX = 'https:' + mw.config.get('wgServer');	const ARTICLE_PATH = mw.config.get('wgArticlePath').replace(/\$1/, );	const SCRIPT_PATH = mw.config.get('wgScript') + '?title=';	const SIGNATURE_LINKS = ['User:', 'User_talk:', 'Special:Contributions/', 'Special:Log/', 'Special:EmailUser/'];	const SIGNATURE_DELIMITERS = new Set([, '#', '&', '/', '?']);	const SIGNATURE_PARAMS = new Set(['action', 'redlink', 'safemode', 'title']); const AUTHOR_REGEX = /^c-(.*?)-\d{4}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])(?:[01]\d|2[0-3])[0-5]\d[0-5]\d(?:\-|$)/; const DEBUG = mw.user.options.get('userjs-vanilla') === 'debug';

function extractAuthor(span) { const match = span.getAttribute('data-mw-comment-end').match(AUTHOR_REGEX);

if (match && match[1]) { return match[1]; }		return ''; }

function isUserPage(node, authorString) { let url = node.href;

if (url.startsWith(SERVER_PREFIX)) { url = url.substring(SERVER_PREFIX.length); } else if (node.classList.contains('extiw')) { try { const urlObject = new URL(url); url = urlObject.pathname + urlObject.search + urlObject.hash; } catch (error) { console.warn(`Vanilla error parsing "${url}":`, error); return false; }		}		if (url.startsWith(ARTICLE_PATH)) { url = url.substring(ARTICLE_PATH.length); } else if (url.startsWith(SCRIPT_PATH)) { url = url.substring(SCRIPT_PATH.length); } else { return false; }		for (const prefix of SIGNATURE_LINKS) { if (url.startsWith(prefix)) { url = url.substring(prefix.length); if (url.includes('%')) { try { const decoded = decodeURIComponent(url); url = decoded; } catch (error) { console.warn(`Vanilla error decoding "${url}":`, error); return false; }				}				if (url.toLowerCase.startsWith(authorString)) { url = url.substring(authorString.length); if (!SIGNATURE_DELIMITERS.has(url.charAt(0))) { return false; }					const paramsIndex = node.href.indexOf('?'); if (paramsIndex !== -1) { const queryString = node.href.substring(paramsIndex + 1); const queryParams = new URLSearchParams(queryString); if ([...queryParams.keys].some(key => !SIGNATURE_PARAMS.has(key))) { return false; }					}					return true; }				return false; }		}		return false; }

function reviewTextNode(node, author) { let parentNode = node.parentNode;

while (parentNode) { if (parentNode.nodeType === Node.ELEMENT_NODE) { if (parentNode.tagName === 'A') { return null; }				if (parentNode.tagName === 'DIV') { break; }			}			parentNode = parentNode.parentNode; }

const anchor = document.createElement('a'); parentNode = node.parentNode; anchor.href = `/wiki/User:${author}`; anchor.textContent = node.nodeValue; parentNode.insertBefore(anchor, node); parentNode.removeChild(node); return parentNode; }

function signatureNodes(endNode, author) { const authorString = author.toLowerCase.replace(/ /g, '_'); const conditions = {}; const matchedNodes = []; const authorNodes = []; const styleNodes = []; let textLength = 0;

function checkNode(node) { const childNodes = node.childNodes;

for (let i = childNodes.length - 1; i >= 0; i--) { if (!checkNode(childNodes[i])) { return false; }			}

if (node.nodeType === Node.TEXT_NODE) { textLength += node.nodeValue.length; if (textLength > 120) { return false; }				if (node.nodeValue.toLowerCase.includes(authorString)) { authorNodes.push(node); }			} else if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === 'A') { if (isUserPage(node, authorString)) { conditions.userPage = true; } else if (node.href) { // allow other links until user page seen if (conditions.userPage) { return false; }					} else { return false; }				} else if (node.tagName === 'SPAN') { if (node.hasAttribute('data-mw-comment-start')) { return false; }					if (!SIGNATURE_SPAN_CLASSES.has(node.getAttribute('class'))) { return false; }				} else if (!SIGNATURE_ELEMENTS.has(node.tagName)) { return false; }			} else { return false; }

if (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute('style')) { styleNodes.push(node); }			return true; }

let node = endNode.previousSibling; if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute('class') === 'ext-discussiontools-init-replylink-buttons') { node = node.previousSibling; }		while (node) { if (checkNode(node)) { matchedNodes.unshift(node); } else { break; }			node = node.previousSibling; }		if (matchedNodes && conditions.userPage) { // remove style attributes styleNodes.forEach(node => node.removeAttribute('style')); // review authorNodes authorNodes.forEach(node => {				const modified = reviewTextNode(node, author);				if (modified) {					mw.hook('wikipage.content').fire($(modified));				}			}); return matchedNodes; }		return null; }

function nodesHTML(nodes) { const container = document.createElement('span'); nodes.forEach(node => {			container.appendChild(node.cloneNode(true));		}); return container.innerHTML.replace(/\n/g, ''); }

function execute($content) { if (!SIGNATURE_NAMESPACES.has(mw.config.get('wgNamespaceNumber'))) { return; }		$content[0].querySelectorAll('span[data-mw-comment-end]').forEach(span => {			const author = extractAuthor(span);			if (author) {				const matchedNodes = signatureNodes(span, author);				if (DEBUG) {					console.log(author, matchedNodes ? nodesHTML(matchedNodes) : 'none');				}			}		}); }

mw.hook('wikipage.content').add(execute); });