MediaWiki:Timeless.js

Revision as of 21:37, 15 September 2026 by Stoulram (talk | contribs) (Trying dark mode)
(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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* All JavaScript here will be loaded for users of the Timeless skin */

/**
 * Timeless Light/Dark Mode Toggle
 *
 * Requires the corresponding CSS in MediaWiki:Timeless.css
 */

(function () {
    'use strict';

    var STORAGE_KEY = 'timeless-theme';

    /**
     * Get the user's preferred theme.
     * Stored preference takes priority; otherwise use system preference.
     */
    function getPreferredTheme() {
        var stored = localStorage.getItem(STORAGE_KEY);

        if (stored === 'dark' || stored === 'light') {
            return stored;
        }

        return window.matchMedia &&
            window.matchMedia('(prefers-color-scheme: dark)').matches
            ? 'dark'
            : 'light';
    }

    /**
     * Apply the selected theme.
     */
    function applyTheme(theme) {
        document.documentElement.classList.toggle('dark-mode', theme === 'dark');

        updateButton(theme);
    }

    /**
     * Update the toggle button text and tooltip.
     */
    function updateButton(theme) {
        var button = document.getElementById('timeless-theme-toggle');

        if (!button) {
            return;
        }

        if (theme === 'dark') {
            button.textContent = '☀️ Light mode';
            button.setAttribute('title', 'Switch to light mode');
            button.setAttribute('aria-label', 'Switch to light mode');
        } else {
            button.textContent = '🌙 Dark mode';
            button.setAttribute('title', 'Switch to dark mode');
            button.setAttribute('aria-label', 'Switch to dark mode');
        }
    }

    /**
     * Create the toggle button.
     */
    function createButton() {
        if (document.getElementById('timeless-theme-toggle')) {
            return;
        }

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

        button.id = 'timeless-theme-toggle';
        button.type = 'button';
        button.className = 'timeless-theme-toggle';
        button.addEventListener('click', function () {
            var currentTheme =
                document.documentElement.classList.contains('dark-mode')
                    ? 'dark'
                    : 'light';

            var newTheme = currentTheme === 'dark' ? 'light' : 'dark';

            localStorage.setItem(STORAGE_KEY, newTheme);
            applyTheme(newTheme);
        });

        /*
         * Timeless has a personal tools/navigation area.
         * Try several possible locations so this works across Timeless versions.
         */
        var locations = [
            '#p-personal',
            '#personal',
            '.personal-tools',
            '#mw-panel'
        ];

        var target = null;

        for (var i = 0; i < locations.length; i++) {
            target = document.querySelector(locations[i]);

            if (target) {
                break;
            }
        }

        if (!target) {
            return;
        }

        /*
         * If we found a navigation container, put the button in a list item
         * when possible. Otherwise append it directly.
         */
        var list = target.querySelector('ul');

        if (list) {
            var listItem = document.createElement('li');
            listItem.id = 'timeless-theme-toggle-container';

            listItem.appendChild(button);
            list.appendChild(listItem);
        } else {
            target.appendChild(button);
        }

        updateButton(getPreferredTheme());
    }

    /**
     * Apply the theme as early as possible.
     */
    applyTheme(getPreferredTheme());

    /**
     * Wait for MediaWiki/Timeless to finish constructing the interface.
     */
    mw.loader.using(['mediawiki.util'], function () {
        $(createButton);
    });

    /**
     * If the visitor has not manually selected a theme, automatically
     * follow changes to their operating-system color scheme.
     */
    if (window.matchMedia) {
        var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

        var systemThemeChanged = function (event) {
            if (!localStorage.getItem(STORAGE_KEY)) {
                applyTheme(event.matches ? 'dark' : 'light');
            }
        };

        if (mediaQuery.addEventListener) {
            mediaQuery.addEventListener('change', systemThemeChanged);
        } else if (mediaQuery.addListener) {
            mediaQuery.addListener(systemThemeChanged);
        }
    }
})();