MediaWiki:Common.js

From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search

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.
/**
 * 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, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
    }
}());