MediaWiki:Vector.js: Difference between revisions

From Geometry Dash Expanded Wiki
Jump to navigation Jump to search
Add the square wiki logo to the top-left header bar
Add English/Spanish language navigation and a compact mobile header
Line 264: Line 264:
         document.addEventListener('keydown', function (event) {
         document.addEventListener('keydown', function (event) {
             if (event.key === 'Escape') { appearance.open = false; account.open = false; }
             if (event.key === 'Escape') { appearance.open = false; account.open = false; }
        });
    });
}());
/* Expanded multilingual navigation: shared accounts, files and Recent Changes. */
(function () {
    'use strict';
    mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
        $(function () {
            var controls = document.querySelector('.gd-header-controls');
            if (!controls || document.getElementById('gd-language-menu')) return;
            var page = (mw.config.get('wgPageName') || 'Main_Page').replace(/_/g, ' ');
            var match = /^(Spa|Rus|Pol)\/(.+)$/.exec(page);
            var code = match ? match[1].toLowerCase() : 'eng';
            var base = match ? match[2] : page;
            var spanish = code === 'spa';
            var isArticle = mw.config.get('wgNamespaceNumber') === 0;
            document.documentElement.setAttribute('data-gd-language', code);
            var definitions = [
                {code:'eng', name:'English', lang:'en', flag:'/images/4/49/Flag_gb.svg'},
                {code:'spa', name:'Español', lang:'es', flag:'/images/2/2d/Flag_es.svg'}
            ];
            var menu = document.createElement('details');
            menu.id = 'gd-language-menu'; menu.className = 'gd-header-menu gd-language-menu';
            var summary = document.createElement('summary');
            summary.setAttribute('aria-label', spanish ? 'Cambiar idioma' : 'Change language');
            summary.title = summary.getAttribute('aria-label');
            var current = definitions[spanish ? 1 : 0];
            var image = document.createElement('img'); image.src=current.flag; image.alt=''; image.width=24; image.height=16;
            summary.appendChild(image);
            var label=document.createElement('span'); label.textContent=current.name; summary.appendChild(label);
            menu.appendChild(summary);
            var panel=document.createElement('div'); panel.className='gd-header-dropdown gd-language-options';
            panel.setAttribute('aria-label', spanish ? 'Idiomas' : 'Languages');
            menu.appendChild(panel);
            controls.insertBefore(menu, controls.children[1] || null);
            var api=new mw.Api();
            function route(title, lang) {
                if (!mw.config.get('wgExpandedLanguageRoutes')) return mw.util.getUrl(title, {uselang:lang});
                var m=/^(Spa|Rus|Pol)\/(.+)$/.exec(title);
                var language=m ? m[1].toLowerCase() : 'eng';
                var name=m ? m[2] : title;
                if (name.indexOf(':')!==-1 && !m) return mw.util.getUrl(title, {uselang:lang});
                return '/'+language+'/'+encodeURIComponent(name.replace(/ /g,'_')).replace(/%2F/g,'/')+'?uselang='+lang;
            }
            function build(mapping) {
                var entry=mapping[base] || {};
                var titles=definitions.map(function (d) {
                    return isArticle ? (entry[d.code] || (d.code==='eng' ? base : 'Spa/'+base)) : page;
                });
                api.get({action:'query',titles:titles.join('|'),prop:'info',formatversion:2}).then(function (data) {
                    var existing={};
                    (data.query.pages || []).forEach(function (p) {existing[p.title]=!p.missing && !p.invalid;});
                    definitions.forEach(function (d,i) {
                        var target=titles[i], available=existing[target];
                        var link=document.createElement('a'); link.lang=d.lang; link.hreflang=d.lang;
                        var flag=document.createElement('img'); flag.src=d.flag; flag.alt=''; flag.width=24; flag.height=16;
                        link.appendChild(flag);
                        var text=document.createElement('span'); text.textContent=d.name;
                        link.appendChild(text);
                        if (d.code===code) {link.setAttribute('aria-current','page');}
                        if (available || !isArticle) {
                            link.href=route(target,d.lang);
                        } else {
                            link.className='gd-language-missing';
                            link.href=mw.util.getUrl(target,{action:'edit',uselang:d.lang});
                            var note=document.createElement('small');note.textContent=spanish ? 'Crear traducción' : 'Create translation';link.appendChild(note);
                        }
                        panel.appendChild(link);
                    });
                    var home=document.createElement('a');home.textContent=spanish ? 'Inicio en español' : 'Spanish home';home.href=route('Spa/Main Page','es');panel.appendChild(home);
                    var guide=document.createElement('a');guide.textContent=spanish ? 'Guía de traducción' : 'Translation guide';guide.href=mw.util.getUrl('Help:Translations', {uselang:current.lang});panel.appendChild(guide);
                }, function () {panel.textContent=spanish ? 'No se pudieron cargar los idiomas. Recarga la página.' : 'Languages could not load. Reload the page.';});
            }
            api.get({action:'query',titles:'MediaWiki:Expanded-language-pages.json',prop:'revisions',rvprop:'content',rvslots:'main',formatversion:2}).then(function (data) {
                try {build(JSON.parse(data.query.pages[0].revisions[0].slots.main.content));} catch (error) {build({});}
            },function () {build({});});
            document.addEventListener('click',function (event) {if (!menu.contains(event.target)) menu.open=false;});
            document.addEventListener('keydown',function (event) {if (event.key==='Escape') menu.open=false;});
            if (spanish) {
                var appearance=controls.querySelector('.gd-header-menu summary');
                if (appearance) {appearance.textContent='Apariencia';appearance.title='Apariencia';appearance.setAttribute('aria-label','Apariencia');}
                var account=controls.querySelector('.gd-account-menu summary');
                if (account) {account.title='Menú de cuenta';account.setAttribute('aria-label','Menú de cuenta');}
                var titles={'Blue':'Azul','Purple':'Morado','Green':'Verde','Red':'Rojo','Dark':'Oscuro','Blackout':'Negro'};
                controls.querySelectorAll('.gd-theme-option').forEach(function (button) {if(titles[button.textContent])button.textContent=titles[button.textContent];});
            }
            // Optional translated assets use the same existing theme filters.
            var logos=mw.config.get('wgExpandedLanguageLogos') || {};
            if (logos[code]) {
                if (logos[code].header) {
                    var header=document.querySelector('.gd-header-bar-logo img');if(header)header.src=logos[code].header;
                }
                if (logos[code].banner) document.documentElement.style.setProperty('--expanded-wordmark-url','url("'+logos[code].banner.replace(/["\\]/g,'')+'")');
            }
         });
         });
     });
     });
}());
}());

Revision as of 23:08, 13 October 2026

/* All JavaScript here will be loaded for users of the Vector skin */

/***************************/
/* Interface Modification **/
/***************************/

/* JavaScript Load Class (REQUIRED BY DEFAULT VECTOR.CSS) */
$(function () {
	document.body.classList.add('has-vector-mobile-menu');
});

/* Adapt standard Vector's structure for the imported stylesheet. */
$(function () {
    if (!document.body.classList.contains('skin-vector-legacy')) return;

    var content = document.getElementById('content');
    var navigation = document.getElementById('mw-navigation');
    if (!content || !navigation) return;

    if (!content.parentElement.classList.contains('content-wrapper')) {
        var wrapper = document.createElement('div');
        wrapper.className = 'content-wrapper';
        content.before(wrapper);
        wrapper.append(navigation, content);
    }

    if (!content.querySelector('.content-body')) {
        $(content).wrapInner('<div class="content-body"></div>');
    }
});

/* Search Toggle Button */
$(function () {
	var buttonClass = 'button-toggle button-toggle-search';
	var expandedClass = 'search--expanded';
	let searchButton = document.createElement('button');
	let sidebar = document.getElementById('mw-navigation');
	searchButton.className = buttonClass;
	searchButton.addEventListener('click', function () {
		searchButton.classList.toggle(expandedClass);
		sidebar.classList.toggle(expandedClass);
	});
	sidebar.prepend(searchButton);
});


/* Mobile Sidebar Toggle Button */
$(function () {
	var buttonClass = 'button-toggle button-toggle-sidebar-mobile';
	var expandedClass = 'sidebar-mobile--expanded';
	let mobileSidebarButton = document.createElement('button');
	let sidebar = document.getElementById('mw-navigation');
	mobileSidebarButton.className = buttonClass;
	mobileSidebarButton.addEventListener('click', function () {
		mobileSidebarButton.classList.toggle(expandedClass);
		sidebar.classList.toggle(expandedClass);
	});
	sidebar.prepend(mobileSidebarButton);
});


/* Sidebar Toggle Button */
$(function () {
	var buttonClass = 'button-toggle button-toggle-sidebar';
	var expandedClass = 'sidebar--collapsed';
	let sidebarButton = document.createElement('button');
	let sidebar = document.getElementById('mw-navigation');
	sidebarButton.className = buttonClass;
	sidebarButton.addEventListener('click', function () {
		sidebarButton.classList.toggle(expandedClass);
		sidebar.classList.toggle(expandedClass);
	});
	sidebar.prepend(sidebarButton);
});


/* Sidebar Box Content Collaspible Toggles */
$(function() {
    // configure sections (by header string) to initially collapse for all users by default
    var initiallyCollapsedIDs = []; /* e.g. ['Character_Classes', 'Items', 'External_Links'] */

    // add the p- onto ids for ease of use, if not present
    initiallyCollapsedIDs.forEach(function(id, index) {
        if(!id.startsWith('p-')) {initiallyCollapsedIDs[index] = 'p-' + id;}
    });
	var $portals = $("#mw-panel .vector-menu-portal");
	var setup = function() {
		$portals.each(function(index, el) {
			if(window.innerWidth < 721) {return;}
			var $el = $(el);
			var $id = $el.attr("id");
			if(!$id) {return;}
			// check if this portal should be collapsed - portal is set to be initially collapsed and user has not indicated wanting it open || user has indicated wanting this portal collapsed
			if(
				(initiallyCollapsedIDs.includes($id) && localStorage.getItem('sidebar_c_'+$id) != "n") || (localStorage.getItem('sidebar_c_'+$id) === "y")
			) {$el.addClass('collapsed').find('.vector-menu-content').slideUp(0);}
		});
	};
	setup();
	$(window).on('resize', setup);
	$portals.on("click", "h3", function(event) {
		if(window.innerWidth < 721) {return;}
		var $el = $(this).parent();
		var $id = $el.attr("id");
		if(!$id) {return;}
		$el.toggleClass('collapsed');
		if($el.hasClass('collapsed')) { // more consistent between class and slide status
			localStorage.setItem('sidebar_c_'+$id, "y");
			$el.find('.vector-menu-content').slideUp('fast');
		}
		else {
			localStorage.setItem('sidebar_c_'+$id, "n");
			$el.find('.vector-menu-content').slideDown('fast');
		}
	});
});


/* Header Dropdown Menu Purge Cache Link */
$(function() {
	if (mw.config.get('wgNamespaceNumber') === -1 ) {return;} // disable on special pages
	var url = new URL(window.location.href);
	url.searchParams.set('action', 'purge');
	var button = mw.util.addPortletLink('p-cactions', url.href, 'Purge cache', 'ca-gadget-purgecache', 'Purge the server cache for this page', '1');
	button.addEventListener('click', (function(event) {
		event.preventDefault();
		var api = new mw.Api();
		api.post({
			action: 'purge',
			titles: mw.config.get('wgPageName'),
			forcerecursivelinkupdate: true
		}).then(function() {
			window.location.reload(true);
		}).fail(function(code) {
			mw.notify('Page purge unsuccessful. Error code: $1'.replace('$1', code));
		});
	}));
	console.log('purge loaded');
});

/* Independent GD Wiki header: themes and existing account links. */
(function () {
    'use strict';
    var themes = [
        ['blue', 'Geometry Dash'], ['green', 'Vault'],
        ['purple', 'Vault of Secrets'], ['red', 'Chamber of Time'],
        ['dark', 'Dark'], ['blackout', 'Blackout']
    ];
    var storageKey = 'gdwiki-theme';
    function applyTheme(key) {
        if (!themes.some(function (theme) { return theme[0] === key; })) key = 'blue';
        var root = document.documentElement;
        themes.forEach(function (theme) { root.classList.remove('theme-' + theme[0]); });
        root.classList.remove('view-light');
        root.classList.add('view-dark', 'theme-' + key);
        return key;
    }
    var selected = 'blue';
    try { selected = localStorage.getItem(storageKey) || selected; } catch (error) {}
    selected = applyTheme(selected);
    $(function () {
        if (!document.body.classList.contains('skin-vector-legacy')) return;
        var personal = document.getElementById('p-personal');
        if (!personal || personal.querySelector('.gd-header-controls')) return;
        var links = personal.querySelector('ul');
        if (!links) return;
        var controls = document.createElement('div');
        controls.className = 'gd-header-controls';
        var appearance = document.createElement('details');
        appearance.className = 'gd-header-menu';
        var appearanceLabel = document.createElement('summary');
        appearanceLabel.textContent = 'Appearance';
        appearance.appendChild(appearanceLabel);
        var themePanel = document.createElement('div');
        themePanel.className = 'gd-header-dropdown';
        themePanel.setAttribute('role', 'group');
        themePanel.setAttribute('aria-label', 'Theme');
        themes.forEach(function (theme) {
            var button = document.createElement('button');
            button.type = 'button';
            button.className = 'gd-theme-option';
            button.textContent = theme[1];
            button.setAttribute('aria-pressed', String(theme[0] === selected));
            button.addEventListener('click', function () {
                selected = applyTheme(theme[0]);
                try { localStorage.setItem(storageKey, selected); } catch (error) {}
                themePanel.querySelectorAll('button').forEach(function (item) {
                    item.setAttribute('aria-pressed', String(item === button));
                });
                appearance.open = false;
            });
            themePanel.appendChild(button);
        });
        appearance.appendChild(themePanel);
        controls.appendChild(appearance);
        var icons = {
            gear: '<path d="m10 2 .5 2a8 8 0 0 1 3 0l.5-2 3 1 0 2a8 8 0 0 1 2 2l2 0 1 3-2 .5a8 8 0 0 1 0 3l2 .5-1 3-2 0a8 8 0 0 1-2 2l0 2-3 1-.5-2a8 8 0 0 1-3 0L10 22l-3-1 0-2a8 8 0 0 1-2-2l-2 0-1-3 2-.5a8 8 0 0 1 0-3L2 10l1-3 2 0a8 8 0 0 1 2-2l0-2z"/><circle cx="12" cy="12" r="3" fill="#03052f"/>',
            watch: '<path d="M3 3h18v15h-6l-2 3h-2l-2-3H3zm2 2v11h5l2 3 2-3h5V5z"/>',
            user: '<circle cx="12" cy="7" r="4"/><path d="M3 22v-2a9 9 0 0 1 18 0v2z"/>'
        };
        function iconMarkup(name) {
            return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' + icons[name] + '</svg>';
        }
        function iconLink(id, name, label) {
            var original = document.querySelector('#' + id + ' a');
            if (!original) return;
            var link = original.cloneNode(false);
            link.className = 'gd-header-icon';
            link.title = label;
            link.setAttribute('aria-label', label);
            link.innerHTML = iconMarkup(name);
            controls.appendChild(link);
        }
        iconLink('pt-preferences', 'gear', 'Preferences');
        // Use Echo's actual widgets so alerts and notices retain their behavior.
        ['pt-notifications-alert', 'pt-notifications-notice'].forEach(function (id) {
            var notification = document.getElementById(id);
            if (notification) {
                notification.classList.add('gd-header-notification');
                controls.appendChild(notification);
            }
        });
        var account = document.createElement('details');
        account.className = 'gd-header-menu gd-account-menu';
        var accountLabel = document.createElement('summary');
        accountLabel.setAttribute('aria-label', 'Account menu');
        accountLabel.title = 'Account menu';
        var accountGroup = document.createElement('div');
        accountGroup.className = 'gd-account-group';
        var accountName = document.createElement('a');
        accountName.className = 'gd-account-talk-link';
        var userName = mw.config.get('wgUserName');
        accountName.href = mw.util.getUrl(userName ? 'User:' + userName : 'Special:UserLogin');
        accountName.innerHTML = iconMarkup('user');
        var nameText = document.createElement('span');
        nameText.textContent = userName || 'Account';
        accountName.appendChild(nameText);
        accountName.title = userName ? 'Your user page' : 'Log in';
        accountGroup.appendChild(accountName);
        account.appendChild(accountLabel);
        var duplicateUserPage = links.querySelector('#pt-userpage');
        if (duplicateUserPage) duplicateUserPage.remove();
        links.classList.add('gd-account-links');
        account.appendChild(links);
        accountGroup.appendChild(account);
        controls.appendChild(accountGroup);
        var barLogo = document.createElement('a');
        barLogo.className = 'gd-header-bar-logo';
        barLogo.href = mw.util.getUrl(mw.config.get('wgMainPageTitle') || 'Main Page');
        barLogo.title = mw.config.get('wgSiteName') || 'Geometry Dash Expanded Wiki';
        barLogo.setAttribute('aria-label', barLogo.title + ' main page');
        var logoImage = document.createElement('img');
        logoImage.src = 'https://geometrydashwiki.com/images/d/d4/Logo.jpg';
        logoImage.alt = ''; logoImage.width = 32; logoImage.height = 32;
        barLogo.appendChild(logoImage);
        personal.appendChild(barLogo);
        personal.appendChild(controls);
        personal.classList.add('gd-header-ready');
        document.addEventListener('click', function (event) {
            [appearance, account].forEach(function (menu) {
                if (!menu.contains(event.target)) menu.open = false;
            });
        });
        document.addEventListener('keydown', function (event) {
            if (event.key === 'Escape') { appearance.open = false; account.open = false; }
        });
    });
}());
/* Expanded multilingual navigation: shared accounts, files and Recent Changes. */
(function () {
    'use strict';
    mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
        $(function () {
            var controls = document.querySelector('.gd-header-controls');
            if (!controls || document.getElementById('gd-language-menu')) return;
            var page = (mw.config.get('wgPageName') || 'Main_Page').replace(/_/g, ' ');
            var match = /^(Spa|Rus|Pol)\/(.+)$/.exec(page);
            var code = match ? match[1].toLowerCase() : 'eng';
            var base = match ? match[2] : page;
            var spanish = code === 'spa';
            var isArticle = mw.config.get('wgNamespaceNumber') === 0;
            document.documentElement.setAttribute('data-gd-language', code);
            var definitions = [
                {code:'eng', name:'English', lang:'en', flag:'/images/4/49/Flag_gb.svg'},
                {code:'spa', name:'Español', lang:'es', flag:'/images/2/2d/Flag_es.svg'}
            ];
            var menu = document.createElement('details');
            menu.id = 'gd-language-menu'; menu.className = 'gd-header-menu gd-language-menu';
            var summary = document.createElement('summary');
            summary.setAttribute('aria-label', spanish ? 'Cambiar idioma' : 'Change language');
            summary.title = summary.getAttribute('aria-label');
            var current = definitions[spanish ? 1 : 0];
            var image = document.createElement('img'); image.src=current.flag; image.alt=''; image.width=24; image.height=16;
            summary.appendChild(image);
            var label=document.createElement('span'); label.textContent=current.name; summary.appendChild(label);
            menu.appendChild(summary);
            var panel=document.createElement('div'); panel.className='gd-header-dropdown gd-language-options';
            panel.setAttribute('aria-label', spanish ? 'Idiomas' : 'Languages');
            menu.appendChild(panel);
            controls.insertBefore(menu, controls.children[1] || null);
            var api=new mw.Api();
            function route(title, lang) {
                if (!mw.config.get('wgExpandedLanguageRoutes')) return mw.util.getUrl(title, {uselang:lang});
                var m=/^(Spa|Rus|Pol)\/(.+)$/.exec(title);
                var language=m ? m[1].toLowerCase() : 'eng';
                var name=m ? m[2] : title;
                if (name.indexOf(':')!==-1 && !m) return mw.util.getUrl(title, {uselang:lang});
                return '/'+language+'/'+encodeURIComponent(name.replace(/ /g,'_')).replace(/%2F/g,'/')+'?uselang='+lang;
            }
            function build(mapping) {
                var entry=mapping[base] || {};
                var titles=definitions.map(function (d) {
                    return isArticle ? (entry[d.code] || (d.code==='eng' ? base : 'Spa/'+base)) : page;
                });
                api.get({action:'query',titles:titles.join('|'),prop:'info',formatversion:2}).then(function (data) {
                    var existing={};
                    (data.query.pages || []).forEach(function (p) {existing[p.title]=!p.missing && !p.invalid;});
                    definitions.forEach(function (d,i) {
                        var target=titles[i], available=existing[target];
                        var link=document.createElement('a'); link.lang=d.lang; link.hreflang=d.lang;
                        var flag=document.createElement('img'); flag.src=d.flag; flag.alt=''; flag.width=24; flag.height=16;
                        link.appendChild(flag);
                        var text=document.createElement('span'); text.textContent=d.name;
                        link.appendChild(text);
                        if (d.code===code) {link.setAttribute('aria-current','page');}
                        if (available || !isArticle) {
                            link.href=route(target,d.lang);
                        } else {
                            link.className='gd-language-missing';
                            link.href=mw.util.getUrl(target,{action:'edit',uselang:d.lang});
                            var note=document.createElement('small');note.textContent=spanish ? 'Crear traducción' : 'Create translation';link.appendChild(note);
                        }
                        panel.appendChild(link);
                    });
                    var home=document.createElement('a');home.textContent=spanish ? 'Inicio en español' : 'Spanish home';home.href=route('Spa/Main Page','es');panel.appendChild(home);
                    var guide=document.createElement('a');guide.textContent=spanish ? 'Guía de traducción' : 'Translation guide';guide.href=mw.util.getUrl('Help:Translations', {uselang:current.lang});panel.appendChild(guide);
                }, function () {panel.textContent=spanish ? 'No se pudieron cargar los idiomas. Recarga la página.' : 'Languages could not load. Reload the page.';});
            }
            api.get({action:'query',titles:'MediaWiki:Expanded-language-pages.json',prop:'revisions',rvprop:'content',rvslots:'main',formatversion:2}).then(function (data) {
                try {build(JSON.parse(data.query.pages[0].revisions[0].slots.main.content));} catch (error) {build({});}
            },function () {build({});});
            document.addEventListener('click',function (event) {if (!menu.contains(event.target)) menu.open=false;});
            document.addEventListener('keydown',function (event) {if (event.key==='Escape') menu.open=false;});
            if (spanish) {
                var appearance=controls.querySelector('.gd-header-menu summary');
                if (appearance) {appearance.textContent='Apariencia';appearance.title='Apariencia';appearance.setAttribute('aria-label','Apariencia');}
                var account=controls.querySelector('.gd-account-menu summary');
                if (account) {account.title='Menú de cuenta';account.setAttribute('aria-label','Menú de cuenta');}
                var titles={'Blue':'Azul','Purple':'Morado','Green':'Verde','Red':'Rojo','Dark':'Oscuro','Blackout':'Negro'};
                controls.querySelectorAll('.gd-theme-option').forEach(function (button) {if(titles[button.textContent])button.textContent=titles[button.textContent];});
            }
            // Optional translated assets use the same existing theme filters.
            var logos=mw.config.get('wgExpandedLanguageLogos') || {};
            if (logos[code]) {
                if (logos[code].header) {
                    var header=document.querySelector('.gd-header-bar-logo img');if(header)header.src=logos[code].header;
                }
                if (logos[code].banner) document.documentElement.style.setProperty('--expanded-wordmark-url','url("'+logos[code].banner.replace(/["\\]/g,'')+'")');
            }
        });
    });
}());