MediaWiki:Common.js

    From Marovi AI
    Revision as of 06:10, 24 April 2026 by DeployBot (talk | contribs) ([deploy-bot] Add MathJax 3 client-side loader for source-mode LaTeX rendering)
    (diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

    Note: After publishing, you may have to bypass your browser's cache to see the changes.

    • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
    • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
    • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
    • Opera: Press Ctrl-F5.
    /* ============================================================
       Marovi — Custom Reading Experience Scripts
       Applied via MediaWiki:Common.js
       ============================================================ */
    
    (function () {
        'use strict';
    
        /* --- Glossary Term Popups ---------------------------------- */
    
        var popup = null;
        var hideTimer = null;
    
        function createPopup() {
            if (popup) return popup;
            popup = document.createElement('div');
            popup.className = 'marovi-term-popup';
            popup.style.display = 'none';
            document.body.appendChild(popup);
    
            popup.addEventListener('mouseenter', function () {
                clearTimeout(hideTimer);
            });
            popup.addEventListener('mouseleave', function () {
                hidePopup();
            });
    
            return popup;
        }
    
        function showPopup(term, event) {
            var el = createPopup();
            var definition = term.getAttribute('data-definition');
            var article = term.getAttribute('data-article');
            var termName = term.getAttribute('data-term');
    
            if (!definition) return;
    
            var html = '';
            if (termName) {
                html += '<div class="marovi-term-popup-title">' + escapeHtml(termName) + '</div>';
            }
            html += '<div class="marovi-term-popup-definition">' + escapeHtml(definition) + '</div>';
            if (article) {
                var articleUrl = mw.util.getUrl(article);
                html += '<div class="marovi-term-popup-link"><a href="' + articleUrl + '">Read full article &rarr;</a></div>';
            }
    
            el.innerHTML = html;
            el.style.display = 'block';
    
            var rect = term.getBoundingClientRect();
            var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
            var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    
            el.style.top = (rect.bottom + scrollTop + 8) + 'px';
            el.style.left = Math.max(8, rect.left + scrollLeft - 20) + 'px';
    
            var popupRect = el.getBoundingClientRect();
            if (popupRect.right > window.innerWidth - 8) {
                el.style.left = (window.innerWidth - popupRect.width - 8) + 'px';
            }
        }
    
        function hidePopup() {
            hideTimer = setTimeout(function () {
                if (popup) {
                    popup.style.display = 'none';
                }
            }, 200);
        }
    
        function escapeHtml(text) {
            var div = document.createElement('div');
            div.appendChild(document.createTextNode(text));
            return div.innerHTML;
        }
    
        function initGlossary() {
            var terms = document.querySelectorAll('.marovi-term[data-definition]');
            terms.forEach(function (term) {
                term.addEventListener('mouseenter', function (e) {
                    clearTimeout(hideTimer);
                    showPopup(term, e);
                });
                term.addEventListener('mouseleave', function () {
                    hidePopup();
                });
                term.addEventListener('click', function (e) {
                    e.preventDefault();
                    clearTimeout(hideTimer);
                    showPopup(term, e);
                });
            });
    
            document.addEventListener('keydown', function (e) {
                if (e.key === 'Escape' && popup) {
                    popup.style.display = 'none';
                }
            });
    
            document.addEventListener('click', function (e) {
                if (popup && !popup.contains(e.target) && !e.target.classList.contains('marovi-term')) {
                    popup.style.display = 'none';
                }
            });
        }
    
        /* --- Language Tabs ----------------------------------------- */
    
        function initLanguageTabs() {
            var title = mw.config.get('wgTitle');
            var pageTitle = mw.config.get('wgPageName');
            var namespaceName = mw.config.get('wgCanonicalNamespace');
    
            if (namespaceName !== '' && namespaceName !== 'Main') return;
    
            var parts = pageTitle.split('/');
            var basePage = parts[0];
            var currentLang = parts.length > 1 ? parts[parts.length - 1] : 'en';
    
            var languages = [
                { code: 'en', label: 'EN', page: basePage },
                { code: 'es', label: 'ES', page: basePage + '/es' },
                { code: 'zh', label: '中文', page: basePage + '/zh' }
            ];
    
            var pagesToCheck = languages
                .filter(function (l) { return l.code !== currentLang; })
                .map(function (l) { return l.page; });
    
            if (pagesToCheck.length === 0) return;
    
            new mw.Api().get({
                action: 'query',
                titles: pagesToCheck.join('|'),
                formatversion: 2
            }).then(function (result) {
                var existingPages = {};
                if (result.query && result.query.pages) {
                    result.query.pages.forEach(function (page) {
                        if (!page.missing) {
                            existingPages[page.title.replace(/ /g, '_')] = true;
                        }
                    });
                }
    
                existingPages[basePage.replace(/ /g, '_')] = true;
    
                var availableLangs = languages.filter(function (l) {
                    return existingPages[l.page.replace(/ /g, '_')];
                });
    
                if (availableLangs.length < 2) return;
    
                var container = document.createElement('div');
                container.className = 'marovi-lang-tabs';
    
                availableLangs.forEach(function (lang) {
                    var tab = document.createElement('a');
                    tab.className = 'marovi-lang-tab';
                    tab.textContent = lang.label;
                    tab.href = mw.util.getUrl(lang.page);
    
                    if (lang.code === currentLang) {
                        tab.classList.add('marovi-lang-tab--active');
                    }
    
                    container.appendChild(tab);
                });
    
                var heading = document.getElementById('firstHeading') || document.querySelector('.mw-first-heading');
                if (heading && heading.parentNode) {
                    heading.parentNode.insertBefore(container, heading.nextSibling);
                }
            });
        }
    
        /* --- CJK Body Class --------------------------------------- */
    
        function initCJKClass() {
            var pageTitle = mw.config.get('wgPageName');
            if (pageTitle.match(/\/zh$/)) {
                document.body.classList.add('marovi-lang-zh');
            }
        }
    
        /* --- MathJax loader --------------------------------------- */
    
        // MediaWiki runs in $wgMathValidModes=['source'], so <math> tags are
        // rendered as <span class="mwe-math-fallback-source-{inline,block} tex">
        // containing $...$ / $$...$$ LaTeX. MathJax 3 reparses those on the client.
        function initMathJax() {
            if (!document.querySelector(
                '.mwe-math-fallback-source-inline, .mwe-math-fallback-source-block'
            )) {
                return;
            }
    
            window.MathJax = {
                tex: {
                    inlineMath: [['$', '$'], ['\\(', '\\)']],
                    displayMath: [['$$', '$$'], ['\\[', '\\]']],
                    processEscapes: true,
                    tags: 'none'
                },
                options: {
                    skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
                    ignoreHtmlClass: 'mw-parser-output-nomath|no-mathjax',
                    processHtmlClass: 'mwe-math-fallback-source-inline|mwe-math-fallback-source-block|tex'
                }
            };
    
            var script = document.createElement('script');
            script.async = true;
            script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
            document.head.appendChild(script);
        }
    
        /* --- Init ------------------------------------------------- */
    
        $(function () {
            initGlossary();
            initCJKClass();
            initMathJax();
            mw.loader.using('mediawiki.api').then(initLanguageTabs);
        });
    
    }());