MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
Add Admin Control Panel JS module — live article table with sort, filter, word count, categories, watchers, revisions |
||
| Line 255: | Line 255: | ||
}; | }; | ||
})(); | |||
/* ============================================ */ | |||
/* ADMIN CONTROL PANEL — AlphaX Wiki */ | |||
/* Runs only on AlphaX:Admin_Control_Panel page */ | |||
/* ============================================ */ | |||
(function() { | |||
if (mw.config.get('wgPageName') !== 'AlphaX:Admin_Control_Panel') return; | |||
mw.loader.using(['mediawiki.api'], function() { | |||
// Inject styles | |||
var css = ` | |||
#axcp-root * { box-sizing: border-box; } | |||
#axcp-root { font-family: system-ui,-apple-system,sans-serif; background: #0D0D0D; color: #fff; margin: -8px -20px; padding: 0; border-radius: 12px; overflow: hidden; } | |||
.axcp-header { background: #1A1A1A; border-bottom: 1px solid #2E2E2E; padding: 24px 28px 20px; } | |||
.axcp-title { font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 3px; } | |||
.axcp-subtitle { font-size: 11px; color: #555; text-transform: uppercase; letter-spacing: .1em; } | |||
.axcp-stats-row { display: flex; gap: 12px; margin: 18px 0 0; flex-wrap: wrap; } | |||
.axcp-stat { background: #0D0D0D; border: 1px solid #2E2E2E; border-radius: 12px; padding: 14px 20px; flex: 1; min-width: 100px; } | |||
.axcp-stat-val { font-size: 26px; font-weight: 700; color: #FF6600; line-height: 1; } | |||
.axcp-stat-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: .08em; margin-top: 4px; } | |||
.axcp-controls { display: flex; gap: 10px; padding: 14px 28px; background: #111; border-bottom: 1px solid #2E2E2E; flex-wrap: wrap; align-items: center; } | |||
.axcp-search { flex: 1; min-width: 180px; background: #1A1A1A; border: 1px solid #2E2E2E; border-radius: 8px; padding: 9px 14px; color: #fff; font-size: 13px; outline: none; } | |||
.axcp-search::placeholder { color: #444; } | |||
.axcp-search:focus { border-color: rgba(255,102,0,.5); } | |||
.axcp-select { background: #1A1A1A; border: 1px solid #2E2E2E; border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 12px; outline: none; cursor: pointer; max-width: 200px; } | |||
.axcp-select:focus { border-color: rgba(255,102,0,.5); } | |||
.axcp-count { background: rgba(255,102,0,.12); border: 1px solid rgba(255,102,0,.3); color: #FF6600; border-radius: 6px; padding: 4px 12px; font-size: 12px; font-weight: 600; white-space: nowrap; } | |||
.axcp-refresh-btn { background: linear-gradient(135deg,#FF6600,#FF8533); border: none; border-radius: 8px; color: #fff; padding: 9px 18px; font-size: 12px; font-weight: 700; cursor: pointer; white-space: nowrap; letter-spacing: .03em; } | |||
.axcp-refresh-btn:hover { opacity: .85; } | |||
.axcp-table-wrap { overflow-x: auto; padding: 0 28px 32px; background: #0D0D0D; } | |||
table.axcp-tbl { width: 100%; border-collapse: collapse; margin-top: 14px; font-size: 13px; } | |||
table.axcp-tbl thead th { background: #111; color: #555; text-transform: uppercase; letter-spacing: .08em; font-size: 10px; font-weight: 600; padding: 10px 12px; border-bottom: 1px solid #2E2E2E; cursor: pointer; white-space: nowrap; user-select: none; position: sticky; top: 0; z-index: 2; } | |||
table.axcp-tbl thead th:hover { color: #FF6600; } | |||
table.axcp-tbl thead th.s-asc::after { content: " ↑"; color: #FF6600; } | |||
table.axcp-tbl thead th.s-desc::after { content: " ↓"; color: #FF6600; } | |||
table.axcp-tbl td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,.03); vertical-align: middle; } | |||
table.axcp-tbl tr:hover td { background: rgba(255,102,0,.04); } | |||
table.axcp-tbl tr:last-child td { border-bottom: none; } | |||
.axcp-art-link { color: #fff; text-decoration: none; font-weight: 500; font-size: 13px; } | |||
.axcp-art-link:hover { color: #FF6600; } | |||
.axcp-cat-pill { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; white-space: nowrap; } | |||
.axcp-status { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; } | |||
.st-stub { background: rgba(255,60,60,.15); color: #ff7070; border: 1px solid rgba(255,60,60,.25); } | |||
.st-short { background: rgba(255,166,0,.15); color: #ffb020; border: 1px solid rgba(255,166,0,.25); } | |||
.st-medium { background: rgba(61,220,132,.15); color: #3ddc84; border: 1px solid rgba(61,220,132,.25); } | |||
.st-long { background: rgba(100,160,255,.15); color: #78b0ff; border: 1px solid rgba(100,160,255,.25); } | |||
.axcp-num { text-align: right; font-variant-numeric: tabular-nums; color: #777; } | |||
.axcp-bar-wrap { background: #1A1A1A; border-radius: 3px; height: 5px; width: 60px; display: inline-block; vertical-align: middle; margin-left: 6px; overflow: hidden; } | |||
.axcp-bar-fill { height: 5px; border-radius: 3px; background: linear-gradient(to right, #FF6600, #FF8533); } | |||
.axcp-loading { text-align: center; padding: 80px 40px; color: #555; font-size: 15px; background: #0D0D0D; } | |||
.axcp-progress { color: #FF6600; font-size: 13px; margin-top: 10px; } | |||
.axcp-zero { color: #333; } | |||
.axcp-hi { color: #FF6600; font-weight: 600; } | |||
.axcp-watch { color: #3ddc84; font-weight: 600; } | |||
`; | |||
var styleEl = document.createElement('style'); | |||
styleEl.textContent = css; | |||
document.head.appendChild(styleEl); | |||
// Replace page content | |||
var contentEl = document.querySelector('#mw-content-text .mw-parser-output') || document.getElementById('mw-content-text'); | |||
contentEl.innerHTML = '<div id="axcp-root"><div class="axcp-loading"><div style="font-size:28px;margin-bottom:16px;">⚙️</div><div>Loading article database…</div><div class="axcp-progress" id="axcp-prog">Fetching page list…</div></div></div>'; | |||
var root = document.getElementById('axcp-root'); | |||
var allData = []; | |||
var sortCol = 'title'; | |||
var sortDir = 1; | |||
var filterText = ''; | |||
var filterCat = ''; | |||
var CAT_COLORS = { | |||
'Sexual Health': 'rgba(255,100,100,.18)|#ff8080', | |||
'Dating, Sex & Relationships': 'rgba(255,182,60,.18)|#ffb83c', | |||
'Kink & BDSM': 'rgba(180,80,255,.18)|#c864ff', | |||
'Culture, History & Politics': 'rgba(80,140,255,.18)|#6496ff', | |||
'Fashion & Visual Signaling': 'rgba(255,220,60,.18)|#ffdc3c', | |||
'Community & Identity': 'rgba(61,220,132,.18)|#3ddc84', | |||
'Drugs, Party Culture & Harm Reduction': 'rgba(255,120,40,.18)|#ff8028', | |||
'Life Planning': 'rgba(80,200,255,.18)|#50c8ff' | |||
}; | |||
var KNOWN_CATS = Object.keys(CAT_COLORS); | |||
function catStyle(cat) { | |||
var s = CAT_COLORS[cat] || 'rgba(120,120,120,.18)|#888'; | |||
var p = s.split('|'); | |||
return 'background:'+p[0]+';color:'+p[1]+';border:1px solid '+p[1]+';'; | |||
} | |||
function wordStatus(w) { | |||
if (w < 150) return ['stub', 'Stub']; | |||
if (w < 500) return ['short', 'Short']; | |||
if (w < 1500) return ['medium', 'Medium']; | |||
return ['long', 'Long']; | |||
} | |||
function fmtDate(iso) { | |||
if (!iso) return '<span class="axcp-zero">—</span>'; | |||
var d = new Date(iso); | |||
return d.toLocaleDateString('en-GB', {day:'2-digit',month:'short',year:'numeric'}); | |||
} | |||
function fmtKb(bytes) { | |||
if (!bytes) return '<span class="axcp-zero">—</span>'; | |||
return (bytes/1024).toFixed(1)+' <span style="color:#444;font-size:11px">KB</span>'; | |||
} | |||
function esc(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); } | |||
function render(data) { | |||
var filtered = data.filter(function(r) { | |||
var mt = !filterText || r.title.toLowerCase().indexOf(filterText) >= 0 || (r.cat||'').toLowerCase().indexOf(filterText) >= 0 || (r.subcat||'').toLowerCase().indexOf(filterText) >= 0; | |||
var mc = !filterCat || r.cat === filterCat; | |||
return mt && mc; | |||
}); | |||
filtered.sort(function(a,b) { | |||
var av = a[sortCol], bv = b[sortCol]; | |||
if (typeof av === 'number' && typeof bv === 'number') return sortDir*(av-bv); | |||
return sortDir*String(av||'').localeCompare(String(bv||'')); | |||
}); | |||
var maxW = Math.max.apply(null, [1].concat(filtered.map(function(r){return r.words||0;}))); | |||
var totalW = filtered.reduce(function(s,r){return s+(r.words||0);},0); | |||
var avgW = filtered.length ? Math.round(totalW/filtered.length) : 0; | |||
var totalR = filtered.reduce(function(s,r){return s+(r.revisions||0);},0); | |||
var nocat = filtered.filter(function(r){return !r.cat;}).length; | |||
// Build category list | |||
var cats = {}; | |||
data.forEach(function(r){ if(r.cat) cats[r.cat]=1; }); | |||
var catList = Object.keys(cats).sort(); | |||
var h = ''; | |||
// Header | |||
h += '<div class="axcp-header">'; | |||
h += '<div class="axcp-title">⚙️ Admin Control Panel</div>'; | |||
h += '<div class="axcp-subtitle">AlphaX Wiki · Article Database · Live Data</div>'; | |||
h += '<div class="axcp-stats-row">'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+filtered.length+'</div><div class="axcp-stat-label">Articles</div></div>'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+avgW.toLocaleString()+'</div><div class="axcp-stat-label">Avg Words</div></div>'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+totalW.toLocaleString()+'</div><div class="axcp-stat-label">Total Words</div></div>'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+totalR.toLocaleString()+'</div><div class="axcp-stat-label">Total Revisions</div></div>'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+catList.length+'</div><div class="axcp-stat-label">Categories</div></div>'; | |||
h += '<div class="axcp-stat"><div class="axcp-stat-val" style="color:'+(nocat>0?'#ff7070':'#3ddc84')+'">'+nocat+'</div><div class="axcp-stat-label">Uncategorised</div></div>'; | |||
h += '</div></div>'; | |||
// Controls | |||
h += '<div class="axcp-controls">'; | |||
h += '<input class="axcp-search" id="axcp-s" type="text" placeholder="🔍 Search articles, categories…" value="'+esc(filterText)+'">'; | |||
h += '<select class="axcp-select" id="axcp-cf"><option value="">All Categories ('+catList.length+')</option>'; | |||
catList.forEach(function(c){ h+='<option value="'+esc(c)+'"'+(filterCat===c?' selected':'')+'>'+esc(c)+'</option>'; }); | |||
h += '</select>'; | |||
h += '<select class="axcp-select" id="axcp-sf"><option value="">All Statuses</option><option value="stub">Stub</option><option value="short">Short</option><option value="medium">Medium</option><option value="long">Long</option></select>'; | |||
h += '<span class="axcp-count">'+filtered.length+' articles</span>'; | |||
h += '<button class="axcp-refresh-btn" id="axcp-reload">⟳ Refresh</button>'; | |||
h += '</div>'; | |||
// Table | |||
h += '<div class="axcp-table-wrap"><table class="axcp-tbl">'; | |||
h += '<thead><tr>'; | |||
[ | |||
{k:'title',l:'Article Title'}, | |||
{k:'cat',l:'Category'}, | |||
{k:'subcat',l:'Subcategory'}, | |||
{k:'words',l:'Words'}, | |||
{k:'size',l:'Size'}, | |||
{k:'status',l:'Status'}, | |||
{k:'created',l:'Published'}, | |||
{k:'touched',l:'Last Edited'}, | |||
{k:'revisions',l:'Revisions'}, | |||
{k:'watchers',l:'Watchers'}, | |||
{k:'links',l:'Inbound Links'} | |||
].forEach(function(c) { | |||
var cls = sortCol===c.k ? (sortDir===1?'s-asc':'s-desc') : ''; | |||
h += '<th class="'+cls+'" data-col="'+c.k+'">'+c.l+'</th>'; | |||
}); | |||
h += '</tr></thead><tbody>'; | |||
if (filtered.length === 0) { | |||
h += '<tr><td colspan="11" style="text-align:center;padding:50px;color:#444;">No articles match your filters.</td></tr>'; | |||
} | |||
filtered.forEach(function(r) { | |||
var st = wordStatus(r.words||0); | |||
var barPct = maxW > 0 ? Math.round(((r.words||0)/maxW)*60) : 0; | |||
h += '<tr>'; | |||
h += '<td><a class="axcp-art-link" href="/wiki/'+encodeURIComponent((r.title||'').replace(/ /g,'_'))+'" target="_blank">'+esc(r.title||'')+'</a></td>'; | |||
h += '<td>'+(r.cat ? '<span class="axcp-cat-pill" style="'+catStyle(r.cat)+'">'+esc(r.cat)+'</span>' : '<span class="axcp-zero">—</span>')+'</td>'; | |||
h += '<td style="color:#666;font-size:12px;">'+esc(r.subcat||'—')+'</td>'; | |||
h += '<td class="axcp-num">'+(r.words||0).toLocaleString()+'<span class="axcp-bar-wrap"><span class="axcp-bar-fill" style="width:'+barPct+'px"></span></span></td>'; | |||
h += '<td class="axcp-num">'+fmtKb(r.size)+'</td>'; | |||
h += '<td><span class="axcp-status st-'+st[0]+'">'+st[1]+'</span></td>'; | |||
h += '<td style="color:#666;white-space:nowrap;font-size:12px;">'+fmtDate(r.created)+'</td>'; | |||
h += '<td style="color:#666;white-space:nowrap;font-size:12px;">'+fmtDate(r.touched)+'</td>'; | |||
h += '<td class="axcp-num '+(r.revisions>20?'axcp-hi':'')+'">'+((r.revisions||0))+'</td>'; | |||
h += '<td class="axcp-num '+(r.watchers>0?'axcp-watch':'axcp-zero')+'">'+((r.watchers!=null&&r.watchers>0)? '👁 '+r.watchers : '0')+'</td>'; | |||
h += '<td class="axcp-num '+(r.links>5?'axcp-hi':'')+'">'+((r.links||0))+'</td>'; | |||
h += '</tr>'; | |||
}); | |||
h += '</tbody></table></div>'; | |||
root.innerHTML = h; | |||
// Bind events | |||
var searchEl = document.getElementById('axcp-s'); | |||
if (searchEl) searchEl.addEventListener('input', function(){ filterText = this.value.toLowerCase(); render(allData); }); | |||
var catEl = document.getElementById('axcp-cf'); | |||
if (catEl) catEl.addEventListener('change', function(){ filterCat = this.value; render(allData); }); | |||
var sfEl = document.getElementById('axcp-sf'); | |||
if (sfEl) sfEl.addEventListener('change', function() { | |||
var sv = this.value; | |||
if (!sv) { render(allData); return; } | |||
// Filter by status | |||
var prev = allData; | |||
var tmp = allData.filter(function(r){ return wordStatus(r.words||0)[0] === sv; }); | |||
allData = prev; | |||
// Temporary local render | |||
var savedFilter = filterText; | |||
filterText = ''; | |||
var fc = filterCat; | |||
filterCat = ''; | |||
// just re-render with status override | |||
root.querySelector('#axcp-reload') && void 0; | |||
render(tmp); | |||
allData = prev; | |||
filterText = savedFilter; | |||
filterCat = fc; | |||
}); | |||
var reloadEl = document.getElementById('axcp-reload'); | |||
if (reloadEl) reloadEl.addEventListener('click', function(){ allData=[]; loadData(); }); | |||
document.querySelectorAll('table.axcp-tbl thead th').forEach(function(th){ | |||
th.addEventListener('click', function(){ | |||
var col = this.getAttribute('data-col'); | |||
if (sortCol === col) { sortDir *= -1; } else { sortCol = col; sortDir = 1; } | |||
render(allData); | |||
}); | |||
}); | |||
} | |||
function setProgress(msg) { | |||
var el = document.getElementById('axcp-prog'); | |||
if (el) el.textContent = msg; | |||
} | |||
async function loadData() { | |||
root.innerHTML = '<div class="axcp-loading"><div style="font-size:28px;margin-bottom:16px;">⚙️</div><div>Loading article database…</div><div class="axcp-progress" id="axcp-prog">Step 1/4 — Fetching page list…</div></div>'; | |||
try { | |||
// 1. Get all page IDs | |||
var pages = []; | |||
var apcontinue = null; | |||
do { | |||
var u = '/api.php?action=query&list=allpages&apnamespace=0&aplimit=500&format=json'; | |||
if (apcontinue) u += '&apcontinue=' + encodeURIComponent(apcontinue); | |||
var r = await fetch(u); var d = await r.json(); | |||
pages = pages.concat(d.query.allpages); | |||
apcontinue = d.continue ? d.continue.apcontinue : null; | |||
} while (apcontinue); | |||
setProgress('Step 2/4 — Fetching page info & categories for '+pages.length+' articles…'); | |||
var map = {}; | |||
pages.forEach(function(p){ map[p.pageid] = {title:p.title,cat:'',subcat:'',words:0,size:0,created:'',touched:'',revisions:0,watchers:null,links:0,status:''}; }); | |||
var ids = pages.map(function(p){return p.pageid;}); | |||
// 2. Info + categories + first revision (creation date) | |||
for (var i=0; i<ids.length; i+=50) { | |||
var chunk = ids.slice(i,i+50).join('|'); | |||
var r2 = await fetch('/api.php?action=query&pageids='+chunk+'&prop=info|categories|revisions&inprop=watchers&rvprop=timestamp&rvdir=newer&rvlimit=1&cllimit=15&format=json'); | |||
var d2 = await r2.json(); | |||
Object.keys(d2.query.pages).forEach(function(pid){ | |||
var pg = d2.query.pages[pid]; if(!map[pid])return; | |||
map[pid].size = pg.length || 0; | |||
map[pid].touched = pg.touched || ''; | |||
map[pid].watchers = (pg.watchers != null) ? pg.watchers : null; | |||
if (pg.revisions && pg.revisions[0]) map[pid].created = pg.revisions[0].timestamp || ''; | |||
if (pg.categories) { | |||
pg.categories.forEach(function(c){ | |||
var cn = c.title.replace('Category:',''); | |||
if (KNOWN_CATS.indexOf(cn) >= 0) { if(!map[pid].cat) map[pid].cat = cn; } | |||
else if (cn.indexOf('stub')<0 && cn.indexOf('article')<0 && cn.length < 60) { if(!map[pid].subcat) map[pid].subcat = cn; } | |||
}); | |||
} | |||
}); | |||
} | |||
setProgress('Step 3/4 — Counting revisions…'); | |||
// 3. Revision counts (rvlimit=max gives us up to 500; count them) | |||
for (var j=0; j<ids.length; j+=20) { | |||
var chunk2 = ids.slice(j,j+20).join('|'); | |||
var r3 = await fetch('/api.php?action=query&pageids='+chunk2+'&prop=revisions&rvprop=ids&rvlimit=max&format=json'); | |||
var d3 = await r3.json(); | |||
Object.keys(d3.query.pages).forEach(function(pid){ | |||
var pg = d3.query.pages[pid]; if(!map[pid])return; | |||
map[pid].revisions = (pg.revisions||[]).length; | |||
}); | |||
} | |||
setProgress('Step 4/4 — Calculating word counts & inbound links…'); | |||
// 4. Word counts from wikitext + inbound links | |||
for (var k=0; k<ids.length; k+=10) { | |||
var chunk3 = ids.slice(k,k+10).join('|'); | |||
var r4 = await fetch('/api.php?action=query&pageids='+chunk3+'&prop=revisions|linkshere&rvprop=content&rvslots=main&lhnamespace=0&lhlimit=max&format=json'); | |||
var d4 = await r4.json(); | |||
Object.keys(d4.query.pages).forEach(function(pid){ | |||
var pg = d4.query.pages[pid]; if(!map[pid])return; | |||
map[pid].links = (pg.linkshere||[]).length; | |||
if (!pg.revisions||!pg.revisions[0]) return; | |||
var slot = pg.revisions[0].slots ? pg.revisions[0].slots.main : pg.revisions[0]; | |||
var content = slot['*'] || slot.content || ''; | |||
var clean = content | |||
.replace(/<!--[sS]*?-->/g,'') | |||
.replace(/<[^>]+>/g,' ') | |||
.replace(/[[File:[^]]+]]/gi,'') | |||
.replace(/[[(?:[^]|]+|)?([^]]+)]]/g,'$1') | |||
.replace(/{{[sS]*?}}/g,'') | |||
.replace(/={2,}([^=]+)={2,}/g,'$1') | |||
.replace(/[|!*#;:]/g,' ') | |||
.replace(/https?://S+/g,'') | |||
.replace(/s+/g,' ').trim(); | |||
map[pid].words = clean ? clean.split(/s+/).filter(function(w){return w.length>1;}).length : 0; | |||
}); | |||
} | |||
allData = Object.values(map); | |||
render(allData); | |||
} catch(e) { | |||
root.innerHTML = '<div class="axcp-loading" style="color:#ff7070;">⚠️ Error: '+e.message+'<br><br><button onclick="location.reload()" style="background:#FF6600;border:none;color:#fff;padding:10px 20px;border-radius:8px;cursor:pointer;font-size:14px;">Retry</button></div>'; | |||
} | |||
} | |||
loadData(); | |||
}); | |||
})(); | })(); | ||
Revision as of 01:34, 21 April 2026
(function () {
const KEY = "alphax_entry_consent";
const ANALYTICS_KEY = "alphax_analytics_consent";
const MAX_AGE = 30 * 24 * 60 * 60 * 1000;
const GA_ID = "G-XXXXXXXXXX";
const saved = localStorage.getItem(KEY);
if (saved && (Date.now() - Number(saved) < MAX_AGE)) {
const analyticsConsent = localStorage.getItem(ANALYTICS_KEY);
if (analyticsConsent === "granted") {
loadGoogleAnalytics();
}
return;
}
const style = document.createElement("style");
style.innerHTML = `
#age-overlay{
position:fixed;
inset:0;
background:rgba(0,0,0,0.85);
backdrop-filter:blur(8px);
display:flex;
align-items:center;
justify-content:center;
z-index:999999;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial;
}
#age-box{
background:#0f0f12;
border:1px solid rgba(255,255,255,0.08);
border-radius:20px;
padding:40px 34px;
max-width:520px;
width:90%;
color:white;
box-shadow:0 25px 80px rgba(0,0,0,0.6);
text-align:center;
}
#age-box h2{
margin-top:0;
font-size:32px;
margin-bottom:18px;
}
#age-text{
color:#cfcfd6;
line-height:1.6;
margin-bottom:28px;
}
#checks{
text-align:left;
margin-bottom:28px;
}
#checks label{
display:block;
margin:12px 0;
font-size:15px;
color:#e4e4ea;
cursor:pointer;
line-height:1.5;
}
#checks input{
margin-right:10px;
}
#optional-title{
margin-top:22px;
margin-bottom:8px;
font-size:13px;
font-weight:700;
letter-spacing:0.04em;
text-transform:uppercase;
color:#9ca3af;
}
#buttons{
display:flex;
gap:14px;
justify-content:center;
margin-top:10px;
}
#enter{
background:#34d27a;
color:#05110b;
border:none;
padding:14px 26px;
border-radius:10px;
font-size:16px;
font-weight:700;
cursor:pointer;
opacity:0.4;
}
#enter.active{
opacity:1;
}
#exit{
background:#e74c3c;
border:none;
color:white;
padding:14px 22px;
border-radius:10px;
font-size:16px;
cursor:pointer;
}
#links{
margin-top:22px;
font-size:13px;
opacity:0.8;
}
#links a{
color:#bbb;
margin:0 10px;
text-decoration:none;
}
#links a:hover{
color:white;
}
`;
document.head.appendChild(style);
const overlay = document.createElement("div");
overlay.id = "age-overlay";
overlay.innerHTML = `
<div id="age-box">
<h2>18+ Access</h2>
<div id="age-text">
This website contains adult educational content.<br>
You must confirm the following to enter.
</div>
<div id="checks">
<label>
<input type="checkbox" id="c1">
I confirm that I am at least 18 years old
</label>
<label>
<input type="checkbox" id="c2">
I agree to the Terms & Conditions
</label>
<label>
<input type="checkbox" id="c3">
I agree to the Privacy Policy
</label>
<div id="optional-title">Optional</div>
<label>
<input type="checkbox" id="c4">
Allow anonymous analytics to help improve the website
</label>
</div>
<div id="buttons">
<button id="enter">Enter</button>
<button id="exit">Exit</button>
</div>
<div id="links">
<a href="/index.php?title=Terms_and_Conditions">T&C</a>
<a href="/index.php?title=Datenschutz">Privacy Policy</a>
</div>
</div>
`;
document.body.appendChild(overlay);
const c1 = document.getElementById("c1");
const c2 = document.getElementById("c2");
const c3 = document.getElementById("c3");
const c4 = document.getElementById("c4");
const enter = document.getElementById("enter");
function checkAll(){
if (c1.checked && c2.checked && c3.checked) {
enter.classList.add("active");
enter.disabled = false;
} else {
enter.classList.remove("active");
enter.disabled = true;
}
}
c1.onchange = checkAll;
c2.onchange = checkAll;
c3.onchange = checkAll;
enter.disabled = true;
function loadGoogleAnalytics() {
if (window.__gaLoaded) return;
window.__gaLoaded = true;
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
window.gtag = gtag;
gtag("consent", "default", {
analytics_storage: "granted"
});
const script = document.createElement("script");
script.async = true;
script.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(GA_ID);
document.head.appendChild(script);
gtag("js", new Date());
gtag("config", GA_ID, {
anonymize_ip: true
});
}
enter.onclick = function(){
localStorage.setItem(KEY, String(Date.now()));
if (c4.checked) {
localStorage.setItem(ANALYTICS_KEY, "granted");
loadGoogleAnalytics();
} else {
localStorage.setItem(ANALYTICS_KEY, "denied");
}
overlay.remove();
};
document.getElementById("exit").onclick = function(){
window.location.href = "https://google.com";
};
})();
/* ============================================ */
/* ADMIN CONTROL PANEL — AlphaX Wiki */
/* Runs only on AlphaX:Admin_Control_Panel page */
/* ============================================ */
(function() {
if (mw.config.get('wgPageName') !== 'AlphaX:Admin_Control_Panel') return;
mw.loader.using(['mediawiki.api'], function() {
// Inject styles
var css = `
#axcp-root * { box-sizing: border-box; }
#axcp-root { font-family: system-ui,-apple-system,sans-serif; background: #0D0D0D; color: #fff; margin: -8px -20px; padding: 0; border-radius: 12px; overflow: hidden; }
.axcp-header { background: #1A1A1A; border-bottom: 1px solid #2E2E2E; padding: 24px 28px 20px; }
.axcp-title { font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 3px; }
.axcp-subtitle { font-size: 11px; color: #555; text-transform: uppercase; letter-spacing: .1em; }
.axcp-stats-row { display: flex; gap: 12px; margin: 18px 0 0; flex-wrap: wrap; }
.axcp-stat { background: #0D0D0D; border: 1px solid #2E2E2E; border-radius: 12px; padding: 14px 20px; flex: 1; min-width: 100px; }
.axcp-stat-val { font-size: 26px; font-weight: 700; color: #FF6600; line-height: 1; }
.axcp-stat-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: .08em; margin-top: 4px; }
.axcp-controls { display: flex; gap: 10px; padding: 14px 28px; background: #111; border-bottom: 1px solid #2E2E2E; flex-wrap: wrap; align-items: center; }
.axcp-search { flex: 1; min-width: 180px; background: #1A1A1A; border: 1px solid #2E2E2E; border-radius: 8px; padding: 9px 14px; color: #fff; font-size: 13px; outline: none; }
.axcp-search::placeholder { color: #444; }
.axcp-search:focus { border-color: rgba(255,102,0,.5); }
.axcp-select { background: #1A1A1A; border: 1px solid #2E2E2E; border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 12px; outline: none; cursor: pointer; max-width: 200px; }
.axcp-select:focus { border-color: rgba(255,102,0,.5); }
.axcp-count { background: rgba(255,102,0,.12); border: 1px solid rgba(255,102,0,.3); color: #FF6600; border-radius: 6px; padding: 4px 12px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.axcp-refresh-btn { background: linear-gradient(135deg,#FF6600,#FF8533); border: none; border-radius: 8px; color: #fff; padding: 9px 18px; font-size: 12px; font-weight: 700; cursor: pointer; white-space: nowrap; letter-spacing: .03em; }
.axcp-refresh-btn:hover { opacity: .85; }
.axcp-table-wrap { overflow-x: auto; padding: 0 28px 32px; background: #0D0D0D; }
table.axcp-tbl { width: 100%; border-collapse: collapse; margin-top: 14px; font-size: 13px; }
table.axcp-tbl thead th { background: #111; color: #555; text-transform: uppercase; letter-spacing: .08em; font-size: 10px; font-weight: 600; padding: 10px 12px; border-bottom: 1px solid #2E2E2E; cursor: pointer; white-space: nowrap; user-select: none; position: sticky; top: 0; z-index: 2; }
table.axcp-tbl thead th:hover { color: #FF6600; }
table.axcp-tbl thead th.s-asc::after { content: " ↑"; color: #FF6600; }
table.axcp-tbl thead th.s-desc::after { content: " ↓"; color: #FF6600; }
table.axcp-tbl td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,.03); vertical-align: middle; }
table.axcp-tbl tr:hover td { background: rgba(255,102,0,.04); }
table.axcp-tbl tr:last-child td { border-bottom: none; }
.axcp-art-link { color: #fff; text-decoration: none; font-weight: 500; font-size: 13px; }
.axcp-art-link:hover { color: #FF6600; }
.axcp-cat-pill { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; white-space: nowrap; }
.axcp-status { display: inline-block; padding: 2px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; }
.st-stub { background: rgba(255,60,60,.15); color: #ff7070; border: 1px solid rgba(255,60,60,.25); }
.st-short { background: rgba(255,166,0,.15); color: #ffb020; border: 1px solid rgba(255,166,0,.25); }
.st-medium { background: rgba(61,220,132,.15); color: #3ddc84; border: 1px solid rgba(61,220,132,.25); }
.st-long { background: rgba(100,160,255,.15); color: #78b0ff; border: 1px solid rgba(100,160,255,.25); }
.axcp-num { text-align: right; font-variant-numeric: tabular-nums; color: #777; }
.axcp-bar-wrap { background: #1A1A1A; border-radius: 3px; height: 5px; width: 60px; display: inline-block; vertical-align: middle; margin-left: 6px; overflow: hidden; }
.axcp-bar-fill { height: 5px; border-radius: 3px; background: linear-gradient(to right, #FF6600, #FF8533); }
.axcp-loading { text-align: center; padding: 80px 40px; color: #555; font-size: 15px; background: #0D0D0D; }
.axcp-progress { color: #FF6600; font-size: 13px; margin-top: 10px; }
.axcp-zero { color: #333; }
.axcp-hi { color: #FF6600; font-weight: 600; }
.axcp-watch { color: #3ddc84; font-weight: 600; }
`;
var styleEl = document.createElement('style');
styleEl.textContent = css;
document.head.appendChild(styleEl);
// Replace page content
var contentEl = document.querySelector('#mw-content-text .mw-parser-output') || document.getElementById('mw-content-text');
contentEl.innerHTML = '<div id="axcp-root"><div class="axcp-loading"><div style="font-size:28px;margin-bottom:16px;">⚙️</div><div>Loading article database…</div><div class="axcp-progress" id="axcp-prog">Fetching page list…</div></div></div>';
var root = document.getElementById('axcp-root');
var allData = [];
var sortCol = 'title';
var sortDir = 1;
var filterText = '';
var filterCat = '';
var CAT_COLORS = {
'Sexual Health': 'rgba(255,100,100,.18)|#ff8080',
'Dating, Sex & Relationships': 'rgba(255,182,60,.18)|#ffb83c',
'Kink & BDSM': 'rgba(180,80,255,.18)|#c864ff',
'Culture, History & Politics': 'rgba(80,140,255,.18)|#6496ff',
'Fashion & Visual Signaling': 'rgba(255,220,60,.18)|#ffdc3c',
'Community & Identity': 'rgba(61,220,132,.18)|#3ddc84',
'Drugs, Party Culture & Harm Reduction': 'rgba(255,120,40,.18)|#ff8028',
'Life Planning': 'rgba(80,200,255,.18)|#50c8ff'
};
var KNOWN_CATS = Object.keys(CAT_COLORS);
function catStyle(cat) {
var s = CAT_COLORS[cat] || 'rgba(120,120,120,.18)|#888';
var p = s.split('|');
return 'background:'+p[0]+';color:'+p[1]+';border:1px solid '+p[1]+';';
}
function wordStatus(w) {
if (w < 150) return ['stub', 'Stub'];
if (w < 500) return ['short', 'Short'];
if (w < 1500) return ['medium', 'Medium'];
return ['long', 'Long'];
}
function fmtDate(iso) {
if (!iso) return '<span class="axcp-zero">—</span>';
var d = new Date(iso);
return d.toLocaleDateString('en-GB', {day:'2-digit',month:'short',year:'numeric'});
}
function fmtKb(bytes) {
if (!bytes) return '<span class="axcp-zero">—</span>';
return (bytes/1024).toFixed(1)+' <span style="color:#444;font-size:11px">KB</span>';
}
function esc(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
function render(data) {
var filtered = data.filter(function(r) {
var mt = !filterText || r.title.toLowerCase().indexOf(filterText) >= 0 || (r.cat||'').toLowerCase().indexOf(filterText) >= 0 || (r.subcat||'').toLowerCase().indexOf(filterText) >= 0;
var mc = !filterCat || r.cat === filterCat;
return mt && mc;
});
filtered.sort(function(a,b) {
var av = a[sortCol], bv = b[sortCol];
if (typeof av === 'number' && typeof bv === 'number') return sortDir*(av-bv);
return sortDir*String(av||'').localeCompare(String(bv||''));
});
var maxW = Math.max.apply(null, [1].concat(filtered.map(function(r){return r.words||0;})));
var totalW = filtered.reduce(function(s,r){return s+(r.words||0);},0);
var avgW = filtered.length ? Math.round(totalW/filtered.length) : 0;
var totalR = filtered.reduce(function(s,r){return s+(r.revisions||0);},0);
var nocat = filtered.filter(function(r){return !r.cat;}).length;
// Build category list
var cats = {};
data.forEach(function(r){ if(r.cat) cats[r.cat]=1; });
var catList = Object.keys(cats).sort();
var h = '';
// Header
h += '<div class="axcp-header">';
h += '<div class="axcp-title">⚙️ Admin Control Panel</div>';
h += '<div class="axcp-subtitle">AlphaX Wiki · Article Database · Live Data</div>';
h += '<div class="axcp-stats-row">';
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+filtered.length+'</div><div class="axcp-stat-label">Articles</div></div>';
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+avgW.toLocaleString()+'</div><div class="axcp-stat-label">Avg Words</div></div>';
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+totalW.toLocaleString()+'</div><div class="axcp-stat-label">Total Words</div></div>';
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+totalR.toLocaleString()+'</div><div class="axcp-stat-label">Total Revisions</div></div>';
h += '<div class="axcp-stat"><div class="axcp-stat-val">'+catList.length+'</div><div class="axcp-stat-label">Categories</div></div>';
h += '<div class="axcp-stat"><div class="axcp-stat-val" style="color:'+(nocat>0?'#ff7070':'#3ddc84')+'">'+nocat+'</div><div class="axcp-stat-label">Uncategorised</div></div>';
h += '</div></div>';
// Controls
h += '<div class="axcp-controls">';
h += '<input class="axcp-search" id="axcp-s" type="text" placeholder="🔍 Search articles, categories…" value="'+esc(filterText)+'">';
h += '<select class="axcp-select" id="axcp-cf"><option value="">All Categories ('+catList.length+')</option>';
catList.forEach(function(c){ h+='<option value="'+esc(c)+'"'+(filterCat===c?' selected':'')+'>'+esc(c)+'</option>'; });
h += '</select>';
h += '<select class="axcp-select" id="axcp-sf"><option value="">All Statuses</option><option value="stub">Stub</option><option value="short">Short</option><option value="medium">Medium</option><option value="long">Long</option></select>';
h += '<span class="axcp-count">'+filtered.length+' articles</span>';
h += '<button class="axcp-refresh-btn" id="axcp-reload">⟳ Refresh</button>';
h += '</div>';
// Table
h += '<div class="axcp-table-wrap"><table class="axcp-tbl">';
h += '<thead><tr>';
[
{k:'title',l:'Article Title'},
{k:'cat',l:'Category'},
{k:'subcat',l:'Subcategory'},
{k:'words',l:'Words'},
{k:'size',l:'Size'},
{k:'status',l:'Status'},
{k:'created',l:'Published'},
{k:'touched',l:'Last Edited'},
{k:'revisions',l:'Revisions'},
{k:'watchers',l:'Watchers'},
{k:'links',l:'Inbound Links'}
].forEach(function(c) {
var cls = sortCol===c.k ? (sortDir===1?'s-asc':'s-desc') : '';
h += '<th class="'+cls+'" data-col="'+c.k+'">'+c.l+'</th>';
});
h += '</tr></thead><tbody>';
if (filtered.length === 0) {
h += '<tr><td colspan="11" style="text-align:center;padding:50px;color:#444;">No articles match your filters.</td></tr>';
}
filtered.forEach(function(r) {
var st = wordStatus(r.words||0);
var barPct = maxW > 0 ? Math.round(((r.words||0)/maxW)*60) : 0;
h += '<tr>';
h += '<td><a class="axcp-art-link" href="/wiki/'+encodeURIComponent((r.title||'').replace(/ /g,'_'))+'" target="_blank">'+esc(r.title||'')+'</a></td>';
h += '<td>'+(r.cat ? '<span class="axcp-cat-pill" style="'+catStyle(r.cat)+'">'+esc(r.cat)+'</span>' : '<span class="axcp-zero">—</span>')+'</td>';
h += '<td style="color:#666;font-size:12px;">'+esc(r.subcat||'—')+'</td>';
h += '<td class="axcp-num">'+(r.words||0).toLocaleString()+'<span class="axcp-bar-wrap"><span class="axcp-bar-fill" style="width:'+barPct+'px"></span></span></td>';
h += '<td class="axcp-num">'+fmtKb(r.size)+'</td>';
h += '<td><span class="axcp-status st-'+st[0]+'">'+st[1]+'</span></td>';
h += '<td style="color:#666;white-space:nowrap;font-size:12px;">'+fmtDate(r.created)+'</td>';
h += '<td style="color:#666;white-space:nowrap;font-size:12px;">'+fmtDate(r.touched)+'</td>';
h += '<td class="axcp-num '+(r.revisions>20?'axcp-hi':'')+'">'+((r.revisions||0))+'</td>';
h += '<td class="axcp-num '+(r.watchers>0?'axcp-watch':'axcp-zero')+'">'+((r.watchers!=null&&r.watchers>0)? '👁 '+r.watchers : '0')+'</td>';
h += '<td class="axcp-num '+(r.links>5?'axcp-hi':'')+'">'+((r.links||0))+'</td>';
h += '</tr>';
});
h += '</tbody></table></div>';
root.innerHTML = h;
// Bind events
var searchEl = document.getElementById('axcp-s');
if (searchEl) searchEl.addEventListener('input', function(){ filterText = this.value.toLowerCase(); render(allData); });
var catEl = document.getElementById('axcp-cf');
if (catEl) catEl.addEventListener('change', function(){ filterCat = this.value; render(allData); });
var sfEl = document.getElementById('axcp-sf');
if (sfEl) sfEl.addEventListener('change', function() {
var sv = this.value;
if (!sv) { render(allData); return; }
// Filter by status
var prev = allData;
var tmp = allData.filter(function(r){ return wordStatus(r.words||0)[0] === sv; });
allData = prev;
// Temporary local render
var savedFilter = filterText;
filterText = '';
var fc = filterCat;
filterCat = '';
// just re-render with status override
root.querySelector('#axcp-reload') && void 0;
render(tmp);
allData = prev;
filterText = savedFilter;
filterCat = fc;
});
var reloadEl = document.getElementById('axcp-reload');
if (reloadEl) reloadEl.addEventListener('click', function(){ allData=[]; loadData(); });
document.querySelectorAll('table.axcp-tbl thead th').forEach(function(th){
th.addEventListener('click', function(){
var col = this.getAttribute('data-col');
if (sortCol === col) { sortDir *= -1; } else { sortCol = col; sortDir = 1; }
render(allData);
});
});
}
function setProgress(msg) {
var el = document.getElementById('axcp-prog');
if (el) el.textContent = msg;
}
async function loadData() {
root.innerHTML = '<div class="axcp-loading"><div style="font-size:28px;margin-bottom:16px;">⚙️</div><div>Loading article database…</div><div class="axcp-progress" id="axcp-prog">Step 1/4 — Fetching page list…</div></div>';
try {
// 1. Get all page IDs
var pages = [];
var apcontinue = null;
do {
var u = '/api.php?action=query&list=allpages&apnamespace=0&aplimit=500&format=json';
if (apcontinue) u += '&apcontinue=' + encodeURIComponent(apcontinue);
var r = await fetch(u); var d = await r.json();
pages = pages.concat(d.query.allpages);
apcontinue = d.continue ? d.continue.apcontinue : null;
} while (apcontinue);
setProgress('Step 2/4 — Fetching page info & categories for '+pages.length+' articles…');
var map = {};
pages.forEach(function(p){ map[p.pageid] = {title:p.title,cat:'',subcat:'',words:0,size:0,created:'',touched:'',revisions:0,watchers:null,links:0,status:''}; });
var ids = pages.map(function(p){return p.pageid;});
// 2. Info + categories + first revision (creation date)
for (var i=0; i<ids.length; i+=50) {
var chunk = ids.slice(i,i+50).join('|');
var r2 = await fetch('/api.php?action=query&pageids='+chunk+'&prop=info|categories|revisions&inprop=watchers&rvprop=timestamp&rvdir=newer&rvlimit=1&cllimit=15&format=json');
var d2 = await r2.json();
Object.keys(d2.query.pages).forEach(function(pid){
var pg = d2.query.pages[pid]; if(!map[pid])return;
map[pid].size = pg.length || 0;
map[pid].touched = pg.touched || '';
map[pid].watchers = (pg.watchers != null) ? pg.watchers : null;
if (pg.revisions && pg.revisions[0]) map[pid].created = pg.revisions[0].timestamp || '';
if (pg.categories) {
pg.categories.forEach(function(c){
var cn = c.title.replace('Category:','');
if (KNOWN_CATS.indexOf(cn) >= 0) { if(!map[pid].cat) map[pid].cat = cn; }
else if (cn.indexOf('stub')<0 && cn.indexOf('article')<0 && cn.length < 60) { if(!map[pid].subcat) map[pid].subcat = cn; }
});
}
});
}
setProgress('Step 3/4 — Counting revisions…');
// 3. Revision counts (rvlimit=max gives us up to 500; count them)
for (var j=0; j<ids.length; j+=20) {
var chunk2 = ids.slice(j,j+20).join('|');
var r3 = await fetch('/api.php?action=query&pageids='+chunk2+'&prop=revisions&rvprop=ids&rvlimit=max&format=json');
var d3 = await r3.json();
Object.keys(d3.query.pages).forEach(function(pid){
var pg = d3.query.pages[pid]; if(!map[pid])return;
map[pid].revisions = (pg.revisions||[]).length;
});
}
setProgress('Step 4/4 — Calculating word counts & inbound links…');
// 4. Word counts from wikitext + inbound links
for (var k=0; k<ids.length; k+=10) {
var chunk3 = ids.slice(k,k+10).join('|');
var r4 = await fetch('/api.php?action=query&pageids='+chunk3+'&prop=revisions|linkshere&rvprop=content&rvslots=main&lhnamespace=0&lhlimit=max&format=json');
var d4 = await r4.json();
Object.keys(d4.query.pages).forEach(function(pid){
var pg = d4.query.pages[pid]; if(!map[pid])return;
map[pid].links = (pg.linkshere||[]).length;
if (!pg.revisions||!pg.revisions[0]) return;
var slot = pg.revisions[0].slots ? pg.revisions[0].slots.main : pg.revisions[0];
var content = slot['*'] || slot.content || '';
var clean = content
.replace(/<!--[sS]*?-->/g,'')
.replace(/<[^>]+>/g,' ')
.replace(/[[File:[^]]+]]/gi,'')
.replace(/[[(?:[^]|]+|)?([^]]+)]]/g,'$1')
.replace(/{{[sS]*?}}/g,'')
.replace(/={2,}([^=]+)={2,}/g,'$1')
.replace(/[|!*#;:]/g,' ')
.replace(/https?://S+/g,'')
.replace(/s+/g,' ').trim();
map[pid].words = clean ? clean.split(/s+/).filter(function(w){return w.length>1;}).length : 0;
});
}
allData = Object.values(map);
render(allData);
} catch(e) {
root.innerHTML = '<div class="axcp-loading" style="color:#ff7070;">⚠️ Error: '+e.message+'<br><br><button onclick="location.reload()" style="background:#FF6600;border:none;color:#fff;padding:10px 20px;border-radius:8px;cursor:pointer;font-size:14px;">Retry</button></div>';
}
}
loadData();
});
})();