MediaWiki:Common.js: Difference between revisions
From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search
Add HLS video player gadget for IPFS-hosted videos (via update-page on MediaWiki MCP Server) |
No edit summary |
||
| (27 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
/** | /** | ||
* | * What used to be here now ships with the PickiPediaReleases extension. | ||
* | * | ||
* | * the HLS video player -> ext.pickipediaReleases.videoPlayer.js (#124) | ||
* Blue Railroad date picker -> ext.pickipediaReleases.blueRailroadDate.js (#131) | |||
* "X blocks ago" -> ext.pickipediaReleases.blocksAgo.js (#131) | |||
* Instrument form checks -> ext.pickipediaReleases.instrumentForm.js (#131) | |||
* | |||
* All four load on every page from the extension's BeforePageDisplay hook, | |||
* exactly as they did from here, and each does nothing unless the form or | |||
* element it cares about is on the page being shown. Changing any of them is | |||
* a pull request now: reviewed, syntax-checked by CI, deployed by Jenkins — | |||
* rather than several hundred lines pasted into this box, which is how the | |||
* whole of this file went down for 23 minutes on 15 September 2026. | |||
* | |||
* | * | ||
* | * What remains below is the user-subpage zine styling. It stays because it is | ||
* | * bound to classes defined in MediaWiki:Common.css, and moving the script | ||
* without its stylesheet would split one feature across two homes. It goes | |||
* next, with the CSS, after which this page can be empty. | |||
* | * | ||
* | * See cryptograss/pickipedia#124 and #131. | ||
*/ | */ | ||
/* ============================================================ | |||
* Pickipedia — User-subpage zine/Substack styling | |||
* | |||
* Layout: | |||
* - corner logo (absolute-positioned upper-left of viewport, | |||
* OUTSIDE all wiki wrappers — does not push content down) | |||
* - non-sticky topbar (just nav) at top of body | |||
* - hero block with title (top), then USER:Username kicker below, | |||
* optional dek + meta | |||
* - light/dark mode toggle (persisted in localStorage) | |||
* | |||
* Variant from HTML comment marker `pickipedia:variant=letter|masthead` | |||
* or category membership; default letter. | |||
* ============================================================ */ | |||
(function () { | |||
'use strict'; | |||
if (typeof mw === 'undefined' || !mw.config) return; | |||
var nsNumber = mw.config.get('wgNamespaceNumber'); | |||
var pageName = mw.config.get('wgPageName') || ''; | |||
var title = mw.config.get('wgTitle') || ''; | |||
var action = mw.config.get('wgAction') || 'view'; | |||
if (nsNumber !== 2) return; | |||
if (action !== 'view') return; | |||
if (title.indexOf('/') === -1) return; | |||
var slash = title.indexOf('/'); | |||
var username = title.slice(0, slash).replace(/_/g, ' '); | |||
var subpage = title.slice(slash + 1).replace(/_/g, ' '); | |||
var body = document.body; | |||
body.classList.add('pickipedia-userpost'); | |||
var variant = 'letter'; | |||
var content = document.querySelector('.mw-parser-output'); | |||
if (content) { | |||
var html = content.innerHTML || ''; | |||
var m = html.match(/pickipedia:variant\s*=\s*(letter|masthead)/i); | |||
if (m) variant = m[1].toLowerCase(); | |||
} | |||
var cats = mw.config.get('wgCategories') || []; | |||
if (cats.indexOf('Masthead style') !== -1) variant = 'masthead'; | |||
if (cats.indexOf('Letter style') !== -1) variant = 'letter'; | |||
body.classList.add('pickipedia-variant-' + variant); | |||
var STORAGE_KEY = 'pickipedia-mode'; | |||
var saved = null; | |||
try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {} | |||
if (saved === 'dark') body.classList.add('pickipedia-dark'); | |||
var LOGO_URL = '/images/thumb/8/80/Pickipedia-quarter-transparent.png/400px-Pickipedia-quarter-transparent.png'; | |||
/ | // ---------- Corner logo: stand-alone body child, absolute-positioned. ---------- | ||
var cornerLogo = document.createElement('a'); | |||
cornerLogo.className = 'pp-corner-logo'; | |||
cornerLogo.href = '/wiki/Main_Page'; | |||
cornerLogo.title = 'PickiPedia home'; | |||
cornerLogo.innerHTML = '<img src="' + LOGO_URL + '" alt="PickiPedia">'; | |||
document.body.insertBefore(cornerLogo, document.body.firstChild); | |||
// ---------- Topbar (nav only) ---------- | |||
var topbar = document.createElement('header'); | |||
topbar.className = 'pickipedia-topbar'; | |||
topbar.innerHTML = | |||
'<nav class="pickipedia-topbar-nav">' + | |||
'<a href="' + mw.util.getUrl(pageName, { action: 'edit' }) + '">edit</a>' + | |||
'<a href="' + mw.util.getUrl(pageName, { action: 'history' }) + '">history</a>' + | |||
'<a href="' + mw.util.getUrl('Talk:' + pageName) + '">talk</a>' + | |||
'</nav>'; | |||
document.body.insertBefore(topbar, cornerLogo.nextSibling); | |||
// ---------- Hero ---------- | |||
if (content) { | |||
var dek = ''; | |||
var dekEl = content.querySelector('.pp-dek-source'); | |||
if (dekEl) { | |||
dek = dekEl.innerHTML; | |||
dekEl.remove(); | |||
} | |||
var kickerHtml = '<a href="' + mw.util.getUrl('User:' + username.replace(/ /g, '_')) + | |||
'">User:' + escapeHtml(username) + '</a>'; | |||
var kickerEl = content.querySelector('.pp-kicker-source'); | |||
if (kickerEl) { | |||
kickerHtml = escapeHtml(kickerEl.textContent.trim()); | |||
kickerEl.remove(); | |||
} | } | ||
var lastMod = ''; | |||
var lastModEl = document.getElementById('footer-info-lastmod'); | |||
if (lastModEl) lastMod = lastModEl.textContent.replace(/^\s*This page was last (edited|modified) on\s*/i, '').trim(); | |||
var hero = document.createElement('header'); | |||
hero.className = 'pickipedia-hero'; | |||
if (variant === 'masthead') { | |||
hero.innerHTML = | |||
// | '<div class="pp-topbar">' + | ||
'<div class="pp-kicker">' + kickerHtml + '</div>' + | |||
'<div class="pp-topbar-rule"></div>' + | |||
(lastMod ? '<div class="pp-topbar-vol">' + escapeHtml(lastMod) + '</div>' : '') + | |||
'</div>' + | |||
'<div class="pp-titlewrap">' + | |||
'<h1 class="pp-title">' + escapeHtml(subpage) + '</h1>' + | |||
(dek ? '<p class="pp-dek">' + dek + '</p>' : '') + | |||
'</div>'; | |||
} else { | |||
// Letter variant: title first, then kicker, then optional dek/meta. | |||
hero.innerHTML = | |||
'<h1 class="pp-title">' + escapeHtml(subpage) + '</h1>' + | |||
'<div class="pp-kicker">' + kickerHtml + '</div>' + | |||
(dek ? '<p class="pp-dek">' + dek + '</p>' : '') + | |||
(lastMod ? '<div class="pp-meta">Last edited ' + escapeHtml(lastMod) + '</div>' : ''); | |||
} | } | ||
content.parentNode.insertBefore(hero, content); | |||
} | |||
// ---------- Light/dark toggle ---------- | |||
var toggle = document.createElement('button'); | |||
toggle.className = 'pickipedia-mode'; | |||
} | toggle.type = 'button'; | ||
toggle.textContent = body.classList.contains('pickipedia-dark') ? '☼ Light' : '☾ Dark'; | |||
toggle.addEventListener('click', function () { | |||
var dark = body.classList.toggle('pickipedia-dark'); | |||
toggle.textContent = dark ? '☼ Light' : '☾ Dark'; | |||
try { localStorage.setItem(STORAGE_KEY, dark ? 'dark' : 'light'); } catch (e) {} | |||
}); | |||
document.body.appendChild(toggle); | |||
function escapeHtml(s) { | |||
return String(s == null ? '' : s) | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"'); | |||
} | } | ||
}) | }()); | ||
Latest revision as of 18:55, 9 October 2026
/**
* What used to be here now ships with the PickiPediaReleases extension.
*
* the HLS video player -> ext.pickipediaReleases.videoPlayer.js (#124)
* Blue Railroad date picker -> ext.pickipediaReleases.blueRailroadDate.js (#131)
* "X blocks ago" -> ext.pickipediaReleases.blocksAgo.js (#131)
* Instrument form checks -> ext.pickipediaReleases.instrumentForm.js (#131)
*
* All four load on every page from the extension's BeforePageDisplay hook,
* exactly as they did from here, and each does nothing unless the form or
* element it cares about is on the page being shown. Changing any of them is
* a pull request now: reviewed, syntax-checked by CI, deployed by Jenkins —
* rather than several hundred lines pasted into this box, which is how the
* whole of this file went down for 23 minutes on 15 September 2026.
*
* What remains below is the user-subpage zine styling. It stays because it is
* bound to classes defined in MediaWiki:Common.css, and moving the script
* without its stylesheet would split one feature across two homes. It goes
* next, with the CSS, after which this page can be empty.
*
* See cryptograss/pickipedia#124 and #131.
*/
/* ============================================================
* Pickipedia — User-subpage zine/Substack styling
*
* Layout:
* - corner logo (absolute-positioned upper-left of viewport,
* OUTSIDE all wiki wrappers — does not push content down)
* - non-sticky topbar (just nav) at top of body
* - hero block with title (top), then USER:Username kicker below,
* optional dek + meta
* - light/dark mode toggle (persisted in localStorage)
*
* Variant from HTML comment marker `pickipedia:variant=letter|masthead`
* or category membership; default letter.
* ============================================================ */
(function () {
'use strict';
if (typeof mw === 'undefined' || !mw.config) return;
var nsNumber = mw.config.get('wgNamespaceNumber');
var pageName = mw.config.get('wgPageName') || '';
var title = mw.config.get('wgTitle') || '';
var action = mw.config.get('wgAction') || 'view';
if (nsNumber !== 2) return;
if (action !== 'view') return;
if (title.indexOf('/') === -1) return;
var slash = title.indexOf('/');
var username = title.slice(0, slash).replace(/_/g, ' ');
var subpage = title.slice(slash + 1).replace(/_/g, ' ');
var body = document.body;
body.classList.add('pickipedia-userpost');
var variant = 'letter';
var content = document.querySelector('.mw-parser-output');
if (content) {
var html = content.innerHTML || '';
var m = html.match(/pickipedia:variant\s*=\s*(letter|masthead)/i);
if (m) variant = m[1].toLowerCase();
}
var cats = mw.config.get('wgCategories') || [];
if (cats.indexOf('Masthead style') !== -1) variant = 'masthead';
if (cats.indexOf('Letter style') !== -1) variant = 'letter';
body.classList.add('pickipedia-variant-' + variant);
var STORAGE_KEY = 'pickipedia-mode';
var saved = null;
try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) {}
if (saved === 'dark') body.classList.add('pickipedia-dark');
var LOGO_URL = '/images/thumb/8/80/Pickipedia-quarter-transparent.png/400px-Pickipedia-quarter-transparent.png';
// ---------- Corner logo: stand-alone body child, absolute-positioned. ----------
var cornerLogo = document.createElement('a');
cornerLogo.className = 'pp-corner-logo';
cornerLogo.href = '/wiki/Main_Page';
cornerLogo.title = 'PickiPedia home';
cornerLogo.innerHTML = '<img src="' + LOGO_URL + '" alt="PickiPedia">';
document.body.insertBefore(cornerLogo, document.body.firstChild);
// ---------- Topbar (nav only) ----------
var topbar = document.createElement('header');
topbar.className = 'pickipedia-topbar';
topbar.innerHTML =
'<nav class="pickipedia-topbar-nav">' +
'<a href="' + mw.util.getUrl(pageName, { action: 'edit' }) + '">edit</a>' +
'<a href="' + mw.util.getUrl(pageName, { action: 'history' }) + '">history</a>' +
'<a href="' + mw.util.getUrl('Talk:' + pageName) + '">talk</a>' +
'</nav>';
document.body.insertBefore(topbar, cornerLogo.nextSibling);
// ---------- Hero ----------
if (content) {
var dek = '';
var dekEl = content.querySelector('.pp-dek-source');
if (dekEl) {
dek = dekEl.innerHTML;
dekEl.remove();
}
var kickerHtml = '<a href="' + mw.util.getUrl('User:' + username.replace(/ /g, '_')) +
'">User:' + escapeHtml(username) + '</a>';
var kickerEl = content.querySelector('.pp-kicker-source');
if (kickerEl) {
kickerHtml = escapeHtml(kickerEl.textContent.trim());
kickerEl.remove();
}
var lastMod = '';
var lastModEl = document.getElementById('footer-info-lastmod');
if (lastModEl) lastMod = lastModEl.textContent.replace(/^\s*This page was last (edited|modified) on\s*/i, '').trim();
var hero = document.createElement('header');
hero.className = 'pickipedia-hero';
if (variant === 'masthead') {
hero.innerHTML =
'<div class="pp-topbar">' +
'<div class="pp-kicker">' + kickerHtml + '</div>' +
'<div class="pp-topbar-rule"></div>' +
(lastMod ? '<div class="pp-topbar-vol">' + escapeHtml(lastMod) + '</div>' : '') +
'</div>' +
'<div class="pp-titlewrap">' +
'<h1 class="pp-title">' + escapeHtml(subpage) + '</h1>' +
(dek ? '<p class="pp-dek">' + dek + '</p>' : '') +
'</div>';
} else {
// Letter variant: title first, then kicker, then optional dek/meta.
hero.innerHTML =
'<h1 class="pp-title">' + escapeHtml(subpage) + '</h1>' +
'<div class="pp-kicker">' + kickerHtml + '</div>' +
(dek ? '<p class="pp-dek">' + dek + '</p>' : '') +
(lastMod ? '<div class="pp-meta">Last edited ' + escapeHtml(lastMod) + '</div>' : '');
}
content.parentNode.insertBefore(hero, content);
}
// ---------- Light/dark toggle ----------
var toggle = document.createElement('button');
toggle.className = 'pickipedia-mode';
toggle.type = 'button';
toggle.textContent = body.classList.contains('pickipedia-dark') ? '☼ Light' : '☾ Dark';
toggle.addEventListener('click', function () {
var dark = body.classList.toggle('pickipedia-dark');
toggle.textContent = dark ? '☼ Light' : '☾ Dark';
try { localStorage.setItem(STORAGE_KEY, dark ? 'dark' : 'light'); } catch (e) {}
});
document.body.appendChild(toggle);
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
}());