MediaWiki:Common.js: Difference between revisions

Jump to navigation Jump to search
1,905 bytes removed ,  06:12, 17 December 2025
Undo revision 104883 by Gurwinder (talk)
No edit summary
(Undo revision 104883 by Gurwinder (talk))
Tag: Undo
 
(6 intermediate revisions by the same user not shown)
Line 263: Line 263:




$( function(){
$(function () {
// toggled by toggle button. also determines which toggle button image to use
var useCustom = false;
var useCustom = false;
 
// toggleImgs[0] to switch to custom, toggleImgs[1] to revert to default
var zoomInSVG =
var toggleImgs = [
'<svg width="22" height="22" viewBox="0 0 24 24">' +
'//storage.googleapis.com/material-icons/external-assets/v4/icons/svg/ic_search_black_24px.svg',
'<path d="M15.5 14h-.8l-.3-.3a6.5 6.5 0 1 0-.7.7l.3.3v.8L20 21.5 21.5 20z ' +
'//storage.googleapis.com/material-icons/external-assets/v4/icons/svg/ic_youtube_searched_for_black_24px.svg'
'M10 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z ' +
];
'M11 7H9v2H7v2h2v2h2v-2h2V9h-2z"/>' +
var zoomInImg = '//storage.googleapis.com/material-icons/external-assets/v4/icons/svg/ic_zoom_in_black_24px.svg';
'</svg>';
var zoomOutImg = '//storage.googleapis.com/material-icons/external-assets/v4/icons/svg/ic_zoom_out_black_24px.svg';
 
var zoomOutSVG =
// create DOM elements
'<svg width="22" height="22" viewBox="0 0 24 24">' +
$('#content').prepend('<div id="zoomButtons" style="z-index: 9999;">\
'<path d="M15.5 14h-.8l-.3-.3a6.5 6.5 0 1 0-.7.7l.3.3v.8L20 21.5 21.5 20z ' +
<img id="zoomInIcon" src="' + zoomInImg + '" alt="zoom in"">\
'M7 9h6v2H7z"/>' +
<img id="zoomOutIcon" src="' + zoomOutImg + '" alt="zoom out"">\
'</svg>';
<img id="toggleButton" src="' + toggleImgs[+ useCustom] + '" alt="toggle zoom">&nbsp</div>');
 
$('#zoomButtons').css({'float': 'right'});
var toggleSVG =
'<svg width="22" height="22" viewBox="0 0 24 24">' +
// find DOM elements used later
'<path d="M5 4h14v2H5zm4 4h6l-2 12h-2z"/>' +
'</svg>';
 
$('#content').prepend(
'<div id="zoomButtons" style="z-index:9999; float:right; display:flex; gap:8px; cursor:pointer;">' +
'<span id="zoomInIcon">' + zoomInSVG + '</span>' +
'<span id="zoomOutIcon">' + zoomOutSVG + '</span>' +
'<span id="toggleButton">' + toggleSVG + '</span>' +
'</div>'
);
 
var $bodyContent = $('.mw-body-content');
var $bodyContent = $('.mw-body-content');
var $toggleButton = $('#toggleButton');
 
var sizes = [parseFloat($bodyContent.css('font-size'))];
// sizes[0] is default, sizes[1] is custom
var sizes = [parseFloat($('.mw-body-content').css('font-size'))];
// default custom zoom of 2
sizes[1] = sizes[0] + 2;
sizes[1] = sizes[0] + 2;
 
// the + converts bool to 0 or 1 to use as array index
function updateSize() {
function updateSize() {
$bodyContent.css({'font-size':(sizes[+ useCustom] + 'pt')});
$bodyContent.css('font-size', sizes[+useCustom] + 'pt');
}
}
function toggle() {
function toggle() {
useCustom = !useCustom;
useCustom = !useCustom;
$toggleButton.attr('src',toggleImgs[+ useCustom]);
updateSize();
updateSize();
}
}
function zoom(dif) {
function zoom(dif) {
sizes[1] += dif;
sizes[1] += dif;
Line 308: Line 314:
}
}
}
}
 
$( '#zoomInIcon' ).on( 'click', function(){
$('#zoomInIcon').on('click', function () {
console.log("Zoom +");
zoom(1);
zoom(1);
});
});
 
$( '#zoomOutIcon' ).on( 'click', function(){
$('#zoomOutIcon').on('click', function () {
console.log("Zoom -");
zoom(-1);
zoom(-1);
});
});
 
$( '#toggleButton' ).on( 'click', toggle );
$('#toggleButton').on('click', toggle);
});
});




Line 781: Line 786:
});
});


// ==UserScript==
// @name        MediaWiki Smart Single Quotes
// @namespace    http://your.local/
// @version      1.0
// @description  Convert straight single quotes to typographic single quotes on MediaWiki pages (visual only).
// @match        https://wiki.ekatrafoundation.org/*
// @grant        none
// ==/UserScript==


( function (window, document, mw) {
(function () {
     'use strict';
     'use strict';


    // Expose a minimal API for testing
     function convertSingleQuotesText(text) {
    window.smartQuotes = window.smartQuotes || {};
 
     function convertSingleQuotes(text) {
         if (!text || text.indexOf("'") === -1) return text;
         if (!text || text.indexOf("'") === -1) return text;
        // Protect contractions/possessives like don't or John's
         text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2"); // contractions/possessives
         text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2");
         text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘"); // opening quotes
        // Opening quote when at start or after whitespace or open punctuation
         text = text.replace(/'/g, "’"); // remaining closing quotes
         text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘");
        // Remaining single quotes are closing
         text = text.replace(/'/g, "’");
         return text;
         return text;
     }
     }
Line 801: Line 808:
     function walkAndReplace(root) {
     function walkAndReplace(root) {
         if (!root) return;
         if (!root) return;
         var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
         try {
        var node;
            var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
        var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]);
            var n;
 
            var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]);
        while ((node = walker.nextNode())) {
            while ((n = walker.nextNode())) {
            var parent = node.parentNode;
                var parent = n.parentNode;
            if (!parent) continue;
                if (!parent) continue;
 
                var anc = parent, skip = false;
            // Skip nodes with blacklisted ancestor
                while (anc && anc.nodeType === 1) {
            var anc = parent, skip = false;
                    if (blacklist.has(anc.nodeName)) { skip = true; break; }
            while (anc && anc.nodeType === 1) {
                    anc = anc.parentNode;
                if (blacklist.has(anc.nodeName)) { skip = true; break; }
                }
                anc = anc.parentNode;
                if (skip) continue;
                var orig = n.nodeValue;
                var rep = convertSingleQuotesText(orig);
                if (rep !== orig) n.nodeValue = rep;
             }
             }
            if (skip) continue;
        } catch (e) {
 
             console.error('smartQuotes error', e);
             var original = node.nodeValue;
            var replaced = convertSingleQuotes(original);
            if (replaced !== original) node.nodeValue = replaced;
         }
         }
     }
     }


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


     // MutationObserver for dynamic content
     // run after window.load, also watch for AJAX content
     function initObserver() {
     window.addEventListener('load', function () {
         try {
         setTimeout(applyOnce, 150);
            var observer = new MutationObserver(function (mutations) {
        var obs = new MutationObserver(function (mutations) {
                mutations.forEach(function (m) {
            mutations.forEach(function (m) {
                    if (m.addedNodes && m.addedNodes.length) {
                m.addedNodes && 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 });
            window.smartQuotes._observer = observer;
            console.info('smartQuotes: MutationObserver inited.');
        } catch (e) {
            console.error('smartQuotes observer 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
    function init() {
        processAll();
        initObserver();
    }
    if (mw && mw.loader) {
        mw.loader.using([], function () {
            if (document.readyState === 'complete' || document.readyState === 'interactive') {
                init();
            } else {
                document.addEventListener('DOMContentLoaded', init);
            }
         });
         });
    } else {
         obs.observe(document.body, { childList: true, subtree: true });
         // fallback
     }, false);
        if (document.readyState === 'complete' || document.readyState === 'interactive') init();
})();
        else document.addEventListener('DOMContentLoaded', init);
     }
 
    // Small heartbeat so we can see if script runs at all
    console.info('smartQuotes script loaded.');
 
})(window, document, window.mw);

Navigation menu