MediaWiki:Common.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.
/* Any JavaScript here will be loaded for all users on every page load. */
/* ===== OOO Tab Scroll — minimal, production-safe ===== */
mw.hook('wikipage.content').add(function ($content) {
var wraps = $content.find('.ooo-infobox-tabwrap.ooo-infobox-tabwrap--scroll');
if (!wraps.length) return;
wraps.each(function () {
var wrap = this;
var tabs = wrap.querySelector('.tabber__tabs');
if (!tabs) return;
// Avoid duplicates
if (wrap.querySelector('.ooo-tab-arrow')) return;
// Ensure positioned container for absolute arrows
var csWrap = getComputedStyle(wrap);
if (csWrap.position === 'static') wrap.style.position = 'relative';
// Leave padding so tabs don't sit under the arrows
var PAD = 26;
var csTabs = getComputedStyle(tabs);
var pil = parseInt(csTabs.paddingInlineStart || 0, 10) || 0;
var pie = parseInt(csTabs.paddingInlineEnd || 0, 10) || 0;
tabs.style.scrollBehavior = 'smooth';
tabs.style.paddingInlineStart = (pil + PAD) + 'px';
tabs.style.paddingInlineEnd = (pie + PAD) + 'px';
// RTL awareness (visual direction)
var isRTL = getComputedStyle(wrap).direction === 'rtl';
function scrollByAmount(dir) {
var amount = Math.max(120, Math.round(tabs.clientWidth * 0.7));
var sign = (dir === 'prev') ? -1 : 1;
if (isRTL) sign *= -1;
tabs.scrollBy({ left: sign * amount, behavior: 'smooth' });
}
function makeBtn(dir) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'ooo-tab-arrow ooo-tab-arrow--' + dir;
b.setAttribute('aria-label', dir === 'prev'
? 'Scroll tabs ' + (isRTL ? 'right' : 'left')
: 'Scroll tabs ' + (isRTL ? 'left' : 'right')
);
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 20 20');
svg.setAttribute('width', '12');
svg.setAttribute('height', '12');
svg.setAttribute('focusable', 'false');
svg.setAttribute('aria-hidden', 'true');
svg.classList.add('ooo-tab-arrow__icon');
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', dir === 'prev' ? 'M12.5 15l-5-5 5-5' : 'M7.5 5l5 5-5 5');
path.setAttribute('fill', 'none');
path.setAttribute('stroke', 'currentColor');
path.setAttribute('stroke-width', '2');
path.setAttribute('stroke-linecap', 'round');
path.setAttribute('stroke-linejoin', 'round');
path.setAttribute('vector-effect', 'non-scaling-stroke');
svg.appendChild(path);
b.appendChild(svg);
b.addEventListener('click', function () { scrollByAmount(dir); });
b.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); scrollByAmount(dir); }
if (e.key === 'ArrowLeft') { e.preventDefault(); scrollByAmount(isRTL ? 'next' : 'prev'); }
if (e.key === 'ArrowRight') { e.preventDefault(); scrollByAmount(isRTL ? 'prev' : 'next'); }
if (e.key === 'Home') { e.preventDefault(); tabs.scrollTo({ left: isRTL ? tabs.scrollWidth : 0, behavior: 'smooth' }); }
if (e.key === 'End') { e.preventDefault(); tabs.scrollTo({ left: isRTL ? 0 : tabs.scrollWidth, behavior: 'smooth' }); }
});
return b;
}
wrap.appendChild(makeBtn('prev'));
wrap.appendChild(makeBtn('next'));
function update() {
var prev = wrap.querySelector('.ooo-tab-arrow--prev');
var next = wrap.querySelector('.ooo-tab-arrow--next');
var sl = tabs.scrollLeft;
// Normalize some RTL engines that use negative scrollLeft
if (isRTL) sl = Math.abs(sl);
var atStart = sl <= 2;
var atEnd = sl + tabs.clientWidth >= tabs.scrollWidth - 2;
if (prev) prev.disabled = atStart;
if (next) next.disabled = atEnd;
// Hide both if there is no overflow
var overflow = tabs.scrollWidth > tabs.clientWidth + 2;
if (prev) prev.classList.toggle('is-hidden', !overflow);
if (next) next.classList.toggle('is-hidden', !overflow);
}
tabs.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update);
if (window.ResizeObserver) new ResizeObserver(update).observe(tabs);
// Initial
update();
});
});