MediaWiki:Common.js: Difference between revisions

Jump to navigation Jump to search
no edit summary
No edit summary
No edit summary
Line 782: Line 782:




// Put this in MediaWiki:Common.js
( function () {
( function () {
     function smartSingleQuotes(text) {
     'use strict';
        // Handle apostrophes in contractions or possessives first (like John's, it's)
        text = text.replace(/(\w)'(\w)/g, "$1’$2");


         // Then handle opening quotes (at start or after space/open punctuation)
    // Convert single quotes in a text string to smart quotes
         text = text.replace(/(^|[\s\(\[\{\"‘“])/g, function(match) {
    function convertSingleQuotes(text) {
            return match; // just return the same, will handle in next step
         if (!text || text.indexOf("'") === -1) return text;
         });
 
        // 1) Protect contractions/possessives like "don't" or "John's"
        text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2");
 
        // 2) Replace opening single quotes:
        // Replace a straight quote that occurs at start of string or after whitespace/opening punctuation
        // with an opening single quote ‘
         text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘");
 
        // 3) Remaining single quotes are closing quotes ’
         text = text.replace(/'/g, "’");


         // Now process alternation: alternate between opening ‘ and closing ’
         return text;
        let result = "";
        let isOpen = true;
        for (let char of text) {
            if (char === "'") {
                result += isOpen ? "‘" : "’";
                isOpen = !isOpen;
            } else {
                result += char;
            }
        }
        return result;
     }
     }


    // Walk text nodes under root and replace text content
     function walkAndReplace(root) {
     function walkAndReplace(root) {
         const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
         if (!root) return;
         const blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH"]);
        var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
         var node;
        // Tags to ignore entirely
        var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]);


        let node;
         while ((node = walker.nextNode())) {
         while ((node = walker.nextNode())) {
             let parent = node.parentNode;
             var parent = node.parentNode;
             if (!parent || blacklist.has(parent.nodeName)) continue;
             if (!parent) continue;
 
            // If any ancestor is blacklisted, skip this node
            var skip = false;
            var anc = parent;
            while (anc && anc !== document && anc.nodeType === 1) {
                if (blacklist.has(anc.nodeName)) { skip = true; break; }
                anc = anc.parentNode;
            }
            if (skip) continue;


             let newText = smartSingleQuotes(node.nodeValue);
             var original = node.nodeValue;
             if (newText !== node.nodeValue) node.nodeValue = newText;
            var replaced = convertSingleQuotes(original);
             if (replaced !== original) node.nodeValue = replaced;
         }
         }
     }
     }


     mw.hook("wikipage.content").add(function ($content) {
     // Run on initial content
        walkAndReplace($content[0]);
    function processAll() {
     });
        walkAndReplace(document.body);
    }
 
    // Handle dynamic content (AJAX navigation)
    function initObserver() {
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(m) {
                if (m.addedNodes && m.addedNodes.length) {
                    m.addedNodes.forEach(function(n) {
                        if (n.nodeType === 1) { // element
                            walkAndReplace(n);
                        } else if (n.nodeType === 3) { // text node
                            // handle single text node's parent element
                            var parent = n.parentNode;
                            if (parent) walkAndReplace(parent);
                        }
                    });
                }
            });
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }
 
    // Use MediaWiki hooks if available, otherwise fallback to DOMContentLoaded
    if (window.mw && mw.hook) {
        mw.hook('wikipage.content').add(function($content) { walkAndReplace($content[0]); });
        mw.loader.using(['mediawiki.util']).then(function(){ processAll(); initObserver(); });
     } else {
        document.addEventListener('DOMContentLoaded', function () { processAll(); initObserver(); });
    }


    document.addEventListener("DOMContentLoaded", function () {
        walkAndReplace(document.body);
    });
})();
})();

Navigation menu