Jump to content

MediaWiki:Common.js

From SOJI ELECTRONICS
Revision as of 03:10, 14 May 2026 by Admin (talk | contribs)

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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(function () {
    function esc(text) {
        return String(text || "").replace(/[&<>"]/g, function (m) {
            return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[m];
        });
    }

    function renderLink(item, extraClass) {
        var cls = extraClass ? ' class="' + extraClass + '"' : "";
        var href = item.href ? esc(item.href) : "#";
        return '<a' + cls + ' href="' + href + '">' + esc(item.label) + "</a>";
    }

    function buildFooter(data) {
        var columns = (data.columns || []).map(function (col) {
            var items = (col.items || []).map(function (item) {
                return "<li>" + renderLink(item, "soji-footer-link") + "</li>";
            }).join("");

            return [
                '<section class="soji-footer-col">',
                "<h2>" + esc(col.title) + "</h2>",
                "<ul>" + items + "</ul>",
                "</section>"
            ].join("");
        }).join("");

        var policies = (data.policies || []).map(function (item, i) {
            return (i ? '<span class="soji-footer-sep">|</span>' : "") +
                renderLink(item, "soji-footer-link");
        }).join("");

        var social = (data.social || []).map(function (item) {
            var href = item.href ? esc(item.href) : "#";
            return '<a class="soji-footer-social-item" href="' + href + '" aria-label="' + esc(item.label) + '">' +
                esc(item.short || item.label.charAt(0)) + "</a>";
        }).join("");

        var subscribe = data.subscribe
            ? renderLink(data.subscribe, "soji-footer-subscribe")
            : "";

        return [
            '<section id="soji-site-footer" class="soji-site-footer">',
            '<div class="soji-footer-shell">',
            '<div class="soji-footer-grid">' + columns + "</div>",
            '<div class="soji-footer-bottom">',
            '<div class="soji-footer-meta">',
            '<div class="soji-footer-policy-row">' + policies + "</div>",
            '<div class="soji-footer-copy">' + esc(data.copyright || "") + "</div>",
            "</div>",
            '<div class="soji-footer-actions">',
            subscribe,
            '<div class="soji-footer-social">',
            '<span class="soji-footer-social-label">Connect</span>',
            '<div class="soji-footer-social-list">' + social + "</div>",
            "</div>",
            "</div>",
            "</div>",
            "</div>",
            "</section>"
        ].join("");
    }

    function injectFooter(data) {
        var footer = document.getElementById("footer");
        if (!footer || document.getElementById("soji-site-footer")) return;
        footer.insertAdjacentHTML("beforebegin", buildFooter(data));
        document.body.classList.add("has-soji-footer");
    }

    function init() {
        fetch("/index.php?title=MediaWiki:FooterData.json&action=raw")
            .then(function (r) { return r.text(); })
            .then(function (text) { return JSON.parse(text); })
            .then(injectFooter)
            .catch(function (err) { console.error("Footer load failed:", err); });
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", init);
    } else {
        init();
    }
})();

$(function () {
    if (mw.config.get('wgNamespaceNumber') < 0) return;

    var title = mw.config.get('wgTitle');
    var pageName = mw.config.get('wgPageName').replace(/_/g, ' ');

    // Page Main Page: không cần breadcrumb
    if (pageName === 'Main Page') return;

    // Lấy category trực tiếp của page hoặc của category page hiện tại
    var initialCats = mw.config.get('wgCategories') || [];
    if (!initialCats.length) return;

    var api = new mw.Api();
    var chain = [];
    var seen = {};

    // Đi ngược lên cây qua API
    function walkUp(catName) {
        if (!catName || seen[catName] || catName === 'Main Page') {
            return $.Deferred().resolve();
        }
        seen[catName] = true;
        chain.unshift(catName);

        return api.get({
            action: 'query',
            titles: 'Category:' + catName,
            prop: 'categories',
            cllimit: 1,
            format: 'json',
            formatversion: 2
        }).then(function (data) {
            var pages = (data.query && data.query.pages) || [];
            if (!pages.length || !pages[0].categories) return;
            var parent = pages[0].categories[0].title.replace(/^Category:/, '');
            return walkUp(parent);
        });
    }

    walkUp(initialCats[0]).then(function () {
        var parts = [
            '<a href="' + mw.util.getUrl('Main Page') + '">Main Page</a>'
        ];
        chain.forEach(function (c) {
            parts.push('<a href="' + mw.util.getUrl(c) + '">' + c + '</a>');
        });
        parts.push('<strong>' + title + '</strong>');

        var html = '<nav class="custom-breadcrumb">' + parts.join(' &gt; ') + '</nav>';
        $('#mw-content-text').before(html);
    });
});

$(function () {
    // Tự động thêm target="_blank" cho mọi link tới file .pdf
    $('a[href$=".pdf"], a[href*=".pdf?"], a[href*="/file/"][href*=".pdf"]').each(function () {
        $(this).attr('target', '_blank');
        $(this).attr('rel', 'noopener noreferrer');
        // Thêm icon PDF nhỏ
        if (!$(this).find('.pdf-icon').length) {
            $(this).append(' <span class="pdf-icon" title="Open PDF in new tab">📄</span>');
        }
    });
});

$(function () {
    // Force "View" links to open in new tab
    $('.doc-view a').attr({
        target: '_blank',
        rel: 'noopener noreferrer'
    });

    // Force "Download" links to trigger file download
    $('.doc-download a').each(function () {
        var $a = $(this);
        var href = $a.attr('href');
        // Extract filename from URL
        var match = href.match(/\/([^\/]+\.(?:pdf|zip|doc|docx|xls|xlsx))$/i);
        if (match) {
            $a.attr('download', match[1]);
        } else {
            $a.attr('download', '');
        }
    });
});

/* ============================================
 * Auto-collapse TOC sub-headings on page load
 * Only show H2 (main Heading) by default
 * ============================================ */
$(function () {
    var attempts = 0;
    var maxAttempts = 15;

    function collapseAllTOC() {
        // Find all expanded toggle buttons in TOC
        var $toggles = $('.vector-toc-toggle[aria-expanded="true"]');
        
        if ($toggles.length > 0) {
            $toggles.each(function () {
                // Click toggle to collapse (triggers Vue.js handler)
                this.click();
            });
        } else if (attempts < maxAttempts) {
            // TOC chưa render xong, retry
            attempts++;
            setTimeout(collapseAllTOC, 100);
        }
    }

    collapseAllTOC();
});

/* ============================================
 * Sync chevron arrow with collapsible state
 * ============================================ */
$(function () {
    $('.category-block-toggle span[class*="mw-customtoggle-cb-"]').each(function () {
        var $toggle = $(this);
        var match = $toggle.attr('class').match(/mw-customtoggle-(\S+)/);
        if (!match) return;

        var $target = $('#mw-customcollapsible-' + match[1]);
        if (!$target.length) return;

        function syncArrow() {
            if ($target.hasClass('mw-collapsed')) {
                $toggle.removeClass('is-expanded');
            } else {
                $toggle.addClass('is-expanded');
            }
        }

        // Initial state
        syncArrow();

        // Update on click
        $toggle.on('click', function () {
            setTimeout(syncArrow, 50);
        });
    });
});

$(function () {
    // Chỉ show cho reviewer + sysop
    var groups = mw.config.get( 'wgUserGroups' ) || [];
    if ( !groups.includes( 'reviewer' ) && !groups.includes( 'sysop' ) ) {
        return;
    }
    
    // Poll API every 30 seconds
    function checkPendingChanges() {
        new mw.Api().get({
            action: 'query',
            list: 'oldreviewedpages',
            ornamespace: '0|14',
            orlimit: 50,
            format: 'json'
        }).done(function (data) {
            var pages = (data.query && data.query.oldreviewedpages) || [];
            var count = pages.length;
            updateBadge(count);
        });
    }
    
    function updateBadge(count) {
        var $badge = $('#pending-badge');
        
        if (!$badge.length) {
            // Tạo badge mới
            var html = '<div id="pending-badge" style="padding:8px; background:#fff3e0; border:1px solid #f57c00; border-radius:4px; margin:8px 0;">';
            html += '<a href="/wiki/Special:PendingChanges" style="text-decoration:none; color:#e65100;">';
            html += '⚠️ <span class="pending-count">0</span> pending review';
            html += '</a></div>';
            
            // Insert vào sidebar
            $('#mw-panel .vector-main-menu-content').first().prepend(html);
            $badge = $('#pending-badge');
        }
        
        $badge.find('.pending-count').text(count);
        
        if (count === 0) {
            $badge.hide();
        } else {
            $badge.show();
        }
    }
    
    // Initial check
    checkPendingChanges();
    
    // Poll every 30 seconds
    setInterval(checkPendingChanges, 30000);
});
SOJI Electronics