User:Magent/Common.js without the player

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

/**

  • Blue Railroad Submission - Date to Block Height Converter
  • Adds a datepicker that converts dates to Ethereum block heights
  • Also shows time ago for news templates
  • /

(function() {

   'use strict';
   
   // Only run on the Blue Railroad Submission form
   if (!document.querySelector('input[name="Blue Railroad Submission[block_height]"]')) {
       return;
   }
   
   // Reference point: known block and timestamp
   // Post-merge average block time is ~12.12 seconds
   const AVG_BLOCK_TIME = 12.12;
   
   // Get current block from footer (format: "24,328,442")
   function getCurrentBlockFromFooter() {
       const footerLink = document.querySelector('a[href*="etherscan.io/block/"]');
       if (footerLink) {
           const match = footerLink.href.match(/block\/(\d+)/);
           if (match) return parseInt(match[1]);
       }
       return null;
   }
   
   // Calculate block height from date
   function dateToBlockHeight(targetDate, refBlock, refTimestamp) {
       const targetTimestamp = targetDate.getTime() / 1000;
       const secondsDiff = refTimestamp - targetTimestamp;
       const blocksDiff = Math.round(secondsDiff / AVG_BLOCK_TIME);
       return refBlock - blocksDiff;
   }
   
   // Add the datepicker UI
   function addDatePicker() {
       const blockInput = document.querySelector('input[name="Blue Railroad Submission[block_height]"]');
       if (!blockInput) return;
       
       const container = document.createElement('div');
       container.style.marginTop = '8px';
       container.innerHTML = `
           <label style="display: block; margin-bottom: 4px; font-size: 0.9em;">
               Or pick a date/time:
           </label>
           <input type="datetime-local" id="br-datepicker" style="padding: 4px; margin-right: 8px;">
           <button type="button" id="br-convert-btn" style="padding: 4px 12px; cursor: pointer;">
               Convert to Block
           </button>
           
       `;
       
       blockInput.parentNode.appendChild(container);
       
       const datePicker = document.getElementById('br-datepicker');
       const convertBtn = document.getElementById('br-convert-btn');
       const status = document.getElementById('br-status');
       
       // Set default to now
       const now = new Date();
       datePicker.value = now.toISOString().slice(0, 16);
       
       convertBtn.addEventListener('click', function() {
           const selectedDate = new Date(datePicker.value);
           if (isNaN(selectedDate.getTime())) {
               status.textContent = 'Invalid date';
               status.style.color = 'red';
               return;
           }
           
           const currentBlock = getCurrentBlockFromFooter();
           if (!currentBlock) {
               status.textContent = 'Could not find reference block';
               status.style.color = 'red';
               return;
           }
           
           const currentTimestamp = Date.now() / 1000;
           const estimatedBlock = dateToBlockHeight(selectedDate, currentBlock, currentTimestamp);
           
           if (estimatedBlock > currentBlock) {
               status.textContent = 'Date is in the future!';
               status.style.color = 'orange';
           } else {
               status.textContent = '~estimated';
               status.style.color = 'green';
           }
           
           blockInput.value = estimatedBlock;
       });
   }
   
   // Run when DOM is ready
   if (document.readyState === 'loading') {
       document.addEventListener('DOMContentLoaded', addDatePicker);
   } else {
       addDatePicker();
   }

})();

/**

* TimeAgo — shows "X blocks ago" for transcluded templates.
*
* Usage in wikitext:
*   
*
* Fetches the last revision timestamp from the API and renders
* a block-based relative time string using Ethereum block heights.
*/

(function() {

   'use strict';
   // Post-merge average block time is ~12.12 seconds
   var AVG_BLOCK_TIME = 12.12;
   var spans = document.querySelectorAll('.timeago[data-lastmod-page]');
   if (!spans.length) return;
   // Format number with commas (e.g., 1234567 -> "1,234,567")
   function formatNumber(num) {
       return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
   }
   // Collect unique page titles
   var titles = [];
   spans.forEach(function(span) {
       var t = span.getAttribute('data-lastmod-page');
       if (t && titles.indexOf(t) === -1) titles.push(t);
   });
   // Batch query the API (up to 50 titles per request)
   var api = mw.config.get('wgScriptPath') + '/api.php';
   var url = api + '?action=query&prop=revisions&rvprop=timestamp&format=json&titles=' +
       encodeURIComponent(titles.join('|'));
   fetch(url).then(function(r) { return r.json(); }).then(function(data) {
       var pages = data.query && data.query.pages || {};
       var timestamps = {};
       Object.keys(pages).forEach(function(id) {
           var page = pages[id];
           if (page.revisions && page.revisions[0]) {
               timestamps[page.title] = new Date(page.revisions[0].timestamp);
           }
       });
       spans.forEach(function(span) {
           var title = span.getAttribute('data-lastmod-page');
           var ts = timestamps[title];
           if (!ts) {
               span.textContent = ;
               return;
           }
           span.textContent = formatBlocksAgo(ts);
           span.title = ts.toLocaleString();
       });
   });
   function formatBlocksAgo(date) {
       var secondsAgo = Math.floor((Date.now() - date.getTime()) / 1000);
       var blocksAgo = Math.round(secondsAgo / AVG_BLOCK_TIME);
       if (blocksAgo < 1) {
           return 'this block';
       } else if (blocksAgo === 1) {
           return '1 block ago';
       } else {
           return formatNumber(blocksAgo) + ' blocks ago';
       }
   }

})();

/**

* The HLS video player used to live here.
*
* It now ships with the PickiPediaReleases extension, as
* resources/ext.pickipediaReleases.videoPlayer.js, loaded on every page by
* the BeforePageDisplay hook. Changing it is a pull request now: reviewed,
* syntax-checked, 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.
*
* See cryptograss/pickipedia#124 and Cryptograss:Video player decisions.
*/

/**

* Instrument form — validation + page name computation (v24772700)
*/

(function() {

   'use strict';
   console.log('PickiPedia Common.js instrument module v24772700 loaded');
   mw.hook('pf.formValidation').add(function(args) {
       var nickname = document.querySelector('input[name="Instrument[nickname]"]');
       if (!nickname) return;
       var make = document.querySelector('input[name="Instrument[make]"]');
       var model = document.querySelector('input[name="Instrument[model]"]');
       var serial = document.querySelector('input[name="Instrument[serial]"]');
       var nickVal = nickname.value.trim();
       var makeVal = make ? make.value.trim() : ;
       var modelVal = model ? model.value.trim() : ;
       var serialVal = serial ? serial.value.trim() : ;
       var hasNickname = nickVal !== ;
       var hasMakerComplete = makeVal !==  && modelVal !==  && serialVal !== ;
       if (!hasNickname && !hasMakerComplete) {
           args.numErrors += 1;
           if (makeVal !==  || modelVal !==  || serialVal !== ) {
               alert('Maker identification requires all three: make, model, and serial number. Or provide a nickname instead.');
           } else {
               alert('Please provide either a nickname or maker identification (make, model, and serial number).');
           }
           return;
       }
       // If no nickname, fill it from make/model/serial for page naming
       if (!hasNickname) {
           nickname.value = makeVal + ' ' + modelVal + ' ' + serialVal;
       }
   });

})();

/* ============================================================

* 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 =

'

' + '
' + kickerHtml + '
' + '
' + (lastMod ? '
' + escapeHtml(lastMod) + '
' : ) + '

' + '

' + '

' + escapeHtml(subpage) + '

' + (dek ? '

' + dek + '

' : ) + '

';

   } else {
     // Letter variant: title first, then kicker, then optional dek/meta.
     hero.innerHTML =

'

' + escapeHtml(subpage) + '

' + '

' + kickerHtml + '

' + (dek ? '

' + dek + '

' : ) + (lastMod ? '

Last edited ' + escapeHtml(lastMod) + '

' : );

   }
   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, '"');
   }

}());