MediaWiki:Common.js: Difference between revisions
Appearance
mNo edit summary |
mNo edit summary |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 116: | Line 116: | ||
mw.hook('wikipage.content').add(function () { | mw.hook('wikipage.content').add(function () { | ||
if (!location.hash) return; | |||
const id = location.hash.slice(1); | |||
setTimeout(function(){ | setTimeout(function(){ | ||
const item = document.querySelector('.ooo-browser__item[data-target="'+ | const item = document.querySelector('.ooo-browser__item[data-target="'+id+'"]'); | ||
const panel = document.getElementById( | const panel = document.getElementById(id); | ||
if (!item || !panel) return; | if (!item || !panel) return; | ||
| Line 129: | Line 130: | ||
if (!browser) return; | if (!browser) return; | ||
browser.querySelectorAll('.ooo-browser__item') | browser.querySelectorAll('.ooo-browser__item') | ||
.forEach(el => el.classList.remove('is-active')); | .forEach(el => el.classList.remove('is-active')); | ||
browser.querySelectorAll('.ooo-browser__info') | browser.querySelectorAll('.ooo-browser__info') | ||
.forEach(el => el.classList.remove('is-active')); | .forEach(el => el.classList.remove('is-active')); | ||
item.classList.add('is-active'); | |||
panel.classList.add('is-active'); | panel.classList.add('is-active'); | ||
},150); | |||
}); | |||
/* ========================================================= | |||
OOO Browser controller (click switching) | |||
========================================================= */ | |||
document.addEventListener("click", function (e) { | |||
const item = e.target.closest(".ooo-browser__item"); | |||
if (!item) return; | |||
const browser = item.closest(".ooo-browser"); | |||
if (!browser) return; | |||
const target = item.getAttribute("data-target"); | |||
if (!target) return; | |||
e.preventDefault(); | |||
browser.querySelectorAll(".ooo-browser__item") | |||
.forEach(el => el.classList.remove("is-active")); | |||
browser.querySelectorAll(".ooo-browser__info") | |||
.forEach(el => el.classList.remove("is-active")); | |||
item.classList.add("is-active"); | |||
const panel = browser.querySelector("#" + CSS.escape(target)); | |||
if (panel) panel.classList.add("is-active"); | |||
/* update browser URL */ | |||
history.replaceState(null, null, "#" + target); | |||
}); | }); | ||
Latest revision as of 13:33, 6 March 2026
/* 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();
});
});
/* =========================================================
OOO Anchor navigation (tabs + browser)
========================================================= */
mw.hook('wikipage.content').add(function () {
if (!location.hash) return;
const id = location.hash.slice(1);
setTimeout(function(){
const item = document.querySelector('.ooo-browser__item[data-target="'+id+'"]');
const panel = document.getElementById(id);
if (!item || !panel) return;
const browser = item.closest('.ooo-browser');
if (!browser) return;
browser.querySelectorAll('.ooo-browser__item')
.forEach(el => el.classList.remove('is-active'));
browser.querySelectorAll('.ooo-browser__info')
.forEach(el => el.classList.remove('is-active'));
item.classList.add('is-active');
panel.classList.add('is-active');
},150);
});
/* =========================================================
OOO Browser controller (click switching)
========================================================= */
document.addEventListener("click", function (e) {
const item = e.target.closest(".ooo-browser__item");
if (!item) return;
const browser = item.closest(".ooo-browser");
if (!browser) return;
const target = item.getAttribute("data-target");
if (!target) return;
e.preventDefault();
browser.querySelectorAll(".ooo-browser__item")
.forEach(el => el.classList.remove("is-active"));
browser.querySelectorAll(".ooo-browser__info")
.forEach(el => el.classList.remove("is-active"));
item.classList.add("is-active");
const panel = browser.querySelector("#" + CSS.escape(target));
if (panel) panel.classList.add("is-active");
/* update browser URL */
history.replaceState(null, null, "#" + target);
});