Jump to content

User:Polygnotus/Scripts/Signatures.js

From Wikipedia, the free encyclopedia
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.
// <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>