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.
/* Any JavaScript here will be loaded for all users on every page load. */

/* =========================================================
   WikiPartner — Light / Dark Theme Toggle
   ========================================================= */

(function () {
    'use strict';

    var DAY = 'day';
    var NIGHT = 'night';

    function getCurrentTheme() {
        var html = document.documentElement;

        if (html.classList.contains('skin-theme-clientpref-night')) {
            return NIGHT;
        }

        if (html.classList.contains('skin-theme-clientpref-os')) {
            if (
                window.matchMedia &&
                window.matchMedia('(prefers-color-scheme: dark)').matches
            ) {
                return NIGHT;
            }
        }

        return DAY;
    }

    function applyTheme(theme) {
        var html = document.documentElement;

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

        html.classList.add('skin-theme-clientpref-' + theme);
    }

    function saveTheme(theme) {
        var isLoggedIn = !!mw.config.get('wgUserName');

        /*
         * Logged-out readers:
         * use MediaWiki's native client preference cookie.
         */
        if (!isLoggedIn && mw.user && mw.user.clientPrefs) {
            var saved = mw.user.clientPrefs.set('skin-theme', theme);

            if (!saved) {
                applyTheme(theme);
            }

            return;
        }

        /*
         * Logged-in users:
         * change immediately, then save as a MediaWiki user preference.
         */
        applyTheme(theme);

        mw.loader.using('mediawiki.api').then(function () {
            return new mw.Api().saveOption('skin-theme', theme);
        }).catch(function () {
            // Theme still works for the current page if saving fails.
        });
    }

    function moonIcon() {
        return '' +
            '<svg viewBox="0 0 24 24" aria-hidden="true">' +
                '<path d="M20.2 15.2A8.5 8.5 0 0 1 8.8 3.8a8.6 8.6 0 1 0 11.4 11.4Z"></path>' +
                '<path class="wp-theme-star" d="M17.5 4.5v3M16 6h3"></path>' +
            '</svg>';
    }

    function sunIcon() {
        return '' +
            '<svg viewBox="0 0 24 24" aria-hidden="true">' +
                '<circle cx="12" cy="12" r="4"></circle>' +
                '<path d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.65 17.65l1.42 1.42M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.65 6.35l1.42-1.42"></path>' +
            '</svg>';
    }

    function updateButton(button) {
        var theme = getCurrentTheme();
        var isDark = theme === NIGHT;

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

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

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

    function createThemeToggle() {
        if (document.getElementById('wikipartner-theme-toggle')) {
            return;
        }

        var loginItem = document.getElementById('pt-login');
        var list = loginItem ? loginItem.parentNode : null;

        /*
         * Fallback for logged-in users / slightly different Vector layouts.
         */
        if (!list) {
            list = document.querySelector(
                '#p-personal .vector-menu-content-list, ' +
                '#p-personal ul, ' +
                '.vector-user-links .vector-menu-content-list'
            );
        }

        if (!list) {
            return;
        }

        var item = document.createElement('li');
        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';

        updateButton(button);

        button.addEventListener('click', function () {
            var current = getCurrentTheme();
            var next = current === NIGHT ? DAY : NIGHT;

            saveTheme(next);

            /*
             * clientPrefs changes the class itself for anonymous users,
             * but this guarantees immediate visual response everywhere.
             */
            applyTheme(next);
            updateButton(button);
        });

        item.appendChild(button);

        /*
         * Exact requested position:
         * immediately LEFT of "Log in".
         */
        if (loginItem) {
            list.insertBefore(item, loginItem);
        } else {
            list.insertBefore(item, list.firstChild);
        }

        /*
         * Keep our icon synced if MediaWiki's own appearance
         * controls change the theme.
         */
        var observer = new MutationObserver(function () {
            updateButton(button);
        });

        observer.observe(document.documentElement, {
            attributes: true,
            attributeFilter: ['class']
        });
    }

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