User:Polygnotus/Scripts/Signatures.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/Signatures.
// <nowiki>
// Signature normalizer for Wikipedia common.js
// Replaces non-standard signatures with a clean "— Username (talk · contribs) timestamp" format.
// Requires DiscussionTools to be active (default on all Wikimedia wikis since ~2023).
mw.loader.using(['mediawiki.util'], function () {
'use strict';
// Only run on page views, not on edit pages etc.
if (mw.config.get('wgAction') !== 'view') return;
// Namespace check: only run on talk pages and Wikipedia: discussion pages.
// Even-numbered namespaces are content pages; odd-numbered are talk pages.
// Namespace 4 = Wikipedia:, namespace 0 = mainspace (skip that).
var ns = mw.config.get('wgNamespaceNumber');
if (ns === 0) return;
/**
* Extracts a username from a User: or User_talk: href string.
* Returns null if not a user link.
*/
function usernameFromHref(href) {
var m = /\/wiki\/User(?:_talk)?:([^#?&/]+)/.exec(href);
if (!m) return null;
return decodeURIComponent(m[1]).replace(/_/g, ' ');
}
/**
* Checks whether a signature is already "standard" enough to leave alone.
* A standard signature is one whose visible text is basically just the
* username and "(talk)", with no extra styling or unusual text.
*/
function isAlreadyStandard(nodes, username) {
// Collect all text content from the signature nodes
var text = nodes.map(function (n) {
return n.textContent || '';
}).join('').trim();
// Strip common punctuation/whitespace around the username and talk link
var stripped = text.replace(/^\s*[-–—\u2014\u2013]\s*/, '') // leading dash
.replace(/\s*\(talk\)\s*$/i, '') // trailing "(talk)"
.trim();
return stripped === username;
}
// Find all DiscussionTools signature markers in the page
var sigMarkers = document.querySelectorAll('[data-mw-comment-sig]');
sigMarkers.forEach(function (sigMarker) {
// Collect all sibling nodes that belong to the signature.
// The signature runs from the sig marker up to (but not including)
// the timestamp link and the reply button widget.
var sigNodes = [];
var timestampLink = null;
var node = sigMarker.nextSibling;
while (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
// The timestamp link ends the signature
if (node.classList.contains('ext-discussiontools-init-timestamplink')) {
timestampLink = node;
break;
}
// The reply buttons widget also ends the signature
if (node.classList.contains('ext-discussiontools-init-replylink-buttons')) {
break;
}
}
sigNodes.push(node);
node = node.nextSibling;
}
// Nothing to replace if there are no signature nodes
if (sigNodes.length === 0) return;
// Find the username by scanning anchor tags inside the signature nodes.
// We prefer User: links over User_talk: links.
var username = null;
var userHref = null;
for (var i = 0; i < sigNodes.length; i++) {
var n = sigNodes[i];
var anchors = [];
if (n.nodeType === Node.ELEMENT_NODE) {
if (n.tagName === 'A') anchors.push(n);
// Also check descendants
var children = n.querySelectorAll('a');
for (var j = 0; j < children.length; j++) anchors.push(children[j]);
}
for (var k = 0; k < anchors.length; k++) {
var href = anchors[k].getAttribute('href') || '';
// Prefer User: (not User_talk:) for the display link
if (/\/wiki\/User:/.test(href)) {
username = usernameFromHref(href);
userHref = href.split('#')[0]; // strip fragment
break;
}
}
if (username) break;
}
// If we still haven't found one, try User_talk: links as a fallback
if (!username) {
for (var i = 0; i < sigNodes.length; i++) {
var n = sigNodes[i];
if (n.nodeType === Node.ELEMENT_NODE) {
var anchors = n.tagName === 'A' ? [n] : Array.prototype.slice.call(n.querySelectorAll('a'));
for (var k = 0; k < anchors.length; k++) {
var href = anchors[k].getAttribute('href') || '';
if (/\/wiki\/User_talk:/.test(href)) {
username = usernameFromHref(href);
// Derive the User: page href from the User_talk: href
userHref = href.replace('/wiki/User_talk:', '/wiki/User:').split('#')[0];
break;
}
}
}
if (username) break;
}
}
// Can't determine the username — leave this signature alone
if (!username) return;
// If the signature is already standard, leave it alone
if (isAlreadyStandard(sigNodes, username)) return;
// Build the normalized signature fragment:
// — Username (talk · contribs)
var encodedName = encodeURIComponent(username.replace(/ /g, '_'));
var talkHref = '/wiki/User_talk:' + encodedName;
var contribHref = '/wiki/Special:Contributions/' + encodedName;
var frag = document.createDocumentFragment();
frag.appendChild(document.createTextNode('\u00a0\u2014\u00a0')); // non-breaking space + em dash + nbsp
var nameLink = document.createElement('a');
nameLink.href = userHref || ('/wiki/User:' + encodedName);
nameLink.textContent = username;
frag.appendChild(nameLink);
frag.appendChild(document.createTextNode('\u00a0('));
var talkLink = document.createElement('a');
talkLink.href = talkHref;
talkLink.textContent = 'talk';
frag.appendChild(talkLink);
frag.appendChild(document.createTextNode('\u00a0\u00b7\u00a0')); // nbsp · nbsp
var contribLink = document.createElement('a');
contribLink.href = contribHref;
contribLink.textContent = 'contribs';
frag.appendChild(contribLink);
frag.appendChild(document.createTextNode(')\u00a0'));
// Remove the original signature nodes and insert the normalized one
var parent = sigMarker.parentNode;
var insertBefore = timestampLink || sigMarker.nextSibling;
sigNodes.forEach(function (n) {
if (n.parentNode) n.parentNode.removeChild(n);
});
parent.insertBefore(frag, insertBefore);
});
});
// </nowiki>