MediaWiki:Common.js: Difference between revisions
Add Users by Country counter widget for Admin page |
Improve Users by Country counter with better UI and empty state handling |
||
| Line 696: | Line 696: | ||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
if (mw.config.get('wgPageName') !== 'User:Admin') return; | if (mw.config.get('wgPageName') !== 'User:Admin') return; | ||
var langToCountry = { | var langToCountry = { | ||
'en': '🇺🇸 English-speaking', | 'en': '🇺🇸 English-speaking', 'de': '🇩🇪 Germany / German-speaking', | ||
'fr': '🇫🇷 France / French-speaking', 'es': '🇪🇸 Spain / Spanish-speaking', | |||
'fr': '🇫🇷 France / French-speaking', | 'pt': '🇵🇹 Portugal / Portuguese-speaking', 'it': '🇮🇹 Italy / Italian-speaking', | ||
'nl': '🇳🇱 Netherlands / Dutch-speaking', 'pl': '🇵🇱 Poland / Polish-speaking', | |||
'pt': '🇵🇹 Portugal / Portuguese-speaking', | 'ru': '🇷🇺 Russia / Russian-speaking', 'ja': '🇯🇵 Japan', | ||
'zh': '🇨🇳 China / Chinese-speaking', 'ar': '🇸🇦 Arabic-speaking', | |||
'nl': '🇳🇱 Netherlands / Dutch-speaking', | 'tr': '🇹🇷 Turkey / Turkish-speaking', 'sv': '🇸🇪 Sweden / Swedish-speaking', | ||
'da': '🇩🇰 Denmark / Danish-speaking', 'fi': '🇫🇮 Finland / Finnish-speaking', | |||
'ru': '🇷🇺 Russia / Russian-speaking', | 'nb': '🇳🇴 Norway / Norwegian-speaking', 'cs': '🇨🇿 Czech Republic', | ||
'hu': '🇭🇺 Hungary', 'ro': '🇷🇴 Romania', 'el': '🇬🇷 Greece / Greek-speaking', | |||
'zh': '🇨🇳 China / Chinese-speaking', | 'ko': '🇰🇷 Korea / Korean-speaking', 'uk': '🇺🇦 Ukraine / Ukrainian-speaking', | ||
'he': '🇮🇱 Israel / Hebrew-speaking', 'id': '🇮🇩 Indonesia', | |||
'tr': '🇹🇷 Turkey / Turkish-speaking', | 'vi': '🇻🇳 Vietnam', 'th': '🇹🇭 Thailand', 'hi': '🇮🇳 India / Hindi-speaking', | ||
'bn': '🇧🇩 Bangladesh / Bengali-speaking', 'fa': '🇮🇷 Iran / Persian-speaking' | |||
'da': '🇩🇰 Denmark / Danish-speaking', | |||
'nb': '🇳🇴 Norway / Norwegian-speaking', | |||
'hu': '🇭🇺 Hungary', | |||
'ko': '🇰🇷 Korea / Korean-speaking', | |||
'he': '🇮🇱 Israel / Hebrew-speaking', | |||
'vi': '🇻🇳 Vietnam', | |||
'bn': '🇧🇩 Bangladesh / Bengali-speaking', | |||
}; | }; | ||
function | function renderResults(langCounts, totalUsers) { | ||
var container = document.getElementById('country-user-counter'); | var container = document.getElementById('country-user-counter'); | ||
if (!container) return; | if (!container) return; | ||
var sorted = Object.keys(langCounts) | |||
.filter(function(l) { return langCounts[l] > 0; }) | |||
.sort(function(a, b) { return langCounts[b] - langCounts[a]; }); | |||
var html = '<style>'; | |||
html += '#cuc-wrap{font-family:sans-serif}'; | |||
html += '#cuc-stats{display:flex;gap:20px;margin-bottom:14px;flex-wrap:wrap}'; | |||
html += '.cuc-stat-card{background:#222;border:1px solid #444;border-radius:6px;padding:10px 18px;min-width:120px;text-align:center}'; | |||
html += '.cuc-stat-num{font-size:1.8em;font-weight:bold;color:#e07000}'; | |||
html += '.cuc-stat-lbl{font-size:0.8em;color:#aaa;margin-top:2px}'; | |||
html += '#cuc-table{width:100%;border-collapse:collapse;margin-top:4px}'; | |||
html += '#cuc-table th{background:#2a2a2a;color:#e07000;padding:8px 12px;text-align:left;border:1px solid #444;font-size:0.9em}'; | |||
html += '#cuc-table td{padding:7px 12px;border:1px solid #2e2e2e;color:#ddd;font-size:0.9em}'; | |||
html += '#cuc-table tr:nth-child(even) td{background:#1c1c1c}'; | |||
html += '#cuc-table tr:hover td{background:#252525}'; | |||
html += '.cuc-bar-wrap{background:#111;border-radius:3px;height:14px;overflow:hidden}'; | |||
html += '.cuc-bar{height:14px;background:linear-gradient(90deg,#e07000,#ff9a00);border-radius:3px;transition:width 0.4s ease}'; | |||
html += '.cuc-no-data{background:#1a1a1a;border:1px dashed #444;border-radius:6px;padding:16px;color:#aaa;line-height:1.6}'; | |||
html += '.cuc-no-data code{background:#222;padding:2px 6px;border-radius:3px;color:#e07000;font-size:0.9em}'; | |||
html += '#cuc-timestamp{color:#555;font-size:0.8em;margin-top:10px}'; | |||
html += '</style>'; | |||
html += '<div id="cuc-wrap">'; | |||
// Stats cards | |||
html += '<div id="cuc-stats">'; | |||
html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + totalUsers + '</div><div class="cuc-stat-lbl">Total Users</div></div>'; | |||
html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + sorted.length + '</div><div class="cuc-stat-lbl">Countries / Regions</div></div>'; | |||
var locatedUsers = sorted.reduce(function(sum, l) { return sum + langCounts[l]; }, 0); | |||
html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + locatedUsers + '</div><div class="cuc-stat-lbl">Users with Location</div></div>'; | |||
html += '</div>'; | |||
if (sorted.length === 0) { | |||
var | html += '<div class="cuc-no-data">'; | ||
html += '<strong>📍 No country data available yet.</strong><br>'; | |||
html += 'This counter tracks users by country using the <strong>Babel extension</strong> language categories.<br>'; | |||
html += 'Users can declare their language/country by adding babel tags to their user page:<br><br>'; | |||
html += '<code>{{#babel:en|de|fr}}</code><br><br>'; | |||
html += 'Once users add babel tags, this counter will automatically display their country distribution.<br>'; | |||
html += 'Currently tracking <strong>' + totalUsers + ' registered users</strong> on this wiki.'; | |||
html += '</div>'; | |||
} else { | |||
var maxCount = langCounts[sorted[0]] || 1; | |||
html += '<table id="cuc-table"><thead><tr>'; | |||
html += '<th style="width:40px">#</th>'; | |||
html += '<th>Country / Region</th>'; | |||
html += '<th style="width:70px;text-align:center">Users</th>'; | |||
html += '<th style="width:200px">Distribution</th>'; | |||
html += '</tr></thead><tbody>'; | |||
sorted.forEach(function(lang, idx) { | |||
var count = langCounts[lang]; | |||
var label = langToCountry[lang] || ('🌐 ' + lang.toUpperCase()); | |||
var pct = Math.round((count / locatedUsers) * 100); | |||
var barWidth = Math.round((count / maxCount) * 180); | |||
html += '<tr>'; | |||
html += '<td style="color:#666;text-align:center">' + (idx + 1) + '</td>'; | |||
html += '<td>' + label + '</td>'; | |||
html += '<td style="text-align:center;font-weight:bold;color:#e07000">' + count + '</td>'; | |||
html += '<td><div class="cuc-bar-wrap"><div class="cuc-bar" style="width:' + barWidth + 'px"></div></div></td>'; | |||
html += '</tr>'; | |||
}); | }); | ||
html += '</tbody></table>'; | |||
} | } | ||
html += '<div id="cuc-timestamp">Last updated: ' + new Date().toLocaleString() + '</div>'; | |||
html += '</div>'; | |||
container.innerHTML = html; | |||
} | |||
function buildCounter() { | |||
var container = document.getElementById('country-user-counter'); | |||
if (!container) return; | |||
container.innerHTML = '<p style="color:#aaa;font-style:italic">⏳ Loading user statistics by country...</p>'; | |||
var allUsers = []; | |||
var langCounts = {}; | |||
var langs = Object.keys(langToCountry); | |||
function fetchUsers(aufrom) { | |||
var params = { action: 'query', list: 'allusers', aulimit: 500, format: 'json' }; | |||
if (aufrom) params.aufrom = aufrom; | |||
return $.getJSON(mw.util.wikiScript('api'), params).then(function(data) { | |||
var users = (data.query && data.query.allusers) || []; | |||
allUsers = allUsers.concat(users); | |||
if (data['continue'] && data['continue'].aufrom) return fetchUsers(data['continue'].aufrom); | |||
}); | }); | ||
} | } | ||
function fetchLangCat(lang) { | |||
function | return $.getJSON(mw.util.wikiScript('api'), { | ||
action: 'query', list: 'categorymembers', | |||
cmtitle: 'Category:User_' + lang, | |||
cmtype: 'page', cmnamespace: 2, cmlimit: 500, format: 'json' | |||
}).then(function(data) { | |||
var m = (data.query && data.query.categorymembers) || []; | |||
if (m.length > 0) langCounts[lang] = (langCounts[lang] || 0) + m.length; | |||
}).catch(function() {}); | |||
} | } | ||
fetchUsers().then(function() { | fetchUsers().then(function() { | ||
return | var d = $.Deferred().resolve().promise(); | ||
}).then(function( | langs.forEach(function(lang) { d = d.then(function() { return fetchLangCat(lang); }); }); | ||
return d; | |||
}).then(function() { | |||
renderResults(langCounts, allUsers.length); | renderResults(langCounts, allUsers.length); | ||
}).catch(function( | }).catch(function(e) { | ||
container.innerHTML = '<p style="color:#f66 | container.innerHTML = '<p style="color:#f66">⚠️ Error loading data: ' + String(e) + '</p>'; | ||
}); | }); | ||
} | } | ||
$(document).ready(function() { buildCounter(); }); | |||
$(document).ready(function() { | |||
})(); | })(); | ||
// ===================================================== | // ===================================================== | ||
// End: Users by Country Counter | // End: Users by Country Counter | ||
// ===================================================== | // ===================================================== | ||