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:




( function (window, document, mw) {
// MediaWiki:Common.js
// Smart single-quote substitution (fixed) - ver 1.3
 
( function ( window, document, mw ) {
     'use strict';
     'use strict';


     // Expose a minimal API for testing
     // Minimal safety: if mw is missing, bail out gracefully
     window.smartQuotes = window.smartQuotes || {};
     if ( typeof mw === 'undefined' ) {
        console.warn('smartQuotes: mw is not available; aborting.');
        return;
    }


     function convertSingleQuotes(text) {
     // Ensure we load when ResourceLoader is ready
        if (!text || text.indexOf("'") === -1) return text;
    mw.loader.using([], function () {
        // Protect contractions/possessives like don't or John's
 
        text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2");
         // Utility: are we anonymous? (avoids using mw.user.isAnon)
         // Opening quote when at start or after whitespace or open punctuation
         function isAnon() {
         text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘");
            var wgUserId = mw.config.get('wgUserId');
        // Remaining single quotes are closing
            // wgUserId is 0 or null for anonymous users in many setups
        text = text.replace(/'/g, "’");
            return !wgUserId || wgUserId === 0;
         return text;
         }
    }


    function walkAndReplace(root) {
        // Convert single quotes -> smart quotes in a string
        if (!root) return;
        function convertSingleQuotes(text) {
        var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
            if (!text || text.indexOf("'") === -1) return text;
        var node;
            // Protect contractions/possessives like don't or John's
        var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]);
            text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2");
            // Opening single quote when at start or after whitespace or opening punctuation
            text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘");
            // Remaining single quotes are closing
            text = text.replace(/'/g, "");
            return text;
        }


         while ((node = walker.nextNode())) {
         // Walk text nodes under root and replace text content (skip code-like tags)
            var parent = node.parentNode;
        function walkAndReplace(root) {
            if (!parent) continue;
            try {
                if (!root) return;
                var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
                var node;
                var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]);


            // Skip nodes with blacklisted ancestor
                while ((node = walker.nextNode())) {
            var anc = parent, skip = false;
                    var parent = node.parentNode;
            while (anc && anc.nodeType === 1) {
                    if (!parent) continue;
                if (blacklist.has(anc.nodeName)) { skip = true; break; }
                    // skip if any ancestor is blacklisted
                anc = anc.parentNode;
                    var anc = parent, skip = false;
                    while (anc && anc.nodeType === 1) {
                        if (blacklist.has(anc.nodeName)) { skip = true; break; }
                        anc = anc.parentNode;
                    }
                    if (skip) continue;
                    var original = node.nodeValue;
                    var replaced = convertSingleQuotes(original);
                    if (replaced !== original) node.nodeValue = replaced;
                }
            } catch (e) {
                console.error('smartQuotes.walkAndReplace error:', e);
             }
             }
            if (skip) continue;
            var original = node.nodeValue;
            var replaced = convertSingleQuotes(original);
            if (replaced !== original) node.nodeValue = replaced;
         }
         }
    }


    // Kick off on initial DOM and expose functions for debugging
        // Process initial content
    function processAll() {
        function processAll() {
        try {
            try {
            var root = document.querySelector('.mw-parser-output') || document.body;
                var root = document.querySelector('.mw-parser-output') || document.body;
            walkAndReplace(root);
                walkAndReplace(root);
            console.info('smartQuotes: processAll completed.');
                console.info('smartQuotes: processAll completed.');
        } catch (e) {
            } catch (e) {
            console.error('smartQuotes processAll error:', e);
                console.error('smartQuotes.processAll error:', e);
            }
         }
         }
    }


    // MutationObserver for dynamic content
        // Observer for dynamic content
    function initObserver() {
        function initObserver() {
        try {
            try {
            var observer = new MutationObserver(function (mutations) {
                var observer = new MutationObserver(function (mutations) {
                mutations.forEach(function (m) {
                    mutations.forEach(function (m) {
                    if (m.addedNodes && m.addedNodes.length) {
                        if (m.addedNodes && m.addedNodes.length) {
                        m.addedNodes.forEach(function (n) {
                            m.addedNodes.forEach(function (n) {
                            if (n.nodeType === 1) walkAndReplace(n);
                                if (n.nodeType === 1) walkAndReplace(n);
                            else if (n.nodeType === 3 && n.parentNode) walkAndReplace(n.parentNode);
                                else if (n.nodeType === 3 && n.parentNode) walkAndReplace(n.parentNode);
                        });
                            });
                     }
                        }
                     });
                 });
                 });
            });
                observer.observe(document.body, { childList: true, subtree: true });
            observer.observe(document.body, { childList: true, subtree: true });
                window.smartQuotes = window.smartQuotes || {};
            window.smartQuotes._observer = observer;
                window.smartQuotes._observer = observer;
            console.info('smartQuotes: MutationObserver inited.');
                console.info('smartQuotes: MutationObserver inited.');
        } catch (e) {
            } catch (e) {
            console.error('smartQuotes observer error:', e);
                console.error('smartQuotes.initObserver error:', e);
            }
         }
         }
    }
    // Expose a test utility to call from the console
    window.smartQuotes.test = function (input) {
        input = input || " 'Hello' It's John's 'quote' ";
        var out = convertSingleQuotes(input);
        console.log('smartQuotes.test input:', input);
        console.log('smartQuotes.test output:', out);
        return out;
    };


    // Init after ResourceLoader and DOM ready
        // Expose test function for console verification
    function init() {
        window.smartQuotes = window.smartQuotes || {};
        processAll();
        window.smartQuotes.test = function (input) {
        initObserver();
            input = input || " 'Hello' It's John's 'quote' ";
    }
            var out = convertSingleQuotes(input);
            console.log('smartQuotes.test input:', input);
            console.log('smartQuotes.test output:', out);
            return out;
        };


    if (mw && mw.loader) {
        // init after DOM ready
         mw.loader.using([], function () {
         function initWhenReady() {
             if (document.readyState === 'complete' || document.readyState === 'interactive') {
             if (document.readyState === 'complete' || document.readyState === 'interactive') {
                 init();
                 processAll();
                initObserver();
             } else {
             } else {
                 document.addEventListener('DOMContentLoaded', init);
                 document.addEventListener('DOMContentLoaded', function () {
                    processAll();
                    initObserver();
                });
             }
             }
         });
         }
    } else {
 
        // fallback
         initWhenReady();
        if (document.readyState === 'complete' || document.readyState === 'interactive') init();
         else document.addEventListener('DOMContentLoaded', init);
    }


     // Small heartbeat so we can see if script runs at all
     }); // mw.loader.using
    console.info('smartQuotes script loaded.');


})(window, document, window.mw);
})( window, document, window.mw );

Navigation menu