MediaWiki:Jotunnheim.js
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.
/* Keep the desktop TOC beside the article and expose two drawers on phones. */
(function () {
var mobile = window.matchMedia('(max-width: 50rem)');
function makeButton(name, label, icon) {
var button = document.createElement('button');
button.type = 'button';
button.className = 'jth-mobile-' + name;
button.setAttribute('aria-label', 'Open ' + label);
button.setAttribute('title', label);
button.setAttribute('aria-expanded', 'false');
button.innerHTML = '<svg class="jth-open-icon" viewBox="0 0 24 24" aria-hidden="true">' +
icon + '</svg>' +
'<svg class="jth-close-icon" viewBox="0 0 24 24" aria-hidden="true">' +
'<path d="M5 5l14 14M19 5L5 19"/></svg>';
return button;
}
function init() {
var shell = document.querySelector('body.skin-jotunnheim .jth-shell');
var nav = shell && shell.querySelector('.jth-nav');
var page = shell && shell.querySelector(':scope > .jth-main');
var main = page && page.querySelector('main#content');
var content = main && main.querySelector('.mw-body-content');
if (!nav || !page || !main || !content) {
return;
}
var toc = content.querySelector('#toc');
var column = null;
if (toc) {
column = document.createElement('div');
column.className = 'jth-toc-column';
column.id = 'jth-mobile-toc';
column.tabIndex = -1;
column.setAttribute('role', 'region');
column.setAttribute('aria-label', 'Table of contents');
content.parentNode.insertBefore(column, content);
column.appendChild(toc);
}
nav.id = nav.id || 'jth-mobile-sidebar';
nav.tabIndex = -1;
nav.setAttribute('aria-label', 'Site menu');
var backdrop = document.createElement('div');
backdrop.className = 'jth-mobile-backdrop';
backdrop.hidden = true;
backdrop.setAttribute('aria-hidden', 'true');
var controls = document.createElement('div');
controls.className = 'jth-mobile-controls';
var menuButton = makeButton('menu', 'menu', '<path d="M4 6h16M4 12h16M4 18h16"/>');
menuButton.setAttribute('aria-controls', nav.id);
controls.appendChild(menuButton);
var tocButton = null;
if (column) {
tocButton = makeButton('toc', 'table of contents',
'<path d="M9 6h11M9 12h11M9 18h11"/>' +
'<circle cx="5" cy="6" r="1"/><circle cx="5" cy="12" r="1"/>' +
'<circle cx="5" cy="18" r="1"/>');
tocButton.setAttribute('aria-controls', column.id);
controls.appendChild(tocButton);
}
document.body.append(backdrop, controls);
var openPanel = null;
function setPanel(next, restoreFocus) {
var previous = openPanel;
openPanel = next;
document.documentElement.classList.toggle('jth-menu-open', next === 'menu');
document.documentElement.classList.toggle('jth-toc-open', next === 'toc');
document.body.classList.toggle('jth-mobile-panel-open', !!next);
backdrop.hidden = !next;
menuButton.setAttribute('aria-expanded', String(next === 'menu'));
menuButton.setAttribute('aria-label', (next === 'menu' ? 'Close' : 'Open') + ' menu');
if (tocButton) {
tocButton.setAttribute('aria-expanded', String(next === 'toc'));
tocButton.setAttribute('aria-label',
(next === 'toc' ? 'Close' : 'Open') + ' table of contents');
}
nav.inert = mobile.matches && next !== 'menu';
if (column) {
column.inert = mobile.matches && next !== 'toc';
}
page.inert = mobile.matches && !!next;
if (next) {
(next === 'menu' ? nav : column).focus({ preventScroll: true });
} else if (restoreFocus && previous) {
(previous === 'menu' ? menuButton : tocButton).focus({ preventScroll: true });
}
}
function syncViewport() {
setPanel(null, false);
if (column) {
if (mobile.matches) {
document.body.appendChild(column);
} else {
content.parentNode.insertBefore(column, content);
}
}
nav.inert = mobile.matches;
if (column) {
column.inert = mobile.matches;
}
page.inert = false;
}
menuButton.addEventListener('click', function () {
if (mobile.matches) {
setPanel(openPanel === 'menu' ? null : 'menu', true);
}
});
if (tocButton) {
tocButton.addEventListener('click', function () {
if (mobile.matches) {
setPanel(openPanel === 'toc' ? null : 'toc', true);
}
});
column.addEventListener('click', function (event) {
if (event.target.closest('a[href^="#"]')) {
setPanel(null, false);
}
});
}
backdrop.addEventListener('click', function () {
setPanel(null, true);
});
nav.addEventListener('click', function (event) {
if (event.target.closest('a[href^="#"]')) {
setPanel(null, false);
}
});
document.addEventListener('keydown', function (event) {
if (!openPanel) {
return;
}
if (event.key === 'Escape') {
event.preventDefault();
setPanel(null, true);
} else if (event.key === 'Tab') {
var panel = openPanel === 'menu' ? nav : column;
var focusable = Array.from(panel.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), ' +
'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
)).filter(function (element) {
return element.getClientRects().length > 0;
});
var toggleButton = openPanel === 'menu' ? menuButton : tocButton;
if (!focusable.length) {
event.preventDefault();
toggleButton.focus();
return;
}
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (document.activeElement === toggleButton) {
event.preventDefault();
(event.shiftKey ? last : first).focus();
} else if (!panel.contains(document.activeElement) || document.activeElement === panel) {
event.preventDefault();
(event.shiftKey ? toggleButton : first).focus();
} else if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
toggleButton.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
toggleButton.focus();
}
}
});
document.documentElement.classList.add('jth-mobile-ready');
syncViewport();
mobile.addEventListener('change', syncViewport);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
init();
}
}());