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 =
'
' + '
' +
'
' + escapeHtml(subpage) + '
' + (dek ? '' + dek + '
' : ) + '';
} else {
// Letter variant: title first, then kicker, then optional dek/meta.
hero.innerHTML =
'
' + escapeHtml(subpage) + '
' + '
' + kickerHtml + '
' + (dek ? '
' + dek + '
' : ) + (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, '"');
}
}());