MediaWiki:Gadget-pickipedia-show.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
 
No edit summary
 
(5 intermediate revisions by the same user not shown)
Line 15: Line 15:
   var nsNumber = mw.config.get('wgNamespaceNumber');
   var nsNumber = mw.config.get('wgNamespaceNumber');
   var title    = mw.config.get('wgTitle') || '';
   var title    = mw.config.get('wgTitle') || '';
  var pageName = mw.config.get('wgPageName') || '';
   var action  = mw.config.get('wgAction') || 'view';
   var action  = mw.config.get('wgAction') || 'view';


Line 48: Line 49:
   }
   }


   var artists = fieldValue('Artists');
   var artists     = fieldValue('Artists');
   var venue   = fieldValue('Venue');
   var venue       = fieldValue('Venue');
   var showtime = fieldValue('Showtime');
  var date        = fieldValue('Date');
   var tickets = fieldValue('Tickets');
   var showtime     = fieldValue('Showtime');
   var price   = fieldValue('Price');
  var doors        = fieldValue('Doors');
   var ages     = fieldValue('Ages');
  var show        = fieldValue('Show');
  var posterArtist = fieldValue('Poster art');
   var tickets     = fieldValue('Tickets');
   var price       = fieldValue('Price');
   var ages         = fieldValue('Ages');


   var imageImg = infobox.querySelector('img');
   var imageImg = infobox.querySelector('img');
Line 81: Line 86:
     return 'Block ' + formatted;
     return 'Block ' + formatted;
   }
   }
  // Big date kicker (human-readable) on its own line, above the smaller
  // technical kicker with block/doors/show.
  var dateKickerHtml = date
    ? '<div class="pp-date-kicker">' + escapeHtml(date) + '</div>'
    : '';


   var kickerBits = [];
   var kickerBits = [];
   var showtimeHtml = formatShowtime(showtime);
   var showtimeHtml = formatShowtime(showtime);
   if (showtimeHtml) kickerBits.push(showtimeHtml);
   if (showtimeHtml) kickerBits.push(showtimeHtml);
  if (doors) kickerBits.push('Doors ' + doors);
  if (show)  kickerBits.push('Show ' + show);


   var kickerHtml = kickerBits.length
   var kickerHtml = kickerBits.length
Line 91: Line 104:


   var venueHtml = venue ? '<p class="pp-venue">at ' + venue + '</p>' : '';
   var venueHtml = venue ? '<p class="pp-venue">at ' + venue + '</p>' : '';
  // Ensembles ({{Ensemble|...}} blocks) are intentionally NOT absorbed into
  // the hero — they render in their source position on the page so the
  // wikitext author controls placement. The CSS in this gadget restyles
  // the existing .ensemble-infobox to fit the takeover aesthetic (right-
  // floating, paper-themed, mono-caps title, instrument icons inline).
  var ensembleHtml = '';


   var metaBits = [];
   var metaBits = [];
Line 103: Line 123:
     : '';
     : '';


  // Upgrade the poster thumbnail: Template:Show renders [[File:foo|220px]]
  // which gives us /images/thumb/.../220px-foo.png inside an <a> linking
  // to the File: page. We grab that anchor's href so clicking the big
  // hero poster takes the user to the File: page (where the upload
  // history, full-res download, and license info live). The thumbnail
  // URL gets upsized from /220px-/ to /800px- for a sharper render; CSS
  // scales it to fit the card. SVGs and other non-thumbnailed files
  // keep their original src.
   var imageHtml = '';
   var imageHtml = '';
   if (imageImg) {
   if (imageImg) {
     imageHtml = '<div class="pp-image"><img src="' + imageImg.src +
     var bigSrc = imageImg.src.replace(/\/(\d+)px-/, '/800px-');
       '" alt="' + escapeHtml(imageImg.alt || '') + '"></div>';
    var imageAnchor = imageImg.closest('a');
    var imageHref = imageAnchor ? imageAnchor.getAttribute('href') : '';
    var imgTag = '<img src="' + bigSrc + '" alt="' +
      escapeHtml(imageImg.alt || '') + '">';
    var wrapped = imageHref
      ? '<a class="pp-image-link" href="' + imageHref +
        '" title="View file details">' + imgTag + '</a>'
      : imgTag;
    imageHtml = '<div class="pp-image">' + wrapped +
       (posterArtist
        ? '<p class="pp-poster-credit"><span class="pp-poster-credit-label">Poster art by</span> ' +
            escapeHtml(posterArtist) + '</p>'
        : '') +
      '</div>';
   }
   }


Line 133: Line 174:
   hero.innerHTML =
   hero.innerHTML =
     statusHtml +
     statusHtml +
    dateKickerHtml +
     kickerHtml +
     kickerHtml +
     '<h1 class="pp-title">' + titleHtml + '</h1>' +
     '<h1 class="pp-title">' + titleHtml + '</h1>' +
Line 143: Line 185:
   infobox.classList.add('is-absorbed');
   infobox.classList.add('is-absorbed');
   body.classList.add('has-show-hero');
   body.classList.add('has-show-hero');
  // ---------- Full-takeover chrome: corner logo + slim topbar ----------
  // Only fires when the hero was built (Template:Show present). The CSS
  // chrome-hiding rules are gated by .has-show-hero so bare Show: pages
  // keep their wiki sidebar; if you want to make those takeover too,
  // remove the .has-show-hero gating from the CSS file.
  var LOGO_URL = '/images/thumb/8/80/Pickipedia-quarter-transparent.png/400px-Pickipedia-quarter-transparent.png';
  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">';
  body.insertBefore(cornerLogo, body.firstChild);
  var topbar = document.createElement('header');
  topbar.className = 'pickipedia-show-topbar';
  topbar.innerHTML =
    '<nav class="pickipedia-show-topbar-left">' +
      '<a href="' + mw.util.getUrl('Category:Shows') + '">← All shows</a>' +
    '</nav>' +
    '<nav class="pickipedia-show-topbar-right">' +
      '<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>';
  body.insertBefore(topbar, cornerLogo.nextSibling);


   function escapeHtml(s) {
   function escapeHtml(s) {

Latest revision as of 21:02, 2 June 2026

/**
 * Pickipedia — Show: page hero.
 *
 * For mainspace pages whose title starts with "Show:" (the legacy
 * mainspace convention used pre-namespace), adds .pickipedia-show
 * to body and absorbs Template:Show's infobox into a poster-style
 * hero. If the page does not use Template:Show only the body class
 * is added — the setlist typography in Common.css still applies.
 */
(function () {
  'use strict';

  if (typeof mw === 'undefined' || !mw.config) return;

  var nsNumber = mw.config.get('wgNamespaceNumber');
  var title    = mw.config.get('wgTitle') || '';
  var pageName = mw.config.get('wgPageName') || '';
  var action   = mw.config.get('wgAction') || 'view';

  if (nsNumber !== 0) return;
  if (action !== 'view') return;
  if (title.indexOf('Show:') !== 0) return;

  var body = document.body;
  body.classList.add('pickipedia-show');

  var content = document.querySelector('.mw-parser-output');
  if (!content) return;

  var infobox = content.querySelector('.show-infobox');
  if (!infobox) return;  // Bare show page — body class only.

  // ---------- Pull fields out of the existing infobox ----------
  // Template:Show emits rows of <div><strong>Label:</strong> value</div>
  // plus a top banner with "Artists at Venue" and optional image.
  function fieldValue(label) {
    var rows = infobox.querySelectorAll('div');
    for (var i = 0; i < rows.length; i++) {
      var s = rows[i].querySelector('strong');
      if (!s) continue;
      var labelText = s.textContent.replace(/[:\s]*$/, '').trim();
      if (labelText !== label) continue;
      var clone = rows[i].cloneNode(true);
      var strong = clone.querySelector('strong');
      if (strong) strong.remove();
      return clone.innerHTML.trim().replace(/^[\s:]+/, '');
    }
    return '';
  }

  var artists      = fieldValue('Artists');
  var venue        = fieldValue('Venue');
  var date         = fieldValue('Date');
  var showtime     = fieldValue('Showtime');
  var doors        = fieldValue('Doors');
  var show         = fieldValue('Show');
  var posterArtist = fieldValue('Poster art');
  var tickets      = fieldValue('Tickets');
  var price        = fieldValue('Price');
  var ages         = fieldValue('Ages');

  var imageImg = infobox.querySelector('img');

  var status = '';
  if (infobox.classList.contains('show-verified')) status = 'verified';
  else if (infobox.classList.contains('bot-proposal')) status = 'proposed';
  else if (infobox.classList.contains('show-unverified')) status = 'unverified';

  // ---------- Build the poster hero ----------
  // Showtime is rendered by Template:Show as
  // [https://etherscan.io/block/N N] — pull the block number out and
  // format it with commas so the kicker reads "BLOCK 24,144,194".
  function formatShowtime(htmlIn) {
    if (!htmlIn) return '';
    var tmp = document.createElement('div');
    tmp.innerHTML = htmlIn;
    var anchor = tmp.querySelector('a');
    var blockNum = (anchor ? anchor.textContent : tmp.textContent).trim();
    var asInt = parseInt(blockNum.replace(/[^\d]/g, ''), 10);
    if (isNaN(asInt)) return blockNum;
    var formatted = asInt.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    var href = anchor ? anchor.href : '';
    if (href) {
      return '<a href="' + href + '" target="_blank" rel="noopener">Block ' + formatted + '</a>';
    }
    return 'Block ' + formatted;
  }

  // Big date kicker (human-readable) on its own line, above the smaller
  // technical kicker with block/doors/show.
  var dateKickerHtml = date
    ? '<div class="pp-date-kicker">' + escapeHtml(date) + '</div>'
    : '';

  var kickerBits = [];
  var showtimeHtml = formatShowtime(showtime);
  if (showtimeHtml) kickerBits.push(showtimeHtml);
  if (doors) kickerBits.push('Doors ' + doors);
  if (show)  kickerBits.push('Show ' + show);

  var kickerHtml = kickerBits.length
    ? '<div class="pp-kicker">' + kickerBits.join('<span class="pp-sep">·</span>') + '</div>'
    : '';

  var venueHtml = venue ? '<p class="pp-venue">at ' + venue + '</p>' : '';

  // Ensembles ({{Ensemble|...}} blocks) are intentionally NOT absorbed into
  // the hero — they render in their source position on the page so the
  // wikitext author controls placement. The CSS in this gadget restyles
  // the existing .ensemble-infobox to fit the takeover aesthetic (right-
  // floating, paper-themed, mono-caps title, instrument icons inline).
  var ensembleHtml = '';

  var metaBits = [];
  if (price)
    metaBits.push('<span><span class="pp-meta-label">Price</span><span class="pp-meta-value">$' +
      escapeHtml(price.replace(/^\$/, '')) + '</span></span>');
  if (ages)
    metaBits.push('<span><span class="pp-meta-label">Ages</span><span class="pp-meta-value">' +
      ages + '</span></span>');
  var metaHtml = metaBits.length
    ? '<div class="pp-meta">' + metaBits.join('') + '</div>'
    : '';

  // Upgrade the poster thumbnail: Template:Show renders [[File:foo|220px]]
  // which gives us /images/thumb/.../220px-foo.png inside an <a> linking
  // to the File: page. We grab that anchor's href so clicking the big
  // hero poster takes the user to the File: page (where the upload
  // history, full-res download, and license info live). The thumbnail
  // URL gets upsized from /220px-/ to /800px- for a sharper render; CSS
  // scales it to fit the card. SVGs and other non-thumbnailed files
  // keep their original src.
  var imageHtml = '';
  if (imageImg) {
    var bigSrc = imageImg.src.replace(/\/(\d+)px-/, '/800px-');
    var imageAnchor = imageImg.closest('a');
    var imageHref = imageAnchor ? imageAnchor.getAttribute('href') : '';
    var imgTag = '<img src="' + bigSrc + '" alt="' +
      escapeHtml(imageImg.alt || '') + '">';
    var wrapped = imageHref
      ? '<a class="pp-image-link" href="' + imageHref +
        '" title="View file details">' + imgTag + '</a>'
      : imgTag;
    imageHtml = '<div class="pp-image">' + wrapped +
      (posterArtist
        ? '<p class="pp-poster-credit"><span class="pp-poster-credit-label">Poster art by</span> ' +
            escapeHtml(posterArtist) + '</p>'
        : '') +
      '</div>';
  }

  // Tickets field is an anchor; surface its href as a poster action.
  var actionBits = [];
  if (tickets) {
    var tmp = document.createElement('div'); tmp.innerHTML = tickets;
    var ticketAnchor = tmp.querySelector('a');
    if (ticketAnchor && ticketAnchor.href) {
      actionBits.push('<a href="' + ticketAnchor.href +
        '" target="_blank" rel="noopener">Tickets</a>');
    }
  }
  var actionsHtml = actionBits.length
    ? '<div class="pp-actions">' + actionBits.join('') + '</div>'
    : '';

  var statusHtml = status
    ? '<div class="pp-status is-' + status + '">' + status + '</div>'
    : '';

  var titleHtml = artists || escapeHtml(title.replace(/^Show:\s*/, ''));

  var hero = document.createElement('header');
  hero.className = 'show-poster';
  hero.innerHTML =
    statusHtml +
    dateKickerHtml +
    kickerHtml +
    '<h1 class="pp-title">' + titleHtml + '</h1>' +
    venueHtml +
    metaHtml +
    imageHtml +
    actionsHtml;

  content.insertBefore(hero, content.firstChild);
  infobox.classList.add('is-absorbed');
  body.classList.add('has-show-hero');

  // ---------- Full-takeover chrome: corner logo + slim topbar ----------
  // Only fires when the hero was built (Template:Show present). The CSS
  // chrome-hiding rules are gated by .has-show-hero so bare Show: pages
  // keep their wiki sidebar; if you want to make those takeover too,
  // remove the .has-show-hero gating from the CSS file.
  var LOGO_URL = '/images/thumb/8/80/Pickipedia-quarter-transparent.png/400px-Pickipedia-quarter-transparent.png';

  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">';
  body.insertBefore(cornerLogo, body.firstChild);

  var topbar = document.createElement('header');
  topbar.className = 'pickipedia-show-topbar';
  topbar.innerHTML =
    '<nav class="pickipedia-show-topbar-left">' +
      '<a href="' + mw.util.getUrl('Category:Shows') + '">← All shows</a>' +
    '</nav>' +
    '<nav class="pickipedia-show-topbar-right">' +
      '<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>';
  body.insertBefore(topbar, cornerLogo.nextSibling);

  function escapeHtml(s) {
    return String(s == null ? '' : s)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }
}());