Contents

MediaWiki:Common.js

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.
/* ============================================================
   WIKIPARTNER
   THEME + USER MENU
============================================================ */

(function () {
    'use strict';

    var STORAGE_KEY = 'wikipartner-theme';
    var LIGHT = 'light';
    var DARK = 'dark';


    /* ========================================================
       THEME STORAGE
    ======================================================== */

    function getSavedTheme() {
        try {
            var saved = window.localStorage.getItem(STORAGE_KEY);

            if (saved === LIGHT || saved === DARK) {
                return saved;
            }
        } catch (e) {
            /* localStorage unavailable */
        }

        return LIGHT;
    }


    function saveTheme(theme) {
        try {
            window.localStorage.setItem(
                STORAGE_KEY,
                theme
            );
        } catch (e) {
            /* localStorage unavailable */
        }
    }


    /* ========================================================
       CURRENT THEME
    ======================================================== */

    function getCurrentTheme() {
        return document.documentElement.classList.contains(
            'wp-theme-dark'
        )
            ? DARK
            : LIGHT;
    }


    /* ========================================================
       APPLY THEME
    ======================================================== */

    function applyTheme(theme, shouldSave) {
        var html = document.documentElement;
        var isDark = theme === DARK;

        html.classList.toggle(
            'wp-theme-dark',
            isDark
        );

        html.classList.toggle(
            'wp-theme-light',
            !isDark
        );

        html.classList.remove(
            'skin-theme-clientpref-day',
            'skin-theme-clientpref-night',
            'skin-theme-clientpref-os'
        );

        html.classList.add(
            isDark
                ? 'skin-theme-clientpref-night'
                : 'skin-theme-clientpref-day'
        );

        html.setAttribute(
            'data-wikipartner-theme',
            isDark ? DARK : LIGHT
        );

        if (shouldSave !== false) {
            saveTheme(theme);
        }

        updateThemeButton();
    }


    /* ========================================================
       THEME ICONS
    ======================================================== */

    function getMoonIcon() {
        return [
            '<svg viewBox="0 0 24 24" aria-hidden="true">',
                '<path d="M20.3 15.2A8.4 8.4 0 0 1 8.8 3.7 8.5 8.5 0 1 0 11.5 11.5Z"></path>',
                '<path class="wp-theme-star" d="M17.5 4v3M16 5.5h3"></path>',
            '</svg>'
        ].join('');
    }


    function getSunIcon() {
        return [
            '<svg viewBox="0 0 24 24" aria-hidden="true">',
                '<circle cx="12" cy="12" r="3.8"></circle>',
                '<path d="M12 2.3v2.1"></path>',
                '<path d="M12 19.6v2.1"></path>',
                '<path d="M4.93 4.93l1.5 1.5"></path>',
                '<path d="M17.57 17.57l1.5 1.5"></path>',
                '<path d="M2.3 12h2.1"></path>',
                '<path d="M19.6 12h2.1"></path>',
                '<path d="M4.93 19.07l1.5-1.5"></path>',
                '<path d="M17.57 6.43l1.5-1.5"></path>',
            '</svg>'
        ].join('');
    }


    /* ========================================================
       UPDATE THEME BUTTON
    ======================================================== */

    function updateThemeButton() {
        var button = document.getElementById(
            'wikipartner-theme-toggle'
        );

        if (!button) {
            return;
        }

        var isDark =
            getCurrentTheme() === DARK;

        button.innerHTML =
            '<span class="wp-theme-icon">' +
                (
                    isDark
                        ? getSunIcon()
                        : getMoonIcon()
                ) +
            '</span>';

        button.setAttribute(
            'aria-label',
            isDark
                ? 'Switch to light mode'
                : 'Switch to dark mode'
        );

        button.setAttribute(
            'title',
            isDark
                ? 'Light mode'
                : 'Dark mode'
        );
    }


    /* ========================================================
       CREATE THEME BUTTON
    ======================================================== */

    function createThemeToggle(list) {
        var existing = document.getElementById(
            'wikipartner-theme-item'
        );

        if (existing) {
            return existing;
        }

        var item = document.createElement('li');

        item.id =
            'wikipartner-theme-item';

        item.className =
            'mw-list-item wikipartner-theme-item';


        var button =
            document.createElement('button');

        button.id =
            'wikipartner-theme-toggle';

        button.className =
            'wikipartner-theme-toggle';

        button.type =
            'button';


        button.addEventListener(
            'click',
            function () {
                var nextTheme =
                    getCurrentTheme() === DARK
                        ? LIGHT
                        : DARK;

                applyTheme(
                    nextTheme,
                    true
                );
            }
        );


        item.appendChild(button);

        updateThemeButton();

        return item;
    }


    /* ========================================================
       HAMBURGER ICON
    ======================================================== */

    function getMenuIcon() {
        return [
            '<span class="wp-menu-bars" aria-hidden="true">',
                '<span></span>',
                '<span></span>',
                '<span></span>',
            '</span>'
        ].join('');
    }


    /* ========================================================
       CLEAN DUPLICATE USERNAME
    ======================================================== */

    function cleanUsernameDuplicates(
        primary,
        list
    ) {
        var username = mw.config.get(
            'wgUserName'
        );

        if (!username) {
            return;
        }

        var items =
            Array.prototype.slice.call(
                list.children
            );

        items.forEach(
            function (item) {
                var link =
                    item.querySelector('a');

                var linkText =
                    link
                        ? link.textContent.trim()
                        : '';

                var isUserPageId =
                    item.id === 'pt-userpage' ||
                    item.id === 'pt-userpage-2';

                var isUsername =
                    linkText === username;

                if (
                    isUserPageId ||
                    isUsername
                ) {
                    if (
                        primary &&
                        !primary.querySelector('li')
                    ) {
                        primary.appendChild(item);
                    } else {
                        item.remove();
                    }
                }
            }
        );
    }


    /* ========================================================
       LOGGED OUT LAYOUT
    ======================================================== */

    function buildLoggedOutLayout(
        list,
        themeItem
    ) {
        list.classList.remove(
            'wp-is-logged-in'
        );

        list.classList.add(
            'wp-is-logged-out'
        );

        var loginItem =
            list.querySelector(
                '#pt-login'
            );

        /*
         * Layout:
         *
         * Create account | Theme | Log in
         */

        if (loginItem) {
            list.insertBefore(
                themeItem,
                loginItem
            );
        } else {
            list.appendChild(
                themeItem
            );
        }
    }


    /* ========================================================
       LOGGED IN LAYOUT
    ======================================================== */

    function buildLoggedInLayout(
        primary,
        list,
        themeItem
    ) {
        list.classList.remove(
            'wp-is-logged-out'
        );

        list.classList.add(
            'wp-is-logged-in'
        );


        cleanUsernameDuplicates(
            primary,
            list
        );


        /*
         * Prevent duplicate dropdown.
         */

        var oldDropdown =
            list.querySelector(
                '.wp-user-dropdown'
            );

        if (oldDropdown) {
            return;
        }


        /* ====================================================
           DROPDOWN WRAPPER
        ==================================================== */

        var dropdown =
            document.createElement('li');

        dropdown.className =
            'wp-user-dropdown';


        /* ====================================================
           HAMBURGER BUTTON
        ==================================================== */

        var trigger =
            document.createElement('button');

        trigger.type =
            'button';

        trigger.className =
            'wp-user-menu-trigger';

        trigger.setAttribute(
            'aria-label',
            'Open user menu'
        );

        trigger.setAttribute(
            'title',
            'User menu'
        );

        trigger.setAttribute(
            'aria-expanded',
            'false'
        );

        trigger.innerHTML =
            getMenuIcon();


        /* ====================================================
           DROPDOWN MENU
        ==================================================== */

        var menu =
            document.createElement('ul');

        menu.className =
            'wp-user-dropdown-menu';

        menu.hidden =
            true;


        /*
         * Move all user controls into the
         * hamburger except the theme button.
         *
         * Username already lives separately
         * inside .wp-user-primary.
         */

        Array.prototype
            .slice.call(
                list.children
            )
            .forEach(
                function (item) {
                    if (
                        item !== themeItem &&
                        item !== dropdown
                    ) {
                        menu.appendChild(item);
                    }
                }
            );


        dropdown.appendChild(trigger);
        dropdown.appendChild(menu);


        /*
         * Final logged-in header layout:
         *
         * Username | Theme | ☰
         */

        list.appendChild(themeItem);
        list.appendChild(dropdown);


        /* ====================================================
           OPEN / CLOSE
        ==================================================== */

        function closeMenu() {
            dropdown.classList.remove(
                'is-open'
            );

            menu.hidden =
                true;

            trigger.setAttribute(
                'aria-expanded',
                'false'
            );
        }


        function openMenu() {
            dropdown.classList.add(
                'is-open'
            );

            menu.hidden =
                false;

            trigger.setAttribute(
                'aria-expanded',
                'true'
            );
        }


        /* ====================================================
           HAMBURGER CLICK
        ==================================================== */

        trigger.addEventListener(
            'click',
            function (event) {
                event.preventDefault();
                event.stopPropagation();

                if (
                    dropdown.classList.contains(
                        'is-open'
                    )
                ) {
                    closeMenu();
                } else {
                    openMenu();
                }
            }
        );


        /* ====================================================
           CLICK INSIDE MENU
        ==================================================== */

        menu.addEventListener(
            'click',
            function (event) {
                event.stopPropagation();
            }
        );


        /* ====================================================
           CLICK OUTSIDE
        ==================================================== */

        document.addEventListener(
            'click',
            function (event) {
                if (
                    !dropdown.contains(
                        event.target
                    )
                ) {
                    closeMenu();
                }
            }
        );


        /* ====================================================
           ESCAPE
        ==================================================== */

        document.addEventListener(
            'keydown',
            function (event) {
                if (
                    event.key === 'Escape' &&
                    dropdown.classList.contains(
                        'is-open'
                    )
                ) {
                    closeMenu();
                    trigger.focus();
                }
            }
        );
    }


    /* ========================================================
       INITIALIZE USER AREA
    ======================================================== */

    function initializeUserArea() {
        var primary =
            document.querySelector(
                '.wp-user-primary'
            );

        var list =
            document.querySelector(
                '.wp-user-menu-list'
            );

        if (!list) {
            return;
        }


        /*
         * Hide empty username container
         * for anonymous users.
         */

        if (
            primary &&
            !primary.querySelector('li')
        ) {
            primary.classList.add(
                'is-empty'
            );
        }


        var themeItem =
            createThemeToggle(list);

        var username =
            mw.config.get(
                'wgUserName'
            );


        if (username) {
            buildLoggedInLayout(
                primary,
                list,
                themeItem
            );
        } else {
            buildLoggedOutLayout(
                list,
                themeItem
            );
        }


        updateThemeButton();
    }


    /* ========================================================
       APPLY SAVED THEME
    ======================================================== */

    applyTheme(
        getSavedTheme(),
        false
    );


    /* ========================================================
       START
    ======================================================== */

    function initialize() {
        initializeUserArea();
    }


    if (
        document.readyState ===
        'loading'
    ) {
        document.addEventListener(
            'DOMContentLoaded',
            initialize
        );
    } else {
        initialize();
    }

})();


/* ============================================================
   WIKIPARTNER
   ARTICLE CONTENTS MENU
============================================================ */

(function () {
    'use strict';


    function initializeArticleContents() {
        var toc =
            document.getElementById(
                'wp-toc'
            );

        var article =
            document.querySelector(
                '.wp-article-body'
            );


        if (!toc || !article) {
            return;
        }


        var linksContainer =
            toc.querySelector(
                '.wp-toc-links'
            );


        if (!linksContainer) {
            return;
        }


        /* ====================================================
           FIND ARTICLE HEADINGS
        ==================================================== */

        var headings =
            Array.prototype.slice.call(
                article.querySelectorAll(
                    'h2, h3, h4, h5, h6'
                )
            );


        headings =
            headings.filter(
                function (heading) {
                    var text =
                        heading.textContent
                            .replace(/\[edit\]/gi, '')
                            .trim();

                    return text.length > 0;
                }
            );


        /* ====================================================
           NO HEADINGS = NO CONTENTS BUTTON
        ==================================================== */

        if (!headings.length) {
            toc.classList.remove(
                'wp-toc-ready'
            );

            toc.open =
                false;

            return;
        }


        /* ====================================================
           CLEAR OLD CONTENTS
        ==================================================== */

        linksContainer.innerHTML =
            '';


        var oldHeading =
            toc.querySelector(
                '.wp-toc-heading'
            );


        if (oldHeading) {
            oldHeading.style.display =
                'none';
        }


        /* ====================================================
           BUILD CONTENTS FROM REAL HEADINGS
        ==================================================== */

        headings.forEach(
            function (
                heading,
                index
            ) {

                if (!heading.id) {
                    heading.id =
                        'wikipartner-section-' +
                        (index + 1);
                }


                var level =
                    parseInt(
                        heading.tagName.substring(1),
                        10
                    );


                var headingText =
                    heading.textContent
                        .replace(/\[edit\]/gi, '')
                        .trim();


                var link =
                    document.createElement('a');


                link.href =
                    '#' + heading.id;


                link.className =
                    'wp-toc-level-' +
                    level;


                link.textContent =
                    headingText;


                link.addEventListener(
                    'click',
                    function () {
                        toc.open =
                            false;
                    }
                );


                linksContainer.appendChild(
                    link
                );
            }
        );


        /* ====================================================
           SHOW CONTENTS BUTTON
        ==================================================== */

        toc.classList.add(
            'wp-toc-ready'
        );
    }


    if (
        document.readyState ===
        'loading'
    ) {
        document.addEventListener(
            'DOMContentLoaded',
            initializeArticleContents
        );
    } else {
        initializeArticleContents();
    }

})();


/* ============================================================
   WIKIPARTNER
   WHAT LINKS HERE — CURRENT PAGE
============================================================ */

(function () {
    'use strict';


    function initializeWhatLinksHere() {
        var link =
            document.getElementById(
                'wp-what-links-here'
            );


        if (!link) {
            return;
        }


        var pageName =
            mw.config.get(
                'wgPageName'
            );


        if (!pageName) {
            return;
        }


        /*
         * Use MediaWiki's own URL builder
         * instead of html-title.
         */

        mw.loader
            .using(
                'mediawiki.util'
            )
            .then(
                function () {
                    link.href =
                        mw.util.getUrl(
                            'Special:WhatLinksHere/' +
                            pageName
                        );
                }
            );
    }


    if (
        document.readyState ===
        'loading'
    ) {
        document.addEventListener(
            'DOMContentLoaded',
            initializeWhatLinksHere
        );
    } else {
        initializeWhatLinksHere();
    }

})();