MediaWiki:Common.js
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.
/* Load CRUST brand fonts — ResourceLoader strips @import in Common.css, so inject the link here */
(function(){
var l=document.createElement('link'); l.rel='stylesheet';
l.href='https://fonts.googleapis.com/css2?family=Anton&family=Libre+Franklin:wght@400;500;600;700;800;900&family=Spline+Sans+Mono:wght@400;500;600&display=swap';
document.head.appendChild(l);
})();
(function () {
'use strict';
var KEY = 'icelist_anon_donate_dismiss_until';
var DAYS = 14;
if ((mw.config.get('wgUserName')!==null)) return;
var until = parseInt(localStorage.getItem(KEY) || '0', 10);
if (Date.now() < until) return;
function mount() {
var siteNotice = document.getElementById('siteNotice');
if (!siteNotice) return;
// If empty, do nothing
var txt = (siteNotice.textContent || '').replace(/\s+/g, ' ').trim();
if (!txt) return;
siteNotice.classList.add('icelist-anon-donate');
// Add close button once
if (!siteNotice.querySelector('.icelist-notice-close')) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'icelist-notice-close';
btn.setAttribute('aria-label', 'Close');
btn.textContent = '×';
siteNotice.insertBefore(btn, siteNotice.firstChild);
btn.addEventListener('click', function (e) {
e.preventDefault();
siteNotice.style.display = 'none';
localStorage.setItem(KEY, String(Date.now() + DAYS * 24 * 60 * 60 * 1000));
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', mount);
} else {
mount();
}
})();
mw.loader.using(['jquery']).then(function () {
var s = document.createElement('script');
s.src = 'https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js';
s.setAttribute('data-name', 'BMC-Widget');
s.setAttribute('data-cfasync', 'false');
s.setAttribute('data-id', 'crustnews');
s.setAttribute('data-description', 'Support me on Buy me a coffee!');
s.setAttribute('data-message', '');
s.setAttribute('data-color', '#FF5F5F');
s.setAttribute('data-position', 'Right');
s.setAttribute('data-x_margin', '18');
s.setAttribute('data-y_margin', '18');
document.body.appendChild(s);
});
// Tag agent pages (any page showing an agent card) so the duplicate title/heading can be hidden
if ( document.querySelector( '.ic-agent-card' ) ) {
document.body.classList.add( 'ic-agent-page' );
}
/* ---- Mark anonymous users so anon-only banners can show (fail-safe: hidden by default) ---- */
if ((mw.config.get('wgUserName')===null)) { document.documentElement.classList.add('vol-anon'); }
/* ---- Incident candidates page: logged-in-only + per-row dismiss ---- */
if (/Incident_candidates$/.test(mw.config.get('wgPageName'))) {
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) return;
if ((mw.config.get('wgUserName')===null)) {
content.innerHTML = '<div class="vol-login">This page is for logged-in ICE List volunteers. ' +
'<a href="' + mw.util.getUrl('Special:UserLogin', {returnto: mw.config.get('wgPageName')}) +
'">Log in</a> to review incident candidates.</div>';
return;
}
var api = new mw.Api();
content.querySelectorAll('li').forEach(function (li) {
var create = Array.prototype.find.call(li.querySelectorAll('a'), function (a) {
return /FormEdit\/Incident/.test(a.getAttribute('href') || '');
});
if (!create) return;
var art = Array.prototype.find.call(li.querySelectorAll('a[href^="http"]'), function (a) {
return a !== create && !/FormEdit\/Incident/.test(a.getAttribute('href') || '');
});
var url = art ? art.href : null;
var btn = document.createElement('button');
btn.textContent = '✕ dismiss';
btn.className = 'vol-dismiss';
btn.onclick = function () {
if (!url) { li.remove(); return; }
btn.disabled = true; btn.textContent = '…';
api.get({action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', formatversion: 2})
.then(function (r) {
var lines = r.parse.wikitext.split('\n'), out = [], removed = false;
for (var i = 0; i < lines.length; i++) {
if (!removed && lines[i].indexOf(url) !== -1) {
if (out.length && /^<!-- cand:/.test(out[out.length - 1])) out.pop();
removed = true; continue;
}
out.push(lines[i]);
}
return api.postWithToken('csrf', {action: 'edit', title: mw.config.get('wgPageName'),
text: out.join('\n'), summary: 'Dismiss incident candidate'});
})
.then(function () { li.remove(); })
.catch(function () { btn.disabled = false; btn.textContent = '✕ dismiss'; });
};
li.appendChild(document.createTextNode(' '));
li.appendChild(btn);
});
});
}
/* ============ ICE List client tools (vanilla; robust login detection) ============ */
(function () {
function ready(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }
/* Detect logged-in state robustly: mw.user first, then the DOM logout link
(wgUserName can be null in cached RLCONF, so trust the personal-tools chrome). */
function markMember() {
var el = document.documentElement;
if (el.classList.contains('vol-member')) { return true; }
var inn = false;
try { if (window.mw && mw.user && (mw.config.get('wgUserName')!==null)) { inn = true; } } catch (e) {}
if (!inn && (document.getElementById('pt-logout') ||
document.querySelector('a[href*="Special:UserLogout"], a[href*="UserLogout"], #pt-userpage'))) { inn = true; }
if (inn) { el.classList.add('vol-member'); }
return inn;
}
markMember();
ready(markMember);
/* Homepage: strip native title tooltips on interactive tiles */
ready(function () {
if (mw.config.get('wgPageName') === 'Main_Page') {
document.querySelectorAll('.ic-scenarios a, .ic-stat a, .ic-hero__actions a').forEach(function (a) { a.removeAttribute('title'); });
}
});
/* Render video / social embeds (all users, client-side for privacy) */
ready(function () {
var nodes = document.querySelectorAll('.ic-embed:not([data-done])');
if (!nodes.length) { return; }
var needX = false, needIG = false, needTh = false;
nodes.forEach(function (el) {
el.setAttribute('data-done', '1');
var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
if (el.classList.contains('ic-embed-youtube') && id) {
el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
} else if (el.classList.contains('ic-embed-vimeo') && id) {
el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
} else if (el.classList.contains('ic-embed-x') && url) {
el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
} else if (el.classList.contains('ic-embed-instagram') && url) {
el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
} else if (el.classList.contains('ic-embed-threads') && url) {
el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
}
});
function load(src) { var sc = document.createElement('script'); sc.async = true; sc.src = src; document.body.appendChild(sc); }
if (needX) { load('https://platform.twitter.com/widgets.js'); }
if (needIG) { load('https://www.instagram.com/embed.js'); }
if (needTh) { load('https://www.threads.net/embed.js'); }
});
/* Volunteer incident marking tool */
ready(function () {
if (!markMember()) { return; }
if (mw.config.get('wgAction') !== 'view') { return; }
var cats = mw.config.get('wgCategories') || [];
if (cats.indexOf('Incidents') === -1) { return; }
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) { return; }
var MARKS = ['Agents recorded', 'Plates recorded', 'Faces captured', 'Video secured', 'Location confirmed', 'Witnesses identified', 'Needs review', 'Reviewed'];
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api();
var page = mw.config.get('wgPageName');
var panel = document.createElement('div');
panel.className = 'vol-panel';
panel.innerHTML = '<h4>Volunteer triage <span class="vol-status">loading…</span></h4>';
var status = panel.querySelector('.vol-status');
var boxes = {};
MARKS.forEach(function (m) {
var lab = document.createElement('label');
var cb = document.createElement('input'); cb.type = 'checkbox'; cb.disabled = true;
lab.appendChild(cb); lab.appendChild(document.createTextNode(' ' + m));
panel.appendChild(lab); boxes[m] = cb;
});
content.insertBefore(panel, content.firstChild);
var wikitext = '';
function parseMarks(wt) {
var re = /\{\{\s*Mark\s*\|\s*([^}|]+?)\s*\}\}/g, mm, set = {};
while ((mm = re.exec(wt))) { set[mm[1].trim()] = true; }
return set;
}
api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
var set = parseMarks(wikitext);
MARKS.forEach(function (m) { boxes[m].checked = !!set[m]; boxes[m].disabled = false; });
status.textContent = '';
});
function save() {
status.textContent = 'saving…';
MARKS.forEach(function (m) { boxes[m].disabled = true; });
var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
var inner = chosen.map(function (m) { return '{{Mark|' + m + '}}'; }).join('');
var region = '<div class="vol-only vol-marks"><!--VOLMARKS-->' + inner + '<!--/VOLMARKS--></div>';
var done = false, wt = wikitext;
wt = wikitext.replace(/<div class="vol-only vol-marks"><!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS--><\/div>/, function () { done = true; return region; });
if (!done) { wt = wikitext.replace(/<!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS-->/, function () { done = true; return '<!--VOLMARKS-->' + inner + '<!--/VOLMARKS-->'; }); }
if (!done) {
var idx = wt.search(/\n\[\[Category:/);
if (idx >= 0) { wt = wt.slice(0, idx) + '\n' + region + wt.slice(idx); }
else { wt = wt.replace(/\s*$/, '') + '\n\n' + region + '\n'; }
}
api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Update volunteer triage marks' }).then(function () {
wikitext = wt; status.textContent = 'saved ✓';
MARKS.forEach(function (m) { boxes[m].disabled = false; });
setTimeout(function () { status.textContent = ''; }, 1500);
}).catch(function () {
status.textContent = 'save failed'; MARKS.forEach(function (m) { boxes[m].disabled = false; });
});
}
MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
});
});
})();
/* ============ end ICE List client tools ============ */