MediaWiki:Common.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 235: | Line 235: | ||
.catch(function () { /* leave empty */ }); | .catch(function () { /* leave empty */ }); | ||
})(); | })(); | ||
mw.hook('wikipage.content').add(function ($content) { | |||
$content.find('.infobox-native-audio').each(function () { | |||
const container = this; | |||
if (container.dataset.audioInitialized) { | |||
return; | |||
} | |||
const audio = document.createElement('audio'); | |||
audio.controls = true; | |||
audio.preload = 'metadata'; | |||
audio.src = container.dataset.src; | |||
container.replaceChildren(audio); | |||
container.dataset.audioInitialized = 'true'; | |||
}); | |||
}); | |||
Revision as of 12:24, 4 October 2026
(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);
}
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
};
});
}
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 () { 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');
var 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;
if (!n) return;
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;
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;
deck.innerHTML = '';
nav.innerHTML = '';
cards = [];
dots = [];
active = 0;
list.forEach(function (ep) {
var card = buildCard(ep);
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');
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;
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 empty */ });
})();
mw.hook('wikipage.content').add(function ($content) {
$content.find('.infobox-native-audio').each(function () {
const container = this;
if (container.dataset.audioInitialized) {
return;
}
const audio = document.createElement('audio');
audio.controls = true;
audio.preload = 'metadata';
audio.src = container.dataset.src;
container.replaceChildren(audio);
container.dataset.audioInitialized = 'true';
});
});