Contents

MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
Line 18: Line 18:
     function getSavedTheme() {
     function getSavedTheme() {
         try {
         try {
             var saved = window.localStorage.getItem(
             var saved = window.localStorage.getItem(STORAGE_KEY);
                STORAGE_KEY
            );


             if (
             if (saved === LIGHT || saved === DARK) {
                saved === LIGHT ||
                saved === DARK
            ) {
                 return saved;
                 return saved;
             }
             }
Line 53: Line 48:


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


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


         html.classList.toggle(
         html.classList.toggle(
Line 84: Line 74:
         );
         );


        /*
        * Keep MediaWiki-compatible theme classes too.
        */
         html.classList.remove(
         html.classList.remove(
             'skin-theme-clientpref-day',
             'skin-theme-clientpref-day',
Line 93: Line 79:
             'skin-theme-clientpref-os'
             'skin-theme-clientpref-os'
         );
         );


         html.classList.add(
         html.classList.add(
Line 100: Line 85:
                 : 'skin-theme-clientpref-day'
                 : 'skin-theme-clientpref-day'
         );
         );


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


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


         updateThemeButton();
         updateThemeButton();
Line 126: Line 106:
         return [
         return [
             '<svg viewBox="0 0 24 24" aria-hidden="true">',
             '<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 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" d="M17.5 4v3M16 5.5h3"></path>',
             '</svg>'
             '</svg>'
         ].join('');
         ].join('');
Line 139: Line 116:
         return [
         return [
             '<svg viewBox="0 0 24 24" aria-hidden="true">',
             '<svg viewBox="0 0 24 24" aria-hidden="true">',
                 '<circle cx="12" cy="12" r="3.8"></circle>',
                 '<circle cx="12" cy="12" r="3.8"></circle>',
                 '<path d="M12 2.3v2.1"></path>',
                 '<path d="M12 2.3v2.1"></path>',
                 '<path d="M12 19.6v2.1"></path>',
                 '<path d="M12 19.6v2.1"></path>',
                 '<path d="M4.93 4.93l1.5 1.5"></path>',
                 '<path d="M4.93 4.93l1.5 1.5"></path>',
                 '<path d="M17.57 17.57l1.5 1.5"></path>',
                 '<path d="M17.57 17.57l1.5 1.5"></path>',
                 '<path d="M2.3 12h2.1"></path>',
                 '<path d="M2.3 12h2.1"></path>',
                 '<path d="M19.6 12h2.1"></path>',
                 '<path d="M19.6 12h2.1"></path>',
                 '<path d="M4.93 19.07l1.5-1.5"></path>',
                 '<path d="M4.93 19.07l1.5-1.5"></path>',
                 '<path d="M17.57 6.43l1.5-1.5"></path>',
                 '<path d="M17.57 6.43l1.5-1.5"></path>',
             '</svg>'
             '</svg>'
         ].join('');
         ].join('');
Line 164: Line 135:


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


         if (!button) {
         if (!button) {
             return;
             return;
         }
         }


         var isDark =
         var isDark =
             getCurrentTheme() === DARK;
             getCurrentTheme() === DARK;


         button.innerHTML =
         button.innerHTML =
Line 187: Line 154:
                 ) +
                 ) +
             '</span>';
             '</span>';


         button.setAttribute(
         button.setAttribute(
Line 195: Line 161:
                 : 'Switch to dark mode'
                 : 'Switch to dark mode'
         );
         );


         button.setAttribute(
         button.setAttribute(
Line 211: Line 176:


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


         if (existing) {
         if (existing) {
Line 221: Line 184:
         }
         }


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


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


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




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


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


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


         button.type =
         button.type =
Line 256: Line 209:
             'click',
             'click',
             function () {
             function () {
                 var nextTheme =
                 var nextTheme =
                     getCurrentTheme() === DARK
                     getCurrentTheme() === DARK
                         ? LIGHT
                         ? LIGHT
                         : DARK;
                         : DARK;


                 applyTheme(
                 applyTheme(
Line 271: Line 222:




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


         updateThemeButton();
         updateThemeButton();


         return item;
         return item;
Line 299: Line 246:


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


Line 306: Line 253:
         list
         list
     ) {
     ) {
         var username =
         var username = mw.config.get(
            mw.config.get(
            'wgUserName'
                'wgUserName'
        );
            );
 


         if (!username) {
         if (!username) {
             return;
             return;
         }
         }
        /*
        * MediaWiki's data-user-page already puts
        * the username in .wp-user-primary.
        *
        * Some personal-menu configurations can
        * also provide another user-page link.
        * Remove that duplicate from the menu.
        */


         var items =
         var items =
Line 330: Line 265:
                 list.children
                 list.children
             );
             );


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


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


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


                 var isUsername =
                 var isUsername =
                     linkText === username;
                     linkText === username;


                 if (
                 if (
Line 358: Line 287:
                     isUsername
                     isUsername
                 ) {
                 ) {
                    /*
                    * If primary somehow has no username,
                    * move this one there instead.
                    */
                     if (
                     if (
                         primary &&
                         primary &&
                         !primary.querySelector('li')
                         !primary.querySelector('li')
                     ) {
                     ) {
 
                         primary.appendChild(item);
                         primary.appendChild(
                            item
                        );
 
                     } else {
                     } else {
                         item.remove();
                         item.remove();
                     }
                     }
                 }
                 }
Line 398: Line 316:
             'wp-is-logged-out'
             'wp-is-logged-out'
         );
         );


         var loginItem =
         var loginItem =
Line 404: Line 321:
                 '#pt-login'
                 '#pt-login'
             );
             );


         /*
         /*
         * Desired layout:
         * Layout:
         *
         *
         * Create account | Theme | Log in
         * Create account | Theme | Log in
Line 413: Line 329:


         if (loginItem) {
         if (loginItem) {
             list.insertBefore(
             list.insertBefore(
                 themeItem,
                 themeItem,
                 loginItem
                 loginItem
             );
             );
         } else {
         } else {
             list.appendChild(
             list.appendChild(
                 themeItem
                 themeItem
             );
             );
         }
         }
     }
     }
Line 454: Line 366:


         /*
         /*
         * Remove an old dropdown if ResourceLoader
         * Prevent duplicate dropdown.
        * initialization somehow runs again.
         */
         */


Line 462: Line 373:
                 '.wp-user-dropdown'
                 '.wp-user-dropdown'
             );
             );


         if (oldDropdown) {
         if (oldDropdown) {
Line 470: Line 380:


         /* ====================================================
         /* ====================================================
           CREATE HAMBURGER WRAPPER
           DROPDOWN WRAPPER
         ==================================================== */
         ==================================================== */


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


         dropdown.className =
         dropdown.className =
Line 486: Line 395:


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


         trigger.type =
         trigger.type =
             'button';
             'button';


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


         trigger.setAttribute(
         trigger.setAttribute(
Line 503: Line 407:
             'Open user menu'
             'Open user menu'
         );
         );


         trigger.setAttribute(
         trigger.setAttribute(
Line 509: Line 412:
             'User menu'
             'User menu'
         );
         );


         trigger.setAttribute(
         trigger.setAttribute(
Line 515: Line 417:
             'false'
             'false'
         );
         );


         trigger.innerHTML =
         trigger.innerHTML =
Line 522: Line 423:


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


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


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


         menu.hidden =
         menu.hidden =
Line 538: Line 437:


         /*
         /*
         * Move every existing menu item
         * Move all user controls into the
         * into ☰.
         * hamburger except the theme button.
         *
         *
         * Username is NOT in this list anymore.
         * Username already lives separately
         * Theme is not inserted yet.
         * inside .wp-user-primary.
         */
         */


Line 551: Line 450:
             .forEach(
             .forEach(
                 function (item) {
                 function (item) {
                     if (
                     if (
                         item !== themeItem &&
                         item !== themeItem &&
                         item !== dropdown
                         item !== dropdown
                     ) {
                     ) {
 
                         menu.appendChild(item);
                         menu.appendChild(
                            item
                        );
                     }
                     }
                 }
                 }
Line 565: Line 460:




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




         /*
         /*
         * Exact layout:
         * Final logged-in header layout:
         *
         *
         * Username | Theme | ☰
         * Username | Theme | ☰
        *
        * Username lives in .wp-user-primary,
        * while theme and hamburger live here.
         */
         */


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




Line 602: Line 482:
                 'is-open'
                 'is-open'
             );
             );


             menu.hidden =
             menu.hidden =
                 true;
                 true;


             trigger.setAttribute(
             trigger.setAttribute(
Line 619: Line 497:
                 'is-open'
                 'is-open'
             );
             );


             menu.hidden =
             menu.hidden =
                 false;
                 false;


             trigger.setAttribute(
             trigger.setAttribute(
Line 633: Line 509:


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


Line 639: Line 515:
             'click',
             'click',
             function (event) {
             function (event) {
                 event.preventDefault();
                 event.preventDefault();
                 event.stopPropagation();
                 event.stopPropagation();


                 if (
                 if (
Line 649: Line 523:
                     )
                     )
                 ) {
                 ) {
                     closeMenu();
                     closeMenu();
                 } else {
                 } else {
                     openMenu();
                     openMenu();
                 }
                 }
             }
             }
Line 668: Line 538:
             'click',
             'click',
             function (event) {
             function (event) {
                 event.stopPropagation();
                 event.stopPropagation();
             }
             }
         );
         );
Line 682: Line 550:
             'click',
             'click',
             function (event) {
             function (event) {
                 if (
                 if (
                     !dropdown.contains(
                     !dropdown.contains(
Line 688: Line 555:
                     )
                     )
                 ) {
                 ) {
                     closeMenu();
                     closeMenu();
                 }
                 }
             }
             }
Line 703: Line 568:
             'keydown',
             'keydown',
             function (event) {
             function (event) {
                 if (
                 if (
                     event.key === 'Escape' &&
                     event.key === 'Escape' &&
Line 710: Line 574:
                     )
                     )
                 ) {
                 ) {
                     closeMenu();
                     closeMenu();
                     trigger.focus();
                     trigger.focus();
                 }
                 }
Line 721: Line 583:


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


Line 729: Line 591:
                 '.wp-user-primary'
                 '.wp-user-primary'
             );
             );


         var list =
         var list =
Line 735: Line 596:
                 '.wp-user-menu-list'
                 '.wp-user-menu-list'
             );
             );


         if (!list) {
         if (!list) {
Line 743: Line 603:


         /*
         /*
         * Empty username container should not
         * Hide empty username container
         * take up space for anonymous users.
         * for anonymous users.
         */
         */


Line 751: Line 611:
             !primary.querySelector('li')
             !primary.querySelector('li')
         ) {
         ) {
             primary.classList.add(
             primary.classList.add(
                 'is-empty'
                 'is-empty'
Line 759: Line 618:


         var themeItem =
         var themeItem =
             createThemeToggle(
             createThemeToggle(list);
                list
            );
 


         var username =
         var username =
Line 771: Line 627:


         if (username) {
         if (username) {
             buildLoggedInLayout(
             buildLoggedInLayout(
                 primary,
                 primary,
Line 777: Line 632:
                 themeItem
                 themeItem
             );
             );
         } else {
         } else {
             buildLoggedOutLayout(
             buildLoggedOutLayout(
                 list,
                 list,
Line 814: Line 667:
         'loading'
         'loading'
     ) {
     ) {
         document.addEventListener(
         document.addEventListener(
             'DOMContentLoaded',
             'DOMContentLoaded',
             initialize
             initialize
         );
         );
     } else {
     } else {
         initialize();
         initialize();
     }
     }


})();
})();


/* ============================================================
/* ============================================================
Line 835: Line 685:
(function () {
(function () {
     'use strict';
     'use strict';


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


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


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


         if (!toc || !article) {
         if (!toc || !article) {
Line 849: Line 704:




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


         if (!linksContainer) {
         if (!linksContainer) {
Line 859: Line 716:


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


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


        /*
        * Keep only real headings with visible text.
        */


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


                var text =
                     return text.length > 0;
                     heading.textContent
                }
                        .replace(/\[edit\]/gi, '')
            );
                        .trim();
 
                return text.length > 0;
            }
        );




         /* ====================================================
         /* ====================================================
           NO HEADINGS
           NO HEADINGS = NO CONTENTS BUTTON
          KEEP CONTENTS BUTTON COMPLETELY HIDDEN
         ==================================================== */
         ==================================================== */


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


             toc.open = false;
             toc.open =
                false;


             return;
             return;
Line 907: Line 760:
         ==================================================== */
         ==================================================== */


         linksContainer.innerHTML = '';
         linksContainer.innerHTML =
            '';




         /*
         var oldHeading =
        * Hide the old hard-coded "Contents" heading
            toc.querySelector(
        * if it exists in the Mustache template.
                '.wp-toc-heading'
        */
            );


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


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




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


         headings.forEach(
         headings.forEach(
             function (heading, index) {
             function (
 
                heading,
                /*
                index
                * MediaWiki normally gives headings an ID.
            ) {
                * If one does not exist, create a safe fallback.
                */


                 if (!heading.id) {
                 if (!heading.id) {
                     heading.id =
                     heading.id =
                         'wikipartner-section-' +
                         'wikipartner-section-' +
Line 944: Line 793:




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




Line 972: Line 822:
                     headingText;
                     headingText;


                /*
                * Close the dropdown after
                * selecting a heading.
                */


                 link.addEventListener(
                 link.addEventListener(
                     'click',
                     'click',
                     function () {
                     function () {
 
                         toc.open =
                         toc.open = false;
                            false;
 
                     }
                     }
                 );
                 );
Line 991: Line 835:
                     link
                     link
                 );
                 );
             }
             }
         );
         );
Line 997: Line 840:


         /* ====================================================
         /* ====================================================
           HEADINGS FOUND
           SHOW CONTENTS BUTTON
          NOW SHOW CONTENTS BUTTON
         ==================================================== */
         ==================================================== */


Line 1,007: Line 849:




     if (document.readyState === 'loading') {
     if (
 
        document.readyState ===
        'loading'
    ) {
         document.addEventListener(
         document.addEventListener(
             'DOMContentLoaded',
             'DOMContentLoaded',
             initializeArticleContents
             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;
        }


     } else {
        /*
        * 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
                        );
                }
            );
     }


        initializeArticleContents();


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


})();
})();

Revision as of 02:14, 20 August 2026

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

})();