Contents

MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */
/* ============================================================
 
   WIKIPARTNER
/* =========================================================
  LIGHT / DARK THEME SYSTEM
   WikiPartner — Light / Dark Theme Toggle
============================================================ */
  ========================================================= */


(function () {
(function () {
     'use strict';
     'use strict';


     var DAY = 'day';
     var STORAGE_KEY = 'wikipartner-theme';
     var NIGHT = 'night';
     var LIGHT = 'light';
    var DARK = 'dark';


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


         if (html.classList.contains('skin-theme-clientpref-night')) {
    /* ========================================================
            return NIGHT;
      STORAGE
        }
    ======================================================== */
 
    function getSavedTheme() {
         try {
            var saved = window.localStorage.getItem(STORAGE_KEY);


        if (html.classList.contains('skin-theme-clientpref-os')) {
             if (saved === DARK || saved === LIGHT) {
             if (
                 return saved;
                window.matchMedia &&
                window.matchMedia('(prefers-color-scheme: dark)').matches
            ) {
                 return NIGHT;
             }
             }
        } catch (e) {
            /* localStorage unavailable */
         }
         }


         return DAY;
         return LIGHT;
     }
     }


     function applyTheme(theme) {
 
    function saveTheme(theme) {
        try {
            window.localStorage.setItem(STORAGE_KEY, theme);
        } catch (e) {
            /* localStorage unavailable */
        }
    }
 
 
    /* ========================================================
      APPLY THEME
    ======================================================== */
 
     function applyTheme(theme, shouldSave) {
         var html = document.documentElement;
         var html = document.documentElement;


        var isDark = theme === DARK;
        html.classList.toggle('wp-theme-dark', isDark);
        html.classList.toggle('wp-theme-light', !isDark);
        /*
        * Also expose standard MediaWiki-style theme classes.
        * This allows extensions/components that understand
        * these classes to respond too.
        */
         html.classList.remove(
         html.classList.remove(
             'skin-theme-clientpref-day',
             'skin-theme-clientpref-day',
Line 39: Line 63:
         );
         );


         html.classList.add('skin-theme-clientpref-' + theme);
         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();
     }
     }


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


        /*
    /* ========================================================
        * Logged-out readers:
      CURRENT THEME
        * use MediaWiki's native client preference cookie.
    ======================================================== */
        */
 
        if (!isLoggedIn && mw.user && mw.user.clientPrefs) {
    function getCurrentTheme() {
            var saved = mw.user.clientPrefs.set('skin-theme', theme);
        return document.documentElement.classList.contains(
            'wp-theme-dark'
        ) ? DARK : LIGHT;
    }
 


            if (!saved) {
    /* ========================================================
                applyTheme(theme);
      ICONS
            }
    ======================================================== */


             return;
    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 ',
        * Logged-in users:
                    'class="wp-theme-star" ',
        * change immediately, then save as a MediaWiki user preference.
                    'd="M17.5 4v3M16 5.5h3"',
        */
                '></path>',
        applyTheme(theme);


        mw.loader.using('mediawiki.api').then(function () {
                '<path ',
            return new mw.Api().saveOption('skin-theme', theme);
                    'class="wp-theme-star wp-theme-star-small" ',
        }).catch(function () {
                    'd="M20.2 8.8v1.8M19.3 9.7h1.8"',
             // Theme still works for the current page if saving fails.
                '></path>',
         });
             '</svg>'
         ].join('');
     }
     }


     function moonIcon() {
 
         return '' +
     function getSunIcon() {
             '<svg viewBox="0 0 24 24" aria-hidden="true">' +
         return [
                 '<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>' +
             '<svg viewBox="0 0 24 24" aria-hidden="true">',
                 '<path class="wp-theme-star" d="M17.5 4.5v3M16 6h3"></path>' +
                '<circle cx="12" cy="12" r="3.8"></circle>',
             '</svg>';
 
                '<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('');
     }
     }


    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();
      UPDATE BUTTON
         var isDark = theme === NIGHT;
    ======================================================== */
 
     function updateThemeButton() {
         var button = document.getElementById(
            'wikipartner-theme-toggle'
        );
 
        if (!button) {
            return;
        }
 
         var isDark = getCurrentTheme() === DARK;


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


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


         button.setAttribute(
         button.setAttribute(
             'title',
             'title',
             isDark ? 'Light mode' : 'Dark mode'
             isDark
                ? 'Light mode'
                : 'Dark mode'
         );
         );
     }
     }
    /* ========================================================
      CREATE BUTTON
    ======================================================== */


     function createThemeToggle() {
     function createThemeToggle() {
         if (document.getElementById('wikipartner-theme-toggle')) {
         if (
            document.getElementById(
                'wikipartner-theme-toggle'
            )
        ) {
             return;
             return;
         }
         }
        var loginItem = document.getElementById('pt-login');
        var list = loginItem ? loginItem.parentNode : null;


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


         if (!list) {
         if (!list) {
Line 133: Line 201:


         var item = document.createElement('li');
         var item = document.createElement('li');
         item.className = 'mw-list-item wikipartner-theme-item';
 
        item.id = 'wikipartner-theme-item';
         item.className =
            'mw-list-item wikipartner-theme-item';
 


         var button = document.createElement('button');
         var button = document.createElement('button');
         button.id = 'wikipartner-theme-toggle';
         button.id = 'wikipartner-theme-toggle';
         button.className = 'wikipartner-theme-toggle';
         button.className =
            'wikipartner-theme-toggle';
 
         button.type = 'button';
         button.type = 'button';


        updateButton(button);


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


             saveTheme(next);
                applyTheme(nextTheme, true);
             }
        );


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


         item.appendChild(button);
         item.appendChild(button);


         /*
         /*
         * Exact requested position:
         * LOGGED OUT:
         * immediately LEFT of "Log in".
        *
        * Create account | Theme | Log in
        *
        * The requested position is immediately
         * to the LEFT of Log in.
         */
         */
        var loginItem = list.querySelector('#pt-login');
         if (loginItem) {
         if (loginItem) {
             list.insertBefore(item, loginItem);
             list.insertBefore(
                item,
                loginItem
            );
         } else {
         } else {
             list.insertBefore(item, list.firstChild);
 
            /*
            * LOGGED IN:
            *
            * Put theme button at beginning of
            * account controls.
            */
             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, {
         updateThemeButton();
            attributes: true,
    }
            attributeFilter: ['class']
 
         });
 
    /* ========================================================
      INITIAL THEME
    ======================================================== */
 
    /*
    * Apply saved preference as soon as Common.js runs.
    */
    applyTheme(
        getSavedTheme(),
        false
    );
 
 
    /* ========================================================
      INITIALIZE HEADER BUTTON
    ======================================================== */
 
    function initialize() {
        createThemeToggle();
         updateThemeButton();
     }
     }


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

Revision as of 22:46, 19 August 2026

/* ============================================================
   WIKIPARTNER
   LIGHT / DARK THEME SYSTEM
============================================================ */

(function () {
    'use strict';

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


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

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

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

        return LIGHT;
    }


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


    /* ========================================================
       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);

        /*
         * Also expose standard MediaWiki-style theme classes.
         * This allows extensions/components that understand
         * these classes to respond too.
         */
        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();
    }


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

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


    /* ========================================================
       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>',

                '<path ',
                    'class="wp-theme-star wp-theme-star-small" ',
                    'd="M20.2 8.8v1.8M19.3 9.7h1.8"',
                '></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 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 BUTTON
    ======================================================== */

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

        /*
         * WikiPartner's MediaWiki user menu.
         */
        var list = document.querySelector(
            '.wp-user-menu-list'
        );

        if (!list) {
            return;
        }

        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);


        /*
         * LOGGED OUT:
         *
         * Create account | Theme | Log in
         *
         * The requested position is immediately
         * to the LEFT of Log in.
         */
        var loginItem = list.querySelector('#pt-login');

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

            /*
             * LOGGED IN:
             *
             * Put theme button at beginning of
             * account controls.
             */
            list.insertBefore(
                item,
                list.firstChild
            );
        }


        updateThemeButton();
    }


    /* ========================================================
       INITIAL THEME
    ======================================================== */

    /*
     * Apply saved preference as soon as Common.js runs.
     */
    applyTheme(
        getSavedTheme(),
        false
    );


    /* ========================================================
       INITIALIZE HEADER BUTTON
    ======================================================== */

    function initialize() {
        createThemeToggle();
        updateThemeButton();
    }


    if (document.readyState === 'loading') {

        document.addEventListener(
            'DOMContentLoaded',
            initialize
        );

    } else {

        initialize();

    }

})();