MediaWiki:Common.js: Difference between revisions
Extend inline add-media form to agent + unidentified-agent pages |
Add submission review queue routing gadget |
||
| Line 311: | Line 311: | ||
}); | }); | ||
} catch (e) {} | } catch (e) {} | ||
}); | |||
/* Submission review queue: route each submission; routing removes the options */ | |||
ready(function () { | |||
var q = document.querySelector('.ic-subq'); | |||
if (!q) { return; } | |||
if (!(typeof markMember === 'function' && markMember())) { | |||
var g = q.querySelector('.ic-subq__buttons'); if (g) { g.innerHTML = '<em>Log in as a volunteer to route this submission.</em>'; } | |||
return; | |||
} | |||
mw.loader.using(['mediawiki.api']).then(function () { | |||
var api = new mw.Api(); | |||
var pn = mw.config.get('wgPageName'); | |||
var file = q.getAttribute('data-file') || '', link = q.getAttribute('data-link') || '', suggested = q.getAttribute('data-suggested') || ''; | |||
var VIDEO = /(youtube\.com|youtu\.be|tiktok\.com|instagram\.com|threads\.(com|net)|facebook\.com|fb\.watch|vimeo\.com|x\.com|twitter\.com)/i; | |||
function esc(x) { return (x || '').replace(/[|]/g, '|').replace(/[\[\]{}]/g, ''); } | |||
var wrap = q.querySelector('.ic-subq__buttons'); wrap.innerHTML = ''; | |||
var msg = document.createElement('div'); msg.className = 'ic-subq__msg'; | |||
function attachTo(target, done) { | |||
api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: target, formatversion: 2 }).then(function (d) { | |||
var p = d.query.pages[0]; | |||
if (!p || p.missing !== undefined) { throw new Error('Page not found: ' + target); } | |||
var txt = p.revisions[0].slots.main.content, media = ''; | |||
if (file) { media += '[[File:' + file + '|thumb|300px|Submitted media]]'; } | |||
if (link && VIDEO.test(link)) { media += (media ? '\n' : '') + '{{Embed|url=' + esc(link) + '}}'; } | |||
if (media) { txt = (txt.indexOf('{{MediaForAgent}}') !== -1) ? txt.replace('{{MediaForAgent}}', media + '\n{{MediaForAgent}}') : (txt.trimEnd() + '\n\n' + media + '\n'); } | |||
if (link && !VIDEO.test(link)) { | |||
var ev = '* ' + esc(link) + ' — submitted via form'; | |||
if (/==\s*Evidence and Sources\s*==/.test(txt)) { txt = txt.replace(/(==\s*Evidence and Sources\s*==[^\n]*\n)/, '$1' + ev + '\n'); } | |||
else { txt = txt.trimEnd() + '\n\n== Evidence and Sources ==\n' + ev + '\n'; } | |||
} | |||
return api.postWithToken('csrf', { action: 'edit', title: target, text: txt, summary: 'Add submitted media (review queue)' }); | |||
}).then(function () { return resolve('routed to [[' + target + ']]'); }).then(done).catch(function (e) { fail(e); }); | |||
} | |||
function resolve(note) { | |||
return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) { | |||
var txt = d.query.pages[0].revisions[0].slots.main.content; | |||
txt = txt.replace(/\{\{Submission entry[^}]*\}\}/, '').replace('[[Category:Submission review]]', ''); | |||
txt = "'''Resolved:''' " + note + '\n\n' + txt + '\n[[Category:Resolved submissions]]\n'; | |||
return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Resolve submission: ' + note }); | |||
}); | |||
} | |||
function fail(e) { msg.textContent = 'Error: ' + (e && e.message ? e.message : e); [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = false; }); } | |||
function done() { msg.innerHTML = '✓ Routed — reloading…'; setTimeout(function () { location.reload(); }, 900); } | |||
function busy() { [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = true; }); msg.textContent = 'Working…'; } | |||
function btn(label, cls, fn) { var b = document.createElement('button'); b.className = 'ic-incident-actbtn' + (cls ? ' ' + cls : ''); b.textContent = label; b.addEventListener('click', fn); wrap.appendChild(b); return b; } | |||
if (suggested) { | |||
btn('Attach to ' + suggested, 'primary', function () { busy(); attachTo(suggested, done); }); | |||
} | |||
var row = document.createElement('span'); row.className = 'ic-subq__row'; | |||
var tin = document.createElement('input'); tin.type = 'text'; tin.className = 'ic-incident-input ic-subq__input'; tin.placeholder = 'Attach to another page (exact title)'; | |||
var tbtn = document.createElement('button'); tbtn.className = 'ic-incident-actbtn'; tbtn.textContent = 'Attach'; | |||
tbtn.addEventListener('click', function () { var t = tin.value.trim(); if (!t) { msg.textContent = 'Enter a page title.'; return; } busy(); attachTo(t, done); }); | |||
row.appendChild(tin); row.appendChild(tbtn); wrap.appendChild(row); | |||
btn('Create unidentified agent', '', function () { | |||
var code = prompt('Code for the new unidentified agent (e.g. NY-0007):', ''); if (!code) { return; } | |||
code = code.replace(/[^A-Za-z0-9 .\-]/g, ''); var st = prompt('State (optional):', '') || ''; | |||
busy(); | |||
var title = 'ICE List:Unidentified agent - ' + code, stcat = st.trim() || 'No State Given'; | |||
var body = '{{UnknownAgent\n | code = ' + esc(code) + '\n | image = ' + (file ? file : 'Agent-placeholder.jpg') + '\n | description = Submitted via media form.' + (link ? ' Source: ' + esc(link) : '') + '\n | agency = Unknown\n | state = ' + esc(st) + '\n | seen = \n | status = Open\n | wanted = Standard\n}}\n<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent →]]</div>\n' + (link && VIDEO.test(link) ? '{{Embed|url=' + esc(link) + '}}\n' : '') + '{{MediaForAgent}}\n\n[[Category:Unidentified agents]][[Category:Standard unidentified agents]][[Category:Unidentified agents in ' + stcat + ']][[Category:Agents in ' + stcat + ']]' + (file ? '[[Category:Agents with photo]]' : '') + '\n'; | |||
api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) { | |||
if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('Already exists: ' + title); } | |||
return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create unidentified agent from submission' }); | |||
}).then(function () { return resolve('created [[' + title + ']]'); }).then(done).catch(fail); | |||
}); | |||
btn('Discard', '', function () { if (!confirm('Discard this submission as not usable?')) { return; } busy(); resolve('discarded (not usable)').then(done).catch(fail); }); | |||
wrap.appendChild(msg); | |||
}); | |||
}); | }); | ||
Revision as of 11:00, 14 July 2026
/* 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);
/* Incident pages: footage + agents front and centre, volunteer add-forms */
ready(function () {
try {
var ns = mw.config.get('wgNamespaceNumber');
var cats = mw.config.get('wgCategories') || [];
var pn = mw.config.get('wgPageName');
var pnText = pn.replace(/_/g, ' ');
var isIncident = (ns === 0 && cats.indexOf('Incidents') !== -1);
var isAgent = (ns === 0 && cats.indexOf('Agents') !== -1) || (ns === 4 && /^ICE List:Unidentified agent - /.test(pnText));
if (!isIncident && !isAgent) { return; }
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) { return; }
var isVol = (typeof markMember === 'function') && markMember();
var hero = document.createElement('div'); hero.className = 'ic-incident-hero';
var panels = {};
var box = content.querySelector('.ic-incident-box');
var incState = '';
if (box) {
[].slice.call(box.querySelectorAll('.ic-incident-meta > div')).forEach(function (r) {
var dt = r.querySelector('dt'), dd = r.querySelector('dd');
if (dt && dd && /^state$/i.test(dt.textContent.trim())) { incState = dd.textContent.trim(); }
});
}
if (isVol) {
var bar = document.createElement('div'); bar.className = 'ic-incident-actions';
function mkBtn(label, key, cls) {
var b = document.createElement('button'); b.className = 'ic-incident-actbtn' + (cls ? ' ' + cls : '');
b.innerHTML = label;
b.addEventListener('click', function () {
var open = panels[key] && panels[key].style.display === 'block';
Object.keys(panels).forEach(function (k) { if (panels[k]) { panels[k].style.display = 'none'; } });
if (panels[key] && !open) { panels[key].style.display = 'block'; }
});
bar.appendChild(b); return b;
}
mkBtn('✎ Add information', 'info', 'primary');
mkBtn('▶ Add footage / media', 'media');
if (isIncident) { mkBtn('+ Add an agent', 'agent'); }
hero.appendChild(bar);
}
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api();
function esc(x) { return (x || '').replace(/[|]/g, '|').replace(/[\[\]{}]/g, ''); }
function panel(title) {
var p = document.createElement('div'); p.className = 'ic-incident-panel'; p.style.display = 'none';
var h = document.createElement('div'); h.className = 'ic-incident-panel__h'; h.textContent = title; p.appendChild(h);
return p;
}
function inp(ph, val) { var i = document.createElement('input'); i.type = 'text'; i.placeholder = ph || ''; i.className = 'ic-incident-input'; if (val) { i.value = val; } return i; }
if (isVol) {
/* INFO */
var pInfo = panel('Add information');
var infoP = document.createElement('p'); infoP.textContent = 'Open the editor to add or correct information on this incident.';
var infoA = document.createElement('a'); infoA.className = 'ic-incident-actbtn primary'; infoA.textContent = 'Open editor'; infoA.href = '/index.php/' + pn + '?action=edit';
pInfo.appendChild(infoP); pInfo.appendChild(infoA); hero.appendChild(pInfo); panels.info = pInfo;
/* MEDIA */
var pMedia = panel('Add footage / media');
var urlI = inp('Paste any social/video link (YouTube, X, TikTok, Instagram, Threads, Facebook, Vimeo)');
var capI = inp('Caption (optional)');
var mBtn = document.createElement('button'); mBtn.className = 'ic-incident-actbtn primary'; mBtn.textContent = 'Embed on this page';
var mMsg = document.createElement('div'); mMsg.className = 'ic-incident-msg';
pMedia.appendChild(urlI); pMedia.appendChild(capI); pMedia.appendChild(mBtn); pMedia.appendChild(mMsg);
hero.appendChild(pMedia); panels.media = pMedia;
mBtn.addEventListener('click', function () {
var u = urlI.value.trim();
if (!/^https?:\/\//.test(u)) { mMsg.textContent = 'Enter a valid https link.'; return; }
mBtn.disabled = true; mMsg.textContent = 'Embedding…';
var emb = '{{Embed|url=' + esc(u) + (capI.value.trim() ? '|caption=' + esc(capI.value.trim()) : '') + '}}';
api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) {
var pg = d.query.pages[0]; var txt = (pg.revisions && pg.revisions[0].slots.main.content) || '';
var marker = '<div class="ic-incident-footage">';
if (txt.indexOf(marker) !== -1) { txt = txt.replace(marker, marker + '\n' + emb); }
else { var ci = txt.search(/\n\[\[Category:/); var blk = '\n' + marker + '\n' + emb + '\n</div>\n'; txt = (ci !== -1) ? txt.slice(0, ci) + blk + txt.slice(ci) : txt + blk; }
return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Add footage/media via incident form' });
}).then(function () { mMsg.textContent = '✓ Added — reloading…'; setTimeout(function () { location.reload(); }, 900); })
.catch(function (e) { mBtn.disabled = false; mMsg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
});
if (isIncident) {
/* AGENT */
var pAgent = panel('Add an agent');
var help = document.createElement('div'); help.className = 'ic-incident-help';
help.innerHTML = 'Leave <b>name</b> blank for an <b>unidentified</b> agent (goes to the Most Wanted board). Enter a name to create an <b>identified</b> agent page. Either way it links to this incident.';
pAgent.appendChild(help);
var nameI = inp('Name — blank if unidentified');
var codeI = inp('Code for unidentified (e.g. ' + (incState ? incState.replace(/[^A-Za-z]/g, '').slice(0, 2).toUpperCase() : 'XX') + '-0001)');
var agencyI = inp('Agency (ICE / CBP / Unknown)');
var stateI = inp('State', incState || '');
var descI = inp('What they did / notes');
[nameI, codeI, agencyI, stateI, descI].forEach(function (e) { pAgent.appendChild(e); });
var mwRow = document.createElement('label'); mwRow.className = 'ic-incident-check';
var mwCb = document.createElement('input'); mwCb.type = 'checkbox'; mwRow.appendChild(mwCb);
mwRow.appendChild(document.createTextNode(' Flag as Most Wanted (unidentified only)')); pAgent.appendChild(mwRow);
var aBtn = document.createElement('button'); aBtn.className = 'ic-incident-actbtn primary'; aBtn.textContent = 'Create & link to this incident';
var aMsg = document.createElement('div'); aMsg.className = 'ic-incident-msg';
pAgent.appendChild(aBtn); pAgent.appendChild(aMsg); hero.appendChild(pAgent); panels.agent = pAgent;
aBtn.addEventListener('click', function () {
var name = nameI.value.trim(), code = codeI.value.trim().replace(/[^A-Za-z0-9 .\-]/g, '');
var agency = agencyI.value.trim() || 'Unknown', st = stateI.value.trim(), desc = descI.value.trim();
var named = !!name;
if (!named && !code) { aMsg.textContent = 'Enter a name, or a code for an unidentified agent.'; return; }
aBtn.disabled = true; aMsg.textContent = 'Creating…';
var title, body, agentLink, stcat = st || 'No State Given';
if (named) {
title = name; agentLink = '[[' + name + ']]';
body = '{{Agent page\n|name=' + esc(name) + '\n|agency=' + esc(agency) + '\n|state=' + esc(st) + '\n|status=Active\n|image=Agent-placeholder.jpg\n|summary=' + esc(name + ' was documented in connection with ' + pnText + '.') + '\n}}\n\n== Documented Incidents ==\n{{IncidentsForAgent}}\n\n{{MediaForAgent}}\n\n[[Category:Agents]][[Category:Agents in ' + stcat + ']][[Category:Agents needing a photo]]\n';
} else {
title = 'ICE List:Unidentified agent - ' + code; agentLink = '[[' + title + ']]';
var wanted = mwCb.checked ? 'Most wanted' : 'Standard';
var wcat = mwCb.checked ? 'Most wanted unidentified agents' : 'Standard unidentified agents';
body = '{{UnknownAgent\n | code = ' + esc(code) + '\n | image = Agent-placeholder.jpg\n | description = ' + esc(desc || ('Recorded during ' + pnText)) + '. Source: [[' + pnText + ']]\n | agency = ' + esc(agency) + '\n | state = ' + esc(st) + '\n | seen = \n | status = Open\n | wanted = ' + wanted + '\n}}\n<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent →]]</div>\n{{MediaForAgent}}\n\n[[Category:Unidentified agents]][[Category:' + wcat + ']][[Category:Unidentified agents in ' + stcat + ']][[Category:Agents in ' + stcat + ']]\n';
}
api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) {
if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('That agent page already exists: ' + title); }
return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create agent via incident form' });
}).then(function () {
return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 });
}).then(function (d) {
var txt = d.query.pages[0].revisions[0].slots.main.content;
if (txt.indexOf(agentLink) === -1) {
txt = txt.replace(/(\|\s*agents_involved\s*=\s*)([^\n]*)/, function (m, p, val) { return p + (val.trim() ? val.trim() + '; ' : '') + agentLink; });
}
return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Link agent ' + title + ' to incident' });
}).then(function () {
aMsg.innerHTML = '✓ Created ' + (named ? 'identified agent' : 'unidentified agent (now on the Most Wanted board)') + '. Reloading…';
setTimeout(function () { location.reload(); }, 1300);
}).catch(function (e) { aBtn.disabled = false; aMsg.textContent = 'Error: ' + (e && e.message ? e.message : e); });
});
}
}
/* FOOTAGE hoist */
var fWrap = document.createElement('div');
var media = content.querySelector('.ic-media-block');
var inFoot = content.querySelector('.ic-incident-footage');
if (media || inFoot) { var fh = document.createElement('div'); fh.className = 'ic-incident-hero__h'; fh.textContent = 'Footage & media'; fWrap.appendChild(fh); }
if (inFoot) { fWrap.appendChild(inFoot); }
if (media) { fWrap.appendChild(media); }
hero.appendChild(fWrap);
/* AGENTS strip */
var seen = {}, codes = [];
if (box) {
[].slice.call(box.querySelectorAll('a[href]')).forEach(function (a) {
var m = decodeURIComponent(a.getAttribute('href') || '').replace(/_/g, ' ').match(/Unidentified agent - ([^"#?|\]&]+)/);
if (m) { var c = m[1].trim(); if (!seen[c]) { seen[c] = 1; codes.push(c); } }
});
}
if (codes.length) {
var aw = document.createElement('div');
var ah = document.createElement('div'); ah.className = 'ic-incident-hero__h'; ah.textContent = 'Agents at the scene'; aw.appendChild(ah);
var strip = document.createElement('div'); strip.className = 'ic-incident-agents';
codes.forEach(function (c) {
var src = '/index.php/Special:Redirect/file/' + encodeURIComponent('Wanted-' + c + '.png');
var card = document.createElement('div'); card.className = 'ic-incident-agent';
card.innerHTML = '<a href="/index.php/ICE_List:Unidentified_agent_-_' + encodeURIComponent(c.replace(/ /g, '_')) + '"><img alt="Agent ' + c + '" loading="lazy"></a><div class="ic-incident-agent__cap">Agent ' + c + ' · unidentified</div>';
var img = card.querySelector('img'); img.onerror = function () { card.style.display = 'none'; }; img.src = src;
strip.appendChild(card);
});
aw.appendChild(strip); hero.appendChild(aw);
}
var firstH = content.querySelector('.mw-heading2, h2');
if (firstH) { content.insertBefore(hero, firstH); } else { content.appendChild(hero); }
});
} catch (e) {}
});
/* Submission review queue: route each submission; routing removes the options */
ready(function () {
var q = document.querySelector('.ic-subq');
if (!q) { return; }
if (!(typeof markMember === 'function' && markMember())) {
var g = q.querySelector('.ic-subq__buttons'); if (g) { g.innerHTML = '<em>Log in as a volunteer to route this submission.</em>'; }
return;
}
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api();
var pn = mw.config.get('wgPageName');
var file = q.getAttribute('data-file') || '', link = q.getAttribute('data-link') || '', suggested = q.getAttribute('data-suggested') || '';
var VIDEO = /(youtube\.com|youtu\.be|tiktok\.com|instagram\.com|threads\.(com|net)|facebook\.com|fb\.watch|vimeo\.com|x\.com|twitter\.com)/i;
function esc(x) { return (x || '').replace(/[|]/g, '|').replace(/[\[\]{}]/g, ''); }
var wrap = q.querySelector('.ic-subq__buttons'); wrap.innerHTML = '';
var msg = document.createElement('div'); msg.className = 'ic-subq__msg';
function attachTo(target, done) {
api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: target, formatversion: 2 }).then(function (d) {
var p = d.query.pages[0];
if (!p || p.missing !== undefined) { throw new Error('Page not found: ' + target); }
var txt = p.revisions[0].slots.main.content, media = '';
if (file) { media += '[[File:' + file + '|thumb|300px|Submitted media]]'; }
if (link && VIDEO.test(link)) { media += (media ? '\n' : '') + '{{Embed|url=' + esc(link) + '}}'; }
if (media) { txt = (txt.indexOf('{{MediaForAgent}}') !== -1) ? txt.replace('{{MediaForAgent}}', media + '\n{{MediaForAgent}}') : (txt.trimEnd() + '\n\n' + media + '\n'); }
if (link && !VIDEO.test(link)) {
var ev = '* ' + esc(link) + ' — submitted via form';
if (/==\s*Evidence and Sources\s*==/.test(txt)) { txt = txt.replace(/(==\s*Evidence and Sources\s*==[^\n]*\n)/, '$1' + ev + '\n'); }
else { txt = txt.trimEnd() + '\n\n== Evidence and Sources ==\n' + ev + '\n'; }
}
return api.postWithToken('csrf', { action: 'edit', title: target, text: txt, summary: 'Add submitted media (review queue)' });
}).then(function () { return resolve('routed to [[' + target + ']]'); }).then(done).catch(function (e) { fail(e); });
}
function resolve(note) {
return api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: pn, formatversion: 2 }).then(function (d) {
var txt = d.query.pages[0].revisions[0].slots.main.content;
txt = txt.replace(/\{\{Submission entry[^}]*\}\}/, '').replace('[[Category:Submission review]]', '');
txt = "'''Resolved:''' " + note + '\n\n' + txt + '\n[[Category:Resolved submissions]]\n';
return api.postWithToken('csrf', { action: 'edit', title: pn, text: txt, summary: 'Resolve submission: ' + note });
});
}
function fail(e) { msg.textContent = 'Error: ' + (e && e.message ? e.message : e); [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = false; }); }
function done() { msg.innerHTML = '✓ Routed — reloading…'; setTimeout(function () { location.reload(); }, 900); }
function busy() { [].forEach.call(wrap.querySelectorAll('button,input'), function (x) { x.disabled = true; }); msg.textContent = 'Working…'; }
function btn(label, cls, fn) { var b = document.createElement('button'); b.className = 'ic-incident-actbtn' + (cls ? ' ' + cls : ''); b.textContent = label; b.addEventListener('click', fn); wrap.appendChild(b); return b; }
if (suggested) {
btn('Attach to ' + suggested, 'primary', function () { busy(); attachTo(suggested, done); });
}
var row = document.createElement('span'); row.className = 'ic-subq__row';
var tin = document.createElement('input'); tin.type = 'text'; tin.className = 'ic-incident-input ic-subq__input'; tin.placeholder = 'Attach to another page (exact title)';
var tbtn = document.createElement('button'); tbtn.className = 'ic-incident-actbtn'; tbtn.textContent = 'Attach';
tbtn.addEventListener('click', function () { var t = tin.value.trim(); if (!t) { msg.textContent = 'Enter a page title.'; return; } busy(); attachTo(t, done); });
row.appendChild(tin); row.appendChild(tbtn); wrap.appendChild(row);
btn('Create unidentified agent', '', function () {
var code = prompt('Code for the new unidentified agent (e.g. NY-0007):', ''); if (!code) { return; }
code = code.replace(/[^A-Za-z0-9 .\-]/g, ''); var st = prompt('State (optional):', '') || '';
busy();
var title = 'ICE List:Unidentified agent - ' + code, stcat = st.trim() || 'No State Given';
var body = '{{UnknownAgent\n | code = ' + esc(code) + '\n | image = ' + (file ? file : 'Agent-placeholder.jpg') + '\n | description = Submitted via media form.' + (link ? ' Source: ' + esc(link) : '') + '\n | agency = Unknown\n | state = ' + esc(st) + '\n | seen = \n | status = Open\n | wanted = Standard\n}}\n<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent →]]</div>\n' + (link && VIDEO.test(link) ? '{{Embed|url=' + esc(link) + '}}\n' : '') + '{{MediaForAgent}}\n\n[[Category:Unidentified agents]][[Category:Standard unidentified agents]][[Category:Unidentified agents in ' + stcat + ']][[Category:Agents in ' + stcat + ']]' + (file ? '[[Category:Agents with photo]]' : '') + '\n';
api.get({ action: 'query', titles: title, formatversion: 2 }).then(function (d) {
if (d.query.pages[0] && d.query.pages[0].missing === undefined) { throw new Error('Already exists: ' + title); }
return api.postWithToken('csrf', { action: 'edit', title: title, createonly: 1, text: body, summary: 'Create unidentified agent from submission' });
}).then(function () { return resolve('created [[' + title + ']]'); }).then(done).catch(fail);
});
btn('Discard', '', function () { if (!confirm('Discard this submission as not usable?')) { return; } busy(); resolve('discarded (not usable)').then(done).catch(fail); });
wrap.appendChild(msg);
});
});
/* Wanted posters: with/without photo filter (per-state pages) */
ready(function () {
var bar = document.getElementById('ic-photo-filter');
if (!bar) { return; }
var secs = [].slice.call(document.querySelectorAll('.ic-poster-section[data-photo]'));
if (!secs.length) { return; }
function hasTiles(sec) { return sec && sec.querySelector('.ic-poster-tile'); }
secs.forEach(function (s) { if (!hasTiles(s)) { s.style.display = 'none'; } });
var live = secs.filter(hasTiles);
var hasW = live.some(function (s) { return s.getAttribute('data-photo') === 'yes'; });
var hasN = live.some(function (s) { return s.getAttribute('data-photo') === 'no'; });
if (!(hasW && hasN)) { return; }
var lbl = document.createElement('span'); lbl.className = 'ic-photo-lbl'; lbl.textContent = 'Show:'; bar.appendChild(lbl);
var defs = [['all', 'All'], ['with', 'With photo'], ['needed', 'Photo needed']];
var btns = [];
function apply(mode) {
live.forEach(function (s) { var p = s.getAttribute('data-photo'); s.style.display = (mode === 'all' || (mode === 'with' && p === 'yes') || (mode === 'needed' && p === 'no')) ? '' : 'none'; });
btns.forEach(function (b) { b.classList.toggle('is-active', b.getAttribute('data-m') === mode); });
}
defs.forEach(function (d) {
var b = document.createElement('button'); b.className = 'ic-photo-btn'; b.setAttribute('data-m', d[0]); b.textContent = d[1];
b.addEventListener('click', function () { apply(d[0]); }); bar.appendChild(b); btns.push(b);
});
apply('all');
});
/* 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, needTT = false;
function ytId(u){ var m=u.match(/[?&]v=([\w-]{6,})/)||u.match(/youtu\.be\/([\w-]{6,})/)||u.match(/\/(?:shorts|embed|live)\/([\w-]{6,})/); return m?m[1]:''; }
function vimeoId(u){ var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/); return m?m[1]:''; }
function detect(u){ var h; try{ h=new URL(u).hostname.replace(/^www\./,'').toLowerCase(); }catch(e){ return ''; }
if(h.indexOf('youtube')>-1||h==='youtu.be') return 'youtube';
if(h.indexOf('vimeo')>-1) return 'vimeo';
if(h.indexOf('instagram')>-1) return 'instagram';
if(h.indexOf('tiktok')>-1) return 'tiktok';
if(h.indexOf('facebook')>-1||h==='fb.watch') return 'facebook';
if(h==='x.com'||h.indexOf('twitter')>-1) return 'x';
if(h.indexOf('threads')>-1) return 'threads';
return ''; }
nodes.forEach(function (el) {
el.setAttribute('data-done', '1');
var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
var plat = (el.getAttribute('data-platform') || '').toLowerCase().trim();
if (!plat) {
if (el.classList.contains('ic-embed-youtube')) { plat = 'youtube'; }
else if (el.classList.contains('ic-embed-vimeo')) { plat = 'vimeo'; }
else if (el.classList.contains('ic-embed-x')) { plat = 'x'; }
else if (el.classList.contains('ic-embed-instagram')) { plat = 'instagram'; }
else if (el.classList.contains('ic-embed-threads')) { plat = 'threads'; }
}
if (!plat && url) { plat = detect(url); }
if (plat === 'youtube' && !id && url) { id = ytId(url); }
if (plat === 'vimeo' && !id && url) { id = vimeoId(url); }
if (plat === 'youtube' && id) {
el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
} else if (plat === 'vimeo' && id) {
el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
} else if ((plat === 'x' || plat === 'twitter') && url) {
el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
} else if (plat === 'instagram' && url) {
el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
} else if (plat === 'threads' && url) {
el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
} else if (plat === 'tiktok' && url) {
el.innerHTML = '<blockquote class="tiktok-embed" cite="' + url + '" style="max-width:605px;min-width:325px;"><a href="' + url + '"></a></blockquote>'; needTT = true;
} else if (plat === 'facebook' && url) {
el.className = 'ic-embed ic-embed--ratio'; el.innerHTML = '<iframe loading="lazy" allowfullscreen scrolling="no" frameborder="0" src="https://www.facebook.com/plugins/video.php?show_text=false&href=' + encodeURIComponent(url) + '"></iframe>';
} else if (url) {
el.innerHTML = '<a class="ic-embed-link" href="' + url + '" target="_blank" rel="noopener">' + (el.textContent || 'View media') + ' ↗</a>';
}
});
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'); }
if (needTT) { load('https://www.tiktok.com/embed.js'); }
});
/* Show the shareable accountability card on named-agent pages */
ready(function () {
try {
if (mw.config.get('wgNamespaceNumber') !== 0) { return; }
if ((mw.config.get('wgCategories') || []).indexOf('Agents') === -1) { return; }
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) { return; }
var infoImg = content.querySelector('.ic-agent-card__photo img, .infobox img');
var noPhoto = !infoImg || /placeholder|nopfp|no[-_]?pfp|noimage/i.test((infoImg.getAttribute('src') || '') + '|' + (infoImg.getAttribute('alt') || ''));
if (noPhoto) {
var pn = document.createElement('div'); pn.className = 'ic-photo-needed';
pn.innerHTML = '📷 <strong>We don\'t have a photo of this agent yet.</strong> Do you? <a href="/index.php/ICE_List:Submit_media_form">Submit a photo</a> to help identify and document them — your identity is not recorded.';
content.insertBefore(pn, content.firstChild);
}
var fn = 'Card-' + mw.config.get('wgPageName') + '.png';
var src = mw.util ? mw.util.getUrl('Special:Redirect/file/' + fn) : '/index.php/Special:Redirect/file/' + encodeURIComponent(fn);
var wrap = document.createElement('div'); wrap.className = 'ic-agent-share';
wrap.innerHTML = '<div class="ic-agent-share-h">Share this agent</div>'
+ '<a class="ic-agent-share-img" href="' + src + '" target="_blank" rel="noopener"><img alt="Accountability share card" loading="lazy"></a>'
+ '<div class="ic-agent-share-c">Download and share this card. Recognize them, or have a clearer photo? <a href="/index.php/ICE_List:Submit_media_form">Submit info</a> — your identity is not recorded.</div>';
var img = wrap.querySelector('img');
img.onerror = function () { wrap.style.display = 'none'; };
img.src = src;
content.appendChild(wrap);
} catch (e) {}
});
/* Wanted posters gallery: filter by state */
ready(function () {
var bar = document.getElementById('ic-poster-filter');
if (!bar) { return; }
var tiles = [].slice.call(document.querySelectorAll('.ic-poster-tile[data-state]'));
if (!tiles.length) { return; }
var counts = {};
tiles.forEach(function (t) { var st = (t.getAttribute('data-state') || '').trim(); if (st) { counts[st] = (counts[st] || 0) + 1; } });
var sel = document.createElement('select'); sel.className = 'ic-wanted-statesel';
var all = document.createElement('option'); all.value = ''; all.textContent = 'All states'; sel.appendChild(all);
Object.keys(counts).sort().forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st + ' (' + counts[st] + ')'; sel.appendChild(o); });
var lab = document.createElement('span'); lab.className = 'ic-wanted-filter-label'; lab.textContent = 'Filter by state: ';
bar.appendChild(lab); bar.appendChild(sel);
sel.addEventListener('change', function () {
var v = sel.value;
tiles.forEach(function (t) { t.style.display = (!v || (t.getAttribute('data-state') || '').trim() === v) ? '' : 'none'; });
});
});
/* Header content-language switch (English <-> /es), replaces the confusing ULS trigger */
ready(function () {
try {
mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {
var page = mw.config.get('wgPageName');
if (mw.config.get('wgNamespaceNumber') < 0) { return; }
var isEs = /\/es$/.test(page);
var base = isEs ? page.replace(/\/es$/, '') : page;
function addToggle(target, label, other) {
var host = document.querySelector('.vector-header-end') || document.querySelector('.mw-header');
if (!host) { return; }
var wrap = document.createElement('div'); wrap.className = 'ic-lang-toggle';
var cur = document.createElement('span'); cur.className = 'ic-lang-cur'; cur.textContent = other;
var a = document.createElement('a'); a.href = mw.util.getUrl(target); a.textContent = label;
wrap.appendChild(document.createTextNode('🌐 '));
wrap.appendChild(cur); wrap.appendChild(document.createTextNode(' / ')); wrap.appendChild(a);
host.appendChild(wrap);
}
if (isEs) {
addToggle(base, 'English', 'Español');
} else {
new mw.Api().get({ action: 'query', titles: base + '/es', formatversion: 2 }).then(function (r) {
var p = r.query && r.query.pages && r.query.pages[0];
if (p && !p.missing) { addToggle(base + '/es', 'Español', 'English'); }
});
}
});
} catch (e) {}
});
/* Most Wanted board: filter by state */
ready(function () {
var bar = document.getElementById('ic-wanted-filter');
if (!bar) { return; }
var cards = [].slice.call(document.querySelectorAll('.ic-wanted-card[data-state]'));
if (!cards.length) { return; }
var counts = {};
cards.forEach(function (c) { var st = (c.getAttribute('data-state') || '').trim(); if (st) { counts[st] = (counts[st] || 0) + 1; } });
var sel = document.createElement('select'); sel.className = 'ic-wanted-statesel';
var all = document.createElement('option'); all.value = ''; all.textContent = 'All states'; sel.appendChild(all);
Object.keys(counts).sort().forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st + ' (' + counts[st] + ')'; sel.appendChild(o); });
var lab = document.createElement('span'); lab.className = 'ic-wanted-filter-label'; lab.textContent = 'Filter by state: ';
bar.appendChild(lab); bar.appendChild(sel);
function apply() {
var v = sel.value;
cards.forEach(function (c) {
var wrap = c.closest('.ic-wanted-entry') || c;
wrap.style.display = (!v || (c.getAttribute('data-state') || '').trim() === v) ? '' : 'none';
});
document.querySelectorAll('.ic-wanted-grid').forEach(function (g) {
var any = [].slice.call(g.querySelectorAll('.ic-wanted-entry')).some(function (e) { return e.style.display !== 'none'; });
var h = g.previousElementSibling;
});
}
sel.addEventListener('change', apply);
});
/* Add unidentified agent form (Most Wanted board, logged-in only) */
ready(function () {
try {
var box = document.getElementById('ic-addagent');
if (!box || !markMember()) { return; }
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api(); box.innerHTML = '';
var form = document.createElement('div'); form.className = 'ic-addform';
var drop = document.createElement('div'); drop.className = 'ic-drop';
var hint = document.createElement('span'); hint.textContent = 'Drag & drop a photo here, or click to choose';
var fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.style.display = 'none';
var preview = document.createElement('img'); preview.className = 'ic-drop-preview'; preview.style.display = 'none';
drop.appendChild(hint); drop.appendChild(fileInput); drop.appendChild(preview);
var chosenFile = null;
function setFile(f) { if (!f || f.type.indexOf('image/') !== 0) { return; } chosenFile = f; var r = new FileReader(); r.onload = function (e) { preview.src = e.target.result; preview.style.display = 'block'; hint.textContent = f.name; }; r.readAsDataURL(f); }
drop.addEventListener('click', function () { fileInput.click(); });
fileInput.addEventListener('change', function () { setFile(fileInput.files[0]); });
['dragover', 'dragenter'].forEach(function (ev) { drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.add('drag'); }); });
['dragleave', 'drop'].forEach(function (ev) { drop.addEventListener(ev, function (e) { e.preventDefault(); drop.classList.remove('drag'); }); });
drop.addEventListener('drop', function (e) { if (e.dataTransfer.files[0]) { setFile(e.dataTransfer.files[0]); } });
form.appendChild(drop);
function field(label, ph, ta) {
var w = document.createElement('label'); w.className = 'ic-addrow'; w.appendChild(document.createTextNode(label));
var i = document.createElement(ta ? 'textarea' : 'input'); if (!ta) { i.type = 'text'; } i.placeholder = ph || ''; w.appendChild(i); form.appendChild(w); return i;
}
var codeI = field('Code (e.g. IL-0008)', 'required'); var descI = field('Description / what they did', '', true);
var agencyI = field('Agency', 'ICE / CBP / Unknown'); var stateI = field('Location / state', '');
var srcI = field('Source or incident (optional)', 'link or [[Incident page]]');
var vidI = field('Video link (optional)', 'paste any social video link');
var mwRow = document.createElement('label'); mwRow.className = 'ic-addrow ic-addcheck';
var mwCb = document.createElement('input'); mwCb.type = 'checkbox'; mwRow.appendChild(mwCb); mwRow.appendChild(document.createTextNode(' Add to Most wanted')); form.appendChild(mwRow);
var btn = document.createElement('button'); btn.className = 'uk-flag'; btn.textContent = 'Create entry';
var msg = document.createElement('div'); msg.className = 'ic-addmsg'; form.appendChild(btn); form.appendChild(msg); box.appendChild(form);
btn.addEventListener('click', function () {
var code = codeI.value.trim().replace(/[^A-Za-z0-9 .\-]/g, '');
if (!code) { msg.textContent = 'Enter a code.'; return; }
var title = 'ICE List:Unidentified agent - ' + code, wanted = mwCb.checked ? 'Most wanted' : 'Standard';
btn.disabled = true; msg.textContent = 'Working…';
function esc(x) { return x.replace(/[|]/g, '|'); }
function createPage(img) {
var desc = descI.value.trim(); if (srcI.value.trim()) { desc += (desc ? '. ' : '') + 'Source: ' + srcI.value.trim(); }
var body = '{{UnknownAgent\n | code = ' + code + '\n | image = ' + (img || 'Agent-placeholder.jpg') +
'\n | description = ' + esc(desc) + '\n | agency = ' + (esc(agencyI.value.trim()) || 'Unknown') +
'\n | state = ' + esc(stateI.value.trim()) + '\n | seen = \n | status = Open\n | wanted = ' + wanted + '\n}}\n' +
'<div class="ic-wanted-submit">[[ICE List:Submit media form|Submit info on this agent →]]</div>\n' + (vidI.value.trim() ? '{{Embed|url=' + vidI.value.trim() + '}}\n' : '') + '{{MediaForAgent}}\n' +
'[[Category:Unidentified agents]][[Category:' + wanted + ' unidentified agents]]\n';
api.create(title, { summary: 'Add unidentified agent (board form)' }, body).then(function () {
msg.innerHTML = 'Created. <a href="/index.php/' + encodeURIComponent(title.replace(/ /g, '_')) + '">Open ' + code + ' →</a>'; btn.disabled = false;
form.querySelectorAll('input,textarea').forEach(function (el) { if (el.type !== 'file') { el.value = ''; } el.checked = false; });
preview.style.display = 'none'; chosenFile = null; hint.textContent = 'Drag & drop a photo here, or click to choose';
}).catch(function (e) { msg.textContent = (e === 'articleexists') ? 'An entry with that code already exists.' : ('Create failed: ' + e); btn.disabled = false; });
}
if (chosenFile) {
var ext = (chosenFile.name.split('.').pop() || 'jpg').toLowerCase(); if (ext.length > 4 || !ext) { ext = 'jpg'; }
api.upload(chosenFile, { filename: code + '.' + ext, comment: 'Unidentified agent photo', text: '[[Category:Unidentified agents]]', ignorewarnings: true })
.then(function () { createPage(code + '.' + ext); })
.catch(function (e) { msg.textContent = 'Photo upload unavailable (' + e + ') — creating entry without it…'; createPage(null); });
} else { createPage(null); }
});
});
} catch (e) {}
});
/* Unidentified agent marking panel (Most Wanted board) */
ready(function () {
try {
if (!markMember()) { return; }
if (mw.config.get('wgAction') !== 'view') { return; }
if ((mw.config.get('wgCategories') || []).indexOf('Unidentified agents') === -1) { return; }
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) { return; }
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api(), page = mw.config.get('wgPageName');
var FIELDS = [['description', 'Description'], ['agency', 'Agency'], ['state', 'Location'], ['status', 'Status']];
var panel = document.createElement('div'); panel.className = 'vol-panel uk-panel';
panel.innerHTML = '<h4>Unidentified agent <span class="vol-status">loading…</span></h4>';
var status = panel.querySelector('.vol-status'), inputs = {};
var wl = document.createElement('label'); wl.className = 'uk-row';
var wcb = document.createElement('input'); wcb.type = 'checkbox'; wcb.disabled = true;
wl.appendChild(wcb); wl.appendChild(document.createTextNode(' Most wanted (feature at top of board)')); panel.appendChild(wl);
FIELDS.forEach(function (f) {
var r = document.createElement('label'); r.className = 'uk-row'; r.appendChild(document.createTextNode(f[1] + ': '));
var i = document.createElement('input'); i.type = 'text'; i.disabled = true; r.appendChild(i); panel.appendChild(r); inputs[f[0]] = i;
});
var btn = document.createElement('button'); btn.className = 'uk-flag'; btn.textContent = 'Save'; btn.disabled = true; panel.appendChild(btn);
var vrow = document.createElement('div'); vrow.className = 'uk-row'; vrow.style.marginTop = '.5rem';
vrow.appendChild(document.createTextNode('Add video (paste any social link): '));
var vin = document.createElement('input'); vin.type = 'text'; vin.placeholder = 'https://…'; vin.disabled = true; vin.style.minWidth = '16rem';
var vbtn = document.createElement('button'); vbtn.className = 'uk-flag'; vbtn.textContent = 'Add video'; vbtn.disabled = true; vbtn.style.marginLeft = '.3rem';
vrow.appendChild(vin); vrow.appendChild(vbtn); panel.appendChild(vrow);
vbtn.addEventListener('click', function () {
var u = vin.value.trim();
if (!/^https?:\/\//.test(u)) { status.textContent = 'enter a full URL'; return; }
status.textContent = 'adding…'; vbtn.disabled = true;
var w = wt, emb = '{{Embed|url=' + u + '}}', idx = w.search(/\n\[\[Category:/);
w = idx >= 0 ? (w.slice(0, idx) + '\n' + emb + w.slice(idx)) : (w.replace(/\s*$/, '') + '\n' + emb + '\n');
api.postWithToken('csrf', { action: 'edit', title: page, text: w, summary: 'Add video embed' })
.then(function () { wt = w; vin.value = ''; status.textContent = 'video added ✓ — reload to view'; vbtn.disabled = false; })
.catch(function () { status.textContent = 'add failed (blocked link?)'; vbtn.disabled = false; });
});
content.insertBefore(panel, content.firstChild);
var wt = '';
function gv(k) { var m = wt.match(new RegExp('\\|\\s*' + k + '\\s*=\\s*([^\\n|}]*)')); return m ? m[1].trim() : ''; }
function setP(w, k, v) { var re = new RegExp('(\\|\\s*' + k + '\\s*=)[^\\n|}]*'); if (re.test(w)) { return w.replace(re, '$1 ' + v.replace(/\$/g, '$$$$')); } return w.replace(/(\{\{UnknownAgent[\s\S]*?)(\n\}\})/, '$1\n | ' + k + ' = ' + v + '$2'); }
api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
try { wt = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wt = ''; }
wcb.checked = /Category:Most wanted unidentified agents/.test(wt) || gv('wanted') === 'Most wanted';
FIELDS.forEach(function (f) { inputs[f[0]].value = gv(f[0]); inputs[f[0]].disabled = false; });
wcb.disabled = false; btn.disabled = false; vin.disabled = false; vbtn.disabled = false; status.textContent = '';
});
btn.addEventListener('click', function () {
status.textContent = 'saving…'; btn.disabled = true;
var w = wt, wanted = wcb.checked ? 'Most wanted' : 'Standard';
w = setP(w, 'wanted', wanted);
FIELDS.forEach(function (f) { w = setP(w, f[0], inputs[f[0]].value.trim()); });
if (/Category:(?:Most wanted|Standard) unidentified agents/.test(w)) {
w = w.replace(/\[\[Category:(?:Most wanted|Standard) unidentified agents\]\]/, '[[Category:' + wanted + ' unidentified agents]]');
} else {
w = w.replace(/(\[\[Category:Unidentified agents\]\])/, '$1[[Category:' + wanted + ' unidentified agents]]');
}
api.postWithToken('csrf', { action: 'edit', title: page, text: w, summary: 'Mark unidentified agent (' + wanted + ')' })
.then(function () { wt = w; status.textContent = 'saved ✓ — reload to update'; btn.disabled = false; })
.catch(function () { status.textContent = 'save failed'; btn.disabled = false; });
});
});
} catch (e) {}
});
/* Volunteer media tagging panel (Cargo Media items) */
ready(function () {
try {
if (!markMember()) { return; }
if (mw.config.get('wgAction') !== 'view') { return; }
if ((mw.config.get('wgCategories') || []).indexOf('Media') === -1) { return; }
var content = document.querySelector('#mw-content-text .mw-parser-output');
if (!content) { return; }
mw.loader.using(['mediawiki.api']).then(function () {
var api = new mw.Api(), page = mw.config.get('wgPageName');
var FIELDS = [['agents','Agents (separate with ;)'],['incidents','Incidents (separate with ;)'],['state','State'],['agency','Agency']];
var STATUS = ['Unverified','Pending','Verified'];
var panel = document.createElement('div'); panel.className = 'vol-panel';
panel.innerHTML = '<h4>Tag media <span class="vol-status">loading…</span></h4>';
var status = panel.querySelector('.vol-status'), inputs = {};
FIELDS.forEach(function (f) {
var row = document.createElement('label'); row.className = 'vol-tag-row';
row.appendChild(document.createTextNode(f[1] + ': '));
var inp = document.createElement('input'); inp.type = 'text'; inp.disabled = true;
row.appendChild(inp); panel.appendChild(row); inputs[f[0]] = inp;
});
var srow = document.createElement('label'); srow.className = 'vol-tag-row';
srow.appendChild(document.createTextNode('Status: '));
var sel = document.createElement('select'); sel.disabled = true;
STATUS.forEach(function (v) { var o = document.createElement('option'); o.value = v; o.textContent = v; sel.appendChild(o); });
srow.appendChild(sel); panel.appendChild(srow); inputs.status = sel;
var btn = document.createElement('button'); btn.textContent = 'Save tags'; btn.disabled = true; btn.className = 'vol-tag-save';
panel.appendChild(btn);
content.insertBefore(panel, content.firstChild);
var wikitext = '';
function getVal(k) { var m = wikitext.match(new RegExp('\\|\\s*' + k + '\\s*=\\s*([^\\n|}]*)')); return m ? m[1].trim() : ''; }
function setParam(wt, k, v) {
var re = new RegExp('(\\|\\s*' + k + '\\s*=)[^\\n|}]*');
if (re.test(wt)) { return wt.replace(re, '$1 ' + v.replace(/\$/g, '$$$$')); }
return wt.replace(/(\{\{Media[\s\S]*?)(\n\}\})/, '$1\n | ' + k + ' = ' + v + '$2');
}
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 = ''; }
if (wikitext.indexOf('{{Media') === -1) { panel.parentNode.removeChild(panel); return; }
FIELDS.forEach(function (f) { inputs[f[0]].value = getVal(f[0]); inputs[f[0]].disabled = false; });
sel.value = getVal('status') || 'Unverified'; sel.disabled = false; btn.disabled = false; status.textContent = '';
});
btn.addEventListener('click', function () {
status.textContent = 'saving…'; btn.disabled = true;
var wt = wikitext;
FIELDS.forEach(function (f) { wt = setParam(wt, f[0], inputs[f[0]].value.trim()); });
wt = setParam(wt, 'status', sel.value);
api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Tag media (agents/incidents/state/status)' }).then(function () {
wikitext = wt; status.textContent = 'saved ✓ — reload to update embeds'; btn.disabled = false;
}).catch(function () { status.textContent = 'save failed'; btn.disabled = false; });
});
});
} catch (e) {}
});
/* Volunteer incident marking + stage 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 STAGES = ['', 'Intake', 'OSINT', 'Detailing', 'Verification', 'Writing', 'Published'];
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 stageRow = document.createElement('div'); stageRow.className = 'vol-stage-row';
stageRow.appendChild(document.createTextNode('Stage: '));
var stageSel = document.createElement('select'); stageSel.disabled = true;
STAGES.forEach(function (st) { var o = document.createElement('option'); o.value = st; o.textContent = st || '(unstaged)'; stageSel.appendChild(o); });
stageRow.appendChild(stageSel); panel.appendChild(stageRow);
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; }
function parseStage(wt) { var m = wt.match(/\{\{\s*Stage\s*\|\s*([^}|]+?)\s*\}\}/); return m ? m[1].trim() : ''; }
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; });
stageSel.value = parseStage(wikitext); stageSel.disabled = false;
status.textContent = '';
});
function save() {
status.textContent = 'saving…';
stageSel.disabled = true; MARKS.forEach(function (m) { boxes[m].disabled = true; });
var stg = stageSel.value;
var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
var inner = (stg ? '{{Stage|' + stg + '}}' : '') + 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 / stage' }).then(function () {
wikitext = wt; status.textContent = 'saved ✓';
stageSel.disabled = false; MARKS.forEach(function (m) { boxes[m].disabled = false; });
setTimeout(function () { status.textContent = ''; }, 1500);
}).catch(function () { status.textContent = 'save failed'; stageSel.disabled = false; MARKS.forEach(function (m) { boxes[m].disabled = false; }); });
}
stageSel.addEventListener('change', save);
MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
});
});
})();
/* ============ end ICE List client tools ============ */