MediaWiki:Timeless.js: Difference between revisions

From Exuvia Wiki
Created page with "/* =============================================================================== EXUVIA WIKI - SIDEBAR DROPDOWN MENUS =============================================================================== This script converts sidebar entries into expandable dropdown menus. The sidebar is structured like this: ** Locations *** Continents *** Kingdoms *** Towns ** Religions *** The Valor *** The Brumal The script automatically detects this struc..."
 
No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
/*
$( function () {
===============================================================================
EXUVIA WIKI - SIDEBAR DROPDOWN MENUS
===============================================================================


This script converts sidebar entries into expandable dropdown menus.
    /*
    * ============================================================
    * DROPDOWN MENU CONTENT
    * ============================================================
    *
    * A string creates a normal page link.
    *
    * An object creates a second-level dropdown:
    *
    * 'Continents': [
    *    'Alorea',
    *    'Another Continent'
    * ]
    *
    * This can be nested as deeply as desired.
    */


The sidebar is structured like this:
    var menus = {


    ** Locations
        'Locations': {
    *** Continents
    *** Kingdoms
    *** Towns


    ** Religions
            'Continents': [
    *** The Valor
                'Alorea',
    *** The Brumal
                'Another Continent'
            ],


The script automatically detects this structure.
            'Kingdoms': [
                'Koble',
                'Yamen',
                'Zagis',
                'Tekk',
                'Frossik'
            ],


You do NOT need to add anything to this JavaScript when creating another
            'Towns': [
dropdown.
                'Town A',
                'Town B'
            ]
        },


For example, adding this to MediaWiki:Sidebar:


    ** Organizations
        'Religions': {
    *** The Theurgic Knights
    *** ZIMS
    *** YCCC


will automatically create:
            'Kippelian Pantheon': [
                'The Valor',
                'The Brumal'
            ],


    Organizations
            'Other Religions': [
        The Theurgic Knights
                'Religion A',
        ZIMS
                'Religion B'
         YCCC
            ]
         },




===============================================================================
        'People': {
*/


            'Kippelian Empire': [
                'High Lord Kip',
                'Queen Melena'
            ],


/*
            'Other People': [
===============================================================================
                'Person A',
1. WAIT FOR THE PAGE TO FINISH LOADING
                'Person B'
===============================================================================
            ]
        },


MediaWiki may load this script before the sidebar has finished being created.


$( function () { ... } );
        'World Building': {


waits until the page is ready before running the code.
            'History': [
                'War of Unification',
                'History of Kippel'
            ],


===============================================================================
            'Magic': [
*/
                'Magic',
                'Necromancy'
            ],


$( function () {
            'War': [
                'War of Unification',
                'Thalassic War'
            ]
        }
    };




     /*
     /*
    ===========================================================================
    * ============================================================
    2. FIND THE TIMeless SIDEBAR
    * FIND THE SIDEBAR
    ===========================================================================
    * ============================================================
    */


     #mw-site-navigation
     var sidebar = document.getElementById( 'mw-site-navigation' );


     is the main navigation area used by Timeless.
     if ( !sidebar ) {
        return;
    }


    ===========================================================================
    */


     var sidebar = $( '#mw-site-navigation' );
    /*
    * ============================================================
    * FIND ALL LINKS IN THE SIDEBAR
    * ============================================================
    */
 
     var links = sidebar.getElementsByTagName( 'a' );
 
    var sidebarLinks = Array.prototype.slice.call( links );




     /*
     /*
     If the sidebar cannot be found, stop.
    * ============================================================
    * CREATE A DROPDOWN
    * ============================================================
    *
    * This function creates a dropdown from an object.
    *
    * It can call itself to create another dropdown inside it.
    */
 
     function createDropdown( menuData, parentLink ) {
 
        var dropdown = document.createElement( 'ul' );
 
        dropdown.className = 'exuvia-dropdown-menu exuvia-dropdown-level-2';
 
        dropdown.style.display = 'none';
 


    This prevents JavaScript errors.
        Object.keys( menuData ).forEach( function ( name ) {


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


    if ( !sidebar.length ) {
        return;
    }


            /*
            * ====================================================
            * SECOND-LEVEL DROPDOWN
            * ====================================================
            *
            * If the value is an array, it is a normal list of
            * pages.
            *
            * If the value is an object, it contains another
            * dropdown.
            */


    /*
            if ( Array.isArray( menuData[ name ] ) ) {
    ===========================================================================
    3. FIND SIDEBAR SECTIONS
    ===========================================================================


    Timeless puts groups of sidebar links inside "portal" elements.
                /*
                * Normal page group
                */


    We examine every portal separately.
                var link = document.createElement( 'a' );


    ===========================================================================
                link.href = mw.util.getUrl( name );
    */


    sidebar.find( '.portal' ).each( function () {
                link.textContent = name;


        var portal = $( this );
                item.appendChild( link );




        /*
                /*
        =======================================================================
                * If this is being used as a dropdown title,
        4. FIND THE SIDEBAR LIST
                * it can be turned into a submenu later.
        =======================================================================
                */


        Sidebar links are normally contained inside a <ul>.
            } else {


        ===========================================================================
                /*
        */
                * =================================================
                * CREATE SECOND-LEVEL TITLE
                * =================================================
                */


        var list = portal.find( 'ul' ).first();
                var link = document.createElement( 'a' );


                link.href = '#';


        /*
                link.textContent = name;
        If there isn't a list, there is nothing to modify.


        ===========================================================================
                link.className = 'exuvia-dropdown-title';
        */


        if ( !list.length ) {
            return;
        }


                /*
                * Arrow
                */


        /*
                var arrow = document.createElement( 'span' );
        =======================================================================
        5. LOOK AT EVERY SIDEBAR ITEM
        =======================================================================


        Each sidebar entry is normally an <li>.
                arrow.className = 'exuvia-dropdown-arrow';


        We examine each one to determine whether it should become a dropdown.
                arrow.textContent = ' ▼';


        ===========================================================================
                link.appendChild( arrow );
        */


        list.children( 'li' ).each( function () {


            var item = $( this );
                /*
                * Create the next level recursively.
                */


                var submenu = createDropdown(
                    menuData[ name ],
                    link
                );


            /*
            ===================================================================
            6. LOOK FOR CHILD ITEMS
            ===================================================================


            A normal sidebar entry looks like:
                /*
                * Add submenu to this item.
                */


                 <li>
                 item.appendChild( link );
                    <a>Locations</a>
                </li>


            A dropdown entry needs another list underneath it:
                item.appendChild( submenu );


                <li>
                    <a>Locations</a>


                    <ul>
                /*
                        <li>
                * =================================================
                            <a>Continents</a>
                * OPEN / CLOSE SUBMENU
                        </li>
                * =================================================
                    </ul>
                */
                </li>


            If the <li> contains another <ul>, it has children.
                link.addEventListener( 'click', function ( event ) {


            ===================================================================
                    event.preventDefault();
            */


            var childMenu = item.children( 'ul' );
                    if ( submenu.style.display === 'none' ) {


                        submenu.style.display = 'block';


            /*
                        arrow.textContent = ' ▲';
            ===================================================================
            7. IGNORE NORMAL LINKS
            ===================================================================


            If there isn't a child menu, this is just a normal sidebar link.
                    } else {


            For example:
                        submenu.style.display = 'none';


                ** About
                        arrow.textContent = ' ▼';
                ** Contact


            Those remain normal links.
                    }


            ===================================================================
                });
            */


            if ( !childMenu.length ) {
                return;
             }
             }




             /*
             dropdown.appendChild( item );
            ===================================================================
 
            8. FIND THE TOP-LEVEL LINK
        });
            ===================================================================
 
 
        return dropdown;
    }
 


            This is the link that currently says something like:
    /*
    * ============================================================
    * PROCESS TOP-LEVEL MENUS
    * ============================================================
    */


                Locations
    Object.keys( menus ).forEach( function ( menuName ) {


            We are going to turn this into a button.
        var mainLink = null;


            ===========================================================================
            */


            var mainLink = item.children( 'a' );
        /*
        * Find the sidebar link matching the menu name.
        */


        sidebarLinks.some( function ( link ) {


             /*
             var text = link.textContent.trim();
            ===================================================================
            9. SAFETY CHECK
            ===================================================================


             If no link exists, there is nothing to turn into a dropdown button.
             if ( text === menuName ) {


            ===================================================================
                mainLink = link;
            */


            if ( !mainLink.length ) {
                 return true;
                 return;
             }
             }
            return false;
        });
        if ( !mainLink ) {
            return;
        }


        /*
        * ========================================================
        * FIND THE SIDEBAR ITEM
        * ========================================================
        */
        var parent = mainLink.closest( 'li' );
        if ( !parent ) {
            return;
        }
        /*
        * ========================================================
        * CREATE TOP-LEVEL DROPDOWN
        * ========================================================
        */
        var dropdown = document.createElement( 'ul' );
        dropdown.className = 'exuvia-dropdown-menu exuvia-dropdown-level-1';
        dropdown.style.display = 'none';
        /*
        * ========================================================
        * CREATE FIRST-LEVEL ITEMS
        * ========================================================
        */
        Object.keys( menus[ menuName ] ).forEach( function ( name ) {
            var item = document.createElement( 'li' );


             /*
             /*
             ===================================================================
            * Every entry in the top-level menu is treated as
             10. PREVENT THE MAIN LINK FROM NAVIGATING
            * a submenu title.
             ===================================================================
            */
 
             var link = document.createElement( 'a' );
 
             link.href = '#';
 
             link.textContent = name;


             The user specifically wants:
             link.className = 'exuvia-dropdown-title';


                Clicking Locations = open/close menu


             NOT:
             /*
            * Arrow
            */


                Clicking Locations = go to a Locations page
            var arrow = document.createElement( 'span' );


             preventDefault() prevents the browser from following the link.
             arrow.className = 'exuvia-dropdown-arrow';


             ===================================================================
             arrow.textContent = ' ▼';
            */


             mainLink.on( 'click', function ( event ) {
             link.appendChild( arrow );


                event.preventDefault();


            /*
            * ====================================================
            * CREATE SECOND LEVEL
            * ====================================================
            */


                /*
            var submenuData = menus[ menuName ][ name ];
                ===============================================================
                11. OPEN OR CLOSE THE DROPDOWN
                ===============================================================


                slideToggle() does exactly what is wanted:
            var submenu = document.createElement( 'ul' );


                    closed → open
            submenu.className =
                'exuvia-dropdown-menu exuvia-dropdown-level-2';


                    open → closed
            submenu.style.display = 'none';


                150 means the animation lasts approximately 150 milliseconds.


                ===============================================================
            /*
                */
            * Add the actual pages.
            */


                childMenu.stop( true, true ).slideToggle( 150 );
            submenuData.forEach( function ( pageName ) {


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


                 /*
                 var pageLink = document.createElement( 'a' );
                ===============================================================
                12. ACCESSIBILITY INFORMATION
                ===============================================================


                 aria-expanded tells screen readers whether the menu is open.
                 pageLink.href = mw.util.getUrl( pageName );


                 ===============================================================
                 pageLink.textContent = pageName;
                */


                 var isOpen = childMenu.is( ':visible' );
                 pageItem.appendChild( pageLink );


                 mainLink.attr(
                 submenu.appendChild( pageItem );
                    'aria-expanded',
                    isOpen ? 'true' : 'false'
                );


             });
             });
Line 292: Line 394:


             /*
             /*
            ===================================================================
            * Add submenu.
             13. MARK THE LINK AS A DROPDOWN
            */
            ===================================================================
 
             item.appendChild( link );


             aria-haspopup tells accessibility software that this element
             item.appendChild( submenu );
            controls another menu.


             ===================================================================
             dropdown.appendChild( item );
            */


            mainLink.attr( 'aria-haspopup', 'true' );


             mainLink.attr( 'aria-expanded', 'false' );
             /*
            * ====================================================
            * OPEN / CLOSE SECOND LEVEL
            * ====================================================
            */


            link.addEventListener( 'click', function ( event ) {
                event.preventDefault();
                if ( submenu.style.display === 'none' ) {
                    submenu.style.display = 'block';
                    arrow.textContent = ' ▲';
                } else {
                    submenu.style.display = 'none';
                    arrow.textContent = ' ▼';
                }
            });
        });


            /*
            ===================================================================
            14. ADD A DROPDOWN ARROW
            ===================================================================


            This adds a small visual arrow after the title.
        /*
        * ========================================================
        * ADD DROPDOWN TO SIDEBAR
        * ========================================================
        */


                Locations ▼
        parent.appendChild( dropdown );


            The arrow itself does not affect navigation.


            ===================================================================
        /*
            */
        * ========================================================
        * TOP-LEVEL ARROW
        * ========================================================
        */


            mainLink.append(
        var arrow = document.createElement( 'span' );
                $( '<span>' )
                    .addClass( 'exuvia-dropdown-arrow' )
                    .text( ' ▼' )
            );


        arrow.className = 'exuvia-dropdown-arrow';


            /*
        arrow.textContent = ' ▼';
            ===================================================================
            15. HIDE THE CHILDREN WHEN THE PAGE LOADS
            ===================================================================


            Dropdowns start closed.
        mainLink.appendChild( arrow );


            The user can open them by clicking the title.


            ===================================================================
        /*
            */
        * Mark as dropdown title.
        */


            childMenu.hide();
        mainLink.classList.add( 'exuvia-dropdown-title' );




            /*
        /*
            ===================================================================
        * ========================================================
            16. GIVE THE DROPDOWN A CLASS
        * OPEN / CLOSE TOP LEVEL
            ===================================================================
        * ========================================================
        */


            This lets the CSS identify these menus.
        mainLink.addEventListener( 'click', function ( event ) {


             ===================================================================
             event.preventDefault();
            */


             childMenu.addClass( 'exuvia-dropdown-menu' );
             if ( dropdown.style.display === 'none' ) {


                dropdown.style.display = 'block';


            /*
                arrow.textContent = ' ▲';
            ===================================================================
            17. MARK THE PARENT ITEM
            ===================================================================


             This gives the parent <li> a class.
             } else {


            It can be useful for CSS later.
                dropdown.style.display = 'none';


            ===================================================================
                arrow.textContent = ' ▼';
            */


             item.addClass( 'exuvia-dropdown-parent' );
             }


         });
         });
Line 374: Line 492:
     });
     });


} );
});

Latest revision as of 23:35, 26 August 2026

$( function () {

    /*
     * ============================================================
     * DROPDOWN MENU CONTENT
     * ============================================================
     *
     * A string creates a normal page link.
     *
     * An object creates a second-level dropdown:
     *
     * 'Continents': [
     *     'Alorea',
     *     'Another Continent'
     * ]
     *
     * This can be nested as deeply as desired.
     */

    var menus = {

        'Locations': {

            'Continents': [
                'Alorea',
                'Another Continent'
            ],

            'Kingdoms': [
                'Koble',
                'Yamen',
                'Zagis',
                'Tekk',
                'Frossik'
            ],

            'Towns': [
                'Town A',
                'Town B'
            ]
        },


        'Religions': {

            'Kippelian Pantheon': [
                'The Valor',
                'The Brumal'
            ],

            'Other Religions': [
                'Religion A',
                'Religion B'
            ]
        },


        'People': {

            'Kippelian Empire': [
                'High Lord Kip',
                'Queen Melena'
            ],

            'Other People': [
                'Person A',
                'Person B'
            ]
        },


        'World Building': {

            'History': [
                'War of Unification',
                'History of Kippel'
            ],

            'Magic': [
                'Magic',
                'Necromancy'
            ],

            'War': [
                'War of Unification',
                'Thalassic War'
            ]
        }
    };


    /*
     * ============================================================
     * FIND THE SIDEBAR
     * ============================================================
     */

    var sidebar = document.getElementById( 'mw-site-navigation' );

    if ( !sidebar ) {
        return;
    }


    /*
     * ============================================================
     * FIND ALL LINKS IN THE SIDEBAR
     * ============================================================
     */

    var links = sidebar.getElementsByTagName( 'a' );

    var sidebarLinks = Array.prototype.slice.call( links );


    /*
     * ============================================================
     * CREATE A DROPDOWN
     * ============================================================
     *
     * This function creates a dropdown from an object.
     *
     * It can call itself to create another dropdown inside it.
     */

    function createDropdown( menuData, parentLink ) {

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

        dropdown.className = 'exuvia-dropdown-menu exuvia-dropdown-level-2';

        dropdown.style.display = 'none';


        Object.keys( menuData ).forEach( function ( name ) {

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


            /*
             * ====================================================
             * SECOND-LEVEL DROPDOWN
             * ====================================================
             *
             * If the value is an array, it is a normal list of
             * pages.
             *
             * If the value is an object, it contains another
             * dropdown.
             */

            if ( Array.isArray( menuData[ name ] ) ) {

                /*
                 * Normal page group
                 */

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

                link.href = mw.util.getUrl( name );

                link.textContent = name;

                item.appendChild( link );


                /*
                 * If this is being used as a dropdown title,
                 * it can be turned into a submenu later.
                 */

            } else {

                /*
                 * =================================================
                 * CREATE SECOND-LEVEL TITLE
                 * =================================================
                 */

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

                link.href = '#';

                link.textContent = name;

                link.className = 'exuvia-dropdown-title';


                /*
                 * Arrow
                 */

                var arrow = document.createElement( 'span' );

                arrow.className = 'exuvia-dropdown-arrow';

                arrow.textContent = ' ▼';

                link.appendChild( arrow );


                /*
                 * Create the next level recursively.
                 */

                var submenu = createDropdown(
                    menuData[ name ],
                    link
                );


                /*
                 * Add submenu to this item.
                 */

                item.appendChild( link );

                item.appendChild( submenu );


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

                link.addEventListener( 'click', function ( event ) {

                    event.preventDefault();

                    if ( submenu.style.display === 'none' ) {

                        submenu.style.display = 'block';

                        arrow.textContent = ' ▲';

                    } else {

                        submenu.style.display = 'none';

                        arrow.textContent = ' ▼';

                    }

                });

            }


            dropdown.appendChild( item );

        });


        return dropdown;
    }


    /*
     * ============================================================
     * PROCESS TOP-LEVEL MENUS
     * ============================================================
     */

    Object.keys( menus ).forEach( function ( menuName ) {

        var mainLink = null;


        /*
         * Find the sidebar link matching the menu name.
         */

        sidebarLinks.some( function ( link ) {

            var text = link.textContent.trim();

            if ( text === menuName ) {

                mainLink = link;

                return true;
            }

            return false;

        });


        if ( !mainLink ) {
            return;
        }


        /*
         * ========================================================
         * FIND THE SIDEBAR ITEM
         * ========================================================
         */

        var parent = mainLink.closest( 'li' );

        if ( !parent ) {
            return;
        }


        /*
         * ========================================================
         * CREATE TOP-LEVEL DROPDOWN
         * ========================================================
         */

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

        dropdown.className = 'exuvia-dropdown-menu exuvia-dropdown-level-1';

        dropdown.style.display = 'none';


        /*
         * ========================================================
         * CREATE FIRST-LEVEL ITEMS
         * ========================================================
         */

        Object.keys( menus[ menuName ] ).forEach( function ( name ) {

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

            /*
             * Every entry in the top-level menu is treated as
             * a submenu title.
             */

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

            link.href = '#';

            link.textContent = name;

            link.className = 'exuvia-dropdown-title';


            /*
             * Arrow
             */

            var arrow = document.createElement( 'span' );

            arrow.className = 'exuvia-dropdown-arrow';

            arrow.textContent = ' ▼';

            link.appendChild( arrow );


            /*
             * ====================================================
             * CREATE SECOND LEVEL
             * ====================================================
             */

            var submenuData = menus[ menuName ][ name ];

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

            submenu.className =
                'exuvia-dropdown-menu exuvia-dropdown-level-2';

            submenu.style.display = 'none';


            /*
             * Add the actual pages.
             */

            submenuData.forEach( function ( pageName ) {

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

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

                pageLink.href = mw.util.getUrl( pageName );

                pageLink.textContent = pageName;

                pageItem.appendChild( pageLink );

                submenu.appendChild( pageItem );

            });


            /*
             * Add submenu.
             */

            item.appendChild( link );

            item.appendChild( submenu );

            dropdown.appendChild( item );


            /*
             * ====================================================
             * OPEN / CLOSE SECOND LEVEL
             * ====================================================
             */

            link.addEventListener( 'click', function ( event ) {

                event.preventDefault();

                if ( submenu.style.display === 'none' ) {

                    submenu.style.display = 'block';

                    arrow.textContent = ' ▲';

                } else {

                    submenu.style.display = 'none';

                    arrow.textContent = ' ▼';

                }

            });

        });


        /*
         * ========================================================
         * ADD DROPDOWN TO SIDEBAR
         * ========================================================
         */

        parent.appendChild( dropdown );


        /*
         * ========================================================
         * TOP-LEVEL ARROW
         * ========================================================
         */

        var arrow = document.createElement( 'span' );

        arrow.className = 'exuvia-dropdown-arrow';

        arrow.textContent = ' ▼';

        mainLink.appendChild( arrow );


        /*
         * Mark as dropdown title.
         */

        mainLink.classList.add( 'exuvia-dropdown-title' );


        /*
         * ========================================================
         * OPEN / CLOSE TOP LEVEL
         * ========================================================
         */

        mainLink.addEventListener( 'click', function ( event ) {

            event.preventDefault();

            if ( dropdown.style.display === 'none' ) {

                dropdown.style.display = 'block';

                arrow.textContent = ' ▲';

            } else {

                dropdown.style.display = 'none';

                arrow.textContent = ' ▼';

            }

        });

    });

});