MediaWiki:Timeless.js
From Game Wiki - VortanMU
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ==============================
VortanMU - Timeless Sidebar V3
- Normaliza sidebar esquerda
- Remove resíduos (has-mega/submenu inline)
- Accordion moderno + persistência (localStorage)
============================== */
(function () {
'use strict';
const STORAGE_KEY = 'vm_sidebar_open_portlets_v3';
function qsa(root, sel) {
return Array.prototype.slice.call((root || document).querySelectorAll(sel));
}
function readState() {
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
catch (e) { return []; }
}
function writeState(ids) {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(ids)); }
catch (e) {}
}
function removeInjectedSubmenus(portlet) {
// remove any injected submenu lists
qsa(portlet, 'ul.submenu, ul[class*="submenu"]').forEach(ul => ul.remove());
// ensure original UL is visible (remove inline display none)
const body = portlet.querySelector('.mw-portlet-body');
if (!body) return;
const ul = body.querySelector('ul');
if (!ul) return;
ul.style.display = '';
ul.hidden = false;
}
function sanitizeHeader(header) {
header.classList.remove('has-mega', 'active', 'open', 'is-open');
header.classList.add('vm-portlet__header');
header.setAttribute('tabindex', '0');
header.setAttribute('role', 'button');
}
function ensurePortletId(portlet, idx) {
if (!portlet.id) portlet.id = 'vm-portlet-' + idx;
return portlet.id;
}
function setOpen(portlet, header, open) {
portlet.classList.toggle('is-open', open);
header.setAttribute('aria-expanded', open ? 'true' : 'false');
}
function normalizeLeftSidebar() {
const nav = document.getElementById('mw-site-navigation');
if (!nav) return;
nav.classList.add('vm-nav');
const inner = nav.querySelector('.sidebar-inner') || nav;
const portlets = qsa(inner, '.mw-portlet[role="navigation"]');
const saved = new Set(readState());
portlets.forEach((p, idx) => {
p.classList.add('vm-portlet');
const header = p.querySelector('h3');
const body = p.querySelector('.mw-portlet-body');
if (!header || !body) return;
const id = ensurePortletId(p, idx);
sanitizeHeader(header);
removeInjectedSubmenus(p);
// default open: use saved state, otherwise open only first
const shouldOpen = saved.size ? saved.has(id) : (idx === 0);
setOpen(p, header, shouldOpen);
if (!header.__vmBound) {
const toggle = () => {
const willOpen = !p.classList.contains('is-open');
// accordion behavior (open one at a time)
portlets.forEach(other => {
if (other !== p) {
const h = other.querySelector('h3.vm-portlet__header');
if (h) setOpen(other, h, false);
}
});
setOpen(p, header, willOpen);
// persist open ones (only one open)
const openIds = portlets
.filter(x => x.classList.contains('is-open'))
.map(x => x.id)
.filter(Boolean);
writeState(openIds);
};
header.addEventListener('click', toggle);
header.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggle();
}
});
header.__vmBound = true;
}
});
// guard: if some script tries to inject submenu again, delete it
if (!inner.__vmObserver) {
const obs = new MutationObserver(() => {
const injected = inner.querySelector('ul.submenu, ul[class*="submenu"]');
if (injected) {
portlets.forEach(p => removeInjectedSubmenus(p));
}
});
obs.observe(inner, { childList: true, subtree: true });
inner.__vmObserver = obs;
}
}
function runSoon() {
normalizeLeftSidebar();
setTimeout(normalizeLeftSidebar, 200);
setTimeout(normalizeLeftSidebar, 900);
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(() => runSoon());
} else if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', runSoon);
} else {
runSoon();
}
})();