MediaWiki:Common.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 14: | Line 14: | ||
const premiumEase = 'cubic-bezier(0.23, 1, 0.32, 1)'; | const premiumEase = 'cubic-bezier(0.23, 1, 0.32, 1)'; | ||
button. | button.addEvent.ener('mouseenter', () => { | ||
button.style.transition = `transform 0.15s ${premiumEase}`; | button.style.transition = `transform 0.15s ${premiumEase}`; | ||
}); | }); | ||
| Line 239: | Line 239: | ||
if (!list.length) return; | if (!list.length) return; | ||
list.forEach(function (ep) { | |||
var card = buildCard(ep); | |||
if (card) { | |||
cards.push(card); | |||
deck.appendChild(card); | |||
} | |||
}); | |||
for (var k = cards.length; k < 3; k++) { | |||
var g = el('div', 'pc-card pc-ghost'); | |||
g.dataset.pos = k; | |||
g.setAttribute('aria-hidden', 'true'); | |||
deck.appendChild(g); | |||
} | |||
root.classList.add('is-loaded'); | |||
layout(); | |||
} | } | ||
if (cards.length > 1) { | if (cards.length > 1) { | ||
Revision as of 22:20, 3 October 2026
/* Any JavaScript here will be loaded for all users on every page load. */
mw.hook('wikipage.content').add(function () {
const logo = document.querySelector('a.mw-logo');
if (logo) {
logo.setAttribute('href', 'https://fedoverse.com');
}
});
mw.hook('wikipage.content').add(function () {
const button = document.querySelector('.your-button-class');
if (!button) return;
const premiumEase = 'cubic-bezier(0.23, 1, 0.32, 1)';
button.addEvent.ener('mouseenter', () => {
button.style.transition = `transform 0.15s ${premiumEase}`;
});
button.addEventListener('mousemove', (e) => {
const rect = button.getBoundingClientRect();
const x = (e.clientX - rect.left - rect.width / 2) / (rect.width / 2);
const y = (e.clientY - rect.top - rect.height / 2) / (rect.height / 2);
button.style.transform = `perspective(400px) scale(1.05) rotateX(${y * -15}deg) rotateY(${x * 15}deg)`;
});
button.addEventListener('mouseleave', () => {
button.style.transition = `transform 0.65s ${premiumEase}`;
// Force the browser to commit the 0.65s transition before updating the transform
void button.offsetHeight;
button.style.transform = 'perspective(400px) scale(1) rotateX(0deg) rotateY(0deg)';
});
});
/* Open top search autocomplete result on Enter in Vector 2022 */
(function () {
window.addEventListener('keydown', function (e) {
if (e.key !== 'Enter') return;
const activeEl = document.activeElement;
// Check if user is typing in a search box
if (!activeEl || !activeEl.matches('input[type="search"], .cdx-text-input__input')) return;
// 1. Locate the first visible suggestion item in Codex / Vector 2022
const firstMenuItem = document.querySelector(
'.cdx-typeahead-search__menu .cdx-menu-item:not(.cdx-menu-item--disabled), ' +
'.cdx-menu__listbox .cdx-menu-item:not(.cdx-menu-item--disabled)'
);
if (!firstMenuItem) return;
// 2. Extract destination URL: either from an anchor or from its data/ID/query attributes
let targetUrl = null;
const anchor = firstMenuItem.querySelector('a') || (firstMenuItem.tagName === 'A' ? firstMenuItem : null);
if (anchor && anchor.getAttribute('href')) {
targetUrl = anchor.getAttribute('href');
} else {
// Codex frequently stores the target title in the element's text or label
const labelEl = firstMenuItem.querySelector('.cdx-search-result-title, .cdx-menu-item__text, .cdx-menu-item__content');
const title = (labelEl || firstMenuItem).textContent.trim();
if (title) {
targetUrl = mw.util.getUrl(title);
}
}
// 3. Prevent standard search submission and redirect immediately
if (targetUrl) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
window.location.href = targetUrl;
}
}, true); // 'true' captures the event before Vue or native form submit triggers
})();
(function () {
var box = document.getElementById('mc-status');
if (!box) return;
var host = box.dataset.host;
var q = function (s) { return box.querySelector(s); };
var logo = q('.mc-icon');
var customLogo = !!logo.firstElementChild;
var lastSrc = '';
function copy() { if (navigator.clipboard) navigator.clipboard.writeText(host); }
q('.mc-main').addEventListener('click', copy);
q('.mc-main').addEventListener('keydown', function (e) { if (e.key === 'Enter') copy(); });
function setIcon(src) {
if (customLogo || src === lastSrc) return;
lastSrc = src;
logo.textContent = '';
var img = document.createElement('img');
img.alt = '';
img.onerror = function () { logo.textContent = '⛏️'; };
img.src = src;
logo.appendChild(img);
}
function load() {
fetch('https://api.mcsrvstat.us/3/' + encodeURIComponent(host))
.then(function (r) { return r.json(); })
.then(function (d) {
box.classList.toggle('is-online', !!d.online);
box.classList.toggle('is-offline', !d.online);
q('.mc-state').textContent = d.online ? 'Online' : 'Offline';
q('.mc-players').textContent = d.players ? d.players.online + ' / ' + d.players.max : '– / –';
q('.mc-players').title = d.players && d.players.list
? d.players.list.map(function (p) { return p.name; }).join(', ') : '';
q('.mc-motd').textContent = d.online && d.motd ? d.motd.clean.join(' ').trim() : '';
setIcon(d.icon || 'https://api.mcsrvstat.us/icon/' + host);
})
.catch(function () { q('.mc-state').textContent = 'Status unavailable'; });
}
load();
setInterval(load, 60000);
})();
(function () {
var root = document.getElementById('pc-podcast');
if (!root) return;
var feed = root.dataset.feed, page = root.dataset.page;
var deck = root.querySelector('.pc-deck'), nav = root.querySelector('.pc-nav');
var cards = [], dots = [], active = 0;
function el(tag, cls, text) {
var e = document.createElement(tag);
if (cls) e.className = cls;
if (text) e.textContent = text;
return e;
}
function safeUrl(u) { return /^https?:\/\//i.test(u || '') ? u : ''; }
function txt(node, tag) { var n = node.getElementsByTagName(tag)[0]; return n ? n.textContent.trim() : ''; }
function plain(html) {
return new DOMParser().parseFromString(html || '', 'text/html').body.textContent.replace(/\s+/g, ' ').trim();
}
function parseDate(s) {
var d = new Date(s);
if (isNaN(d)) d = new Date(String(s).replace(' ', 'T') + 'Z');
return d;
}
function fmtDate(s) {
var d = parseDate(s);
return isNaN(d) ? '' : d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
}
function fmtDur(v) {
if (!v) return '';
if (v.indexOf(':') > -1) return v.replace(/^00:/, '').replace(/^0(\d:)/, '$1');
var s = parseInt(v, 10);
if (!s) return '';
var h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60), sec = s % 60;
return (h ? h + ':' + ('0' + m).slice(-2) : m) + ':' + ('0' + sec).slice(-2);
}
// direct RSS (has season / episode / duration)
function fromXml(xml) {
var doc = new DOMParser().parseFromString(xml, 'application/xml');
var chImg = doc.getElementsByTagName('itunes:image')[0];
var chCover = chImg ? chImg.getAttribute('href') : '';
return Array.prototype.map.call(doc.getElementsByTagName('item'), function (it) {
var img = it.getElementsByTagName('itunes:image')[0];
var s = txt(it, 'itunes:season'), e = txt(it, 'itunes:episode');
return {
title: txt(it, 'title'), link: txt(it, 'link'), date: txt(it, 'pubDate'),
dur: fmtDur(txt(it, 'itunes:duration')),
tag: e ? (s ? 'S' + s + ' · ' : '') + 'E' + e : '',
desc: plain(txt(it, 'description')),
cover: (img && img.getAttribute('href')) || chCover
};
});
}
// fallback through a public RSS-to-JSON proxy if the feed blocks cross-site requests
function fromJson(j) {
var chCover = j.feed && j.feed.image;
return (j.items || []).map(function (it) {
return { title: it.title, link: it.link, date: it.pubDate, dur: '', tag: '',
desc: plain(it.description), cover: it.thumbnail || chCover };
});
}
function buildCard(ep) {
var c = el('article', 'pc-card');
var cover = el('div', 'pc-cover');
if (safeUrl(ep.cover)) {
var img = el('img'); img.alt = ''; img.loading = 'lazy';
img.onerror = function () { img.remove(); cover.textContent = '🎙️'; };
img.src = ep.cover; cover.appendChild(img);
} else cover.textContent = '🎙️';
var body = el('div', 'pc-body');
var meta = el('div', 'pc-meta');
if (ep.tag) meta.appendChild(el('span', 'pc-pill pc-pill-strong', ep.tag));
var ds = fmtDate(ep.date); if (ds) meta.appendChild(el('span', 'pc-pill', ds));
if (ep.dur) meta.appendChild(el('span', 'pc-pill', ep.dur));
if (meta.children.length) body.appendChild(meta);
var href = safeUrl(ep.link) || safeUrl(page);
var title = el('div', 'pc-title'); title.setAttribute('role', 'heading'); title.setAttribute('aria-level', '3');
var a = el('a', '', ep.title || 'Untitled episode');
if (href) { a.href = href; a.target = '_blank'; a.rel = 'noopener'; }
title.appendChild(a); body.appendChild(title);
body.appendChild(el('p', 'pc-desc', ep.desc));
c.appendChild(cover); c.appendChild(body);
if (href) {
var o = el('div', 'mc-open'), oa = el('a');
oa.href = href; oa.target = '_blank'; oa.rel = 'noopener';
oa.title = 'Open episode'; oa.setAttribute('aria-label', 'Open episode');
oa.appendChild(el('span', 'mc-open-icon')); o.appendChild(oa); c.appendChild(o);
}
return c;
}
function layout() {
var n = cards.length;
cards.forEach(function (c, i) {
var pos = (i - active + n) % n;
c.dataset.pos = pos;
c.inert = pos !== 0;
c.setAttribute('aria-hidden', pos ? 'true' : 'false');
});
dots.forEach(function (d, i) { d.setAttribute('aria-current', i === active ? 'true' : 'false'); });
}
function go(delta) { active = (active + delta + cards.length) % cards.length; layout(); }
function arrow(label, path, delta) {
var b = el('button', 'pc-btn'); b.type = 'button'; b.setAttribute('aria-label', label);
b.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="' + path + '"/></svg>';
b.addEventListener('click', function () { go(delta); });
return b;
}
function render(list) {
if (!list || !list.length) return; // nothing found → stays an empty box
list = list.filter(function (e) { return e.title; })
.sort(function (a, b) { return parseDate(b.date) - parseDate(a.date); }).slice(0, 3);
if (!list.length) return;
list.forEach(function (ep) {
var card = buildCard(ep);
if (card) {
cards.push(card);
deck.appendChild(card);
}
});
for (var k = cards.length; k < 3; k++) {
var g = el('div', 'pc-card pc-ghost');
g.dataset.pos = k;
g.setAttribute('aria-hidden', 'true');
deck.appendChild(g);
}
root.classList.add('is-loaded');
layout();
}
if (cards.length > 1) {
var wrap = el('div', 'pc-dots');
cards.forEach(function (_, i) {
var d = el('button', 'pc-dot'); d.type = 'button'; d.setAttribute('aria-label', 'Episode ' + (i + 1));
d.addEventListener('click', function () { active = i; layout(); });
dots.push(d); wrap.appendChild(d);
});
nav.appendChild(arrow('Previous episode', 'm15 18-6-6 6-6', -1));
nav.appendChild(wrap);
nav.appendChild(arrow('Next episode', 'm9 18 6-6-6-6', 1));
var sx = null; // swipe support
deck.addEventListener('pointerdown', function (e) { sx = e.clientX; });
deck.addEventListener('pointerup', function (e) {
if (sx === null) return;
var dx = e.clientX - sx; sx = null;
if (Math.abs(dx) > 45) go(dx < 0 ? 1 : -1);
});
}
layout();
}
fetch(feed)
.then(function (r) { if (!r.ok) throw new Error('feed'); return r.text(); })
.then(fromXml)
.catch(function () {
return fetch('https://api.rss2json.com/v1/api.json?rss_url=' + encodeURIComponent(feed))
.then(function (r) { return r.json(); }).then(fromJson);
})
.then(render)
.catch(function () { /* leave the empty box */ });
})();