MediaWiki:Common.js: Difference between revisions
Add permanent favicon |
Add Users by Country counter widget for Admin page |
||
| Line 689: | Line 689: | ||
document.head.appendChild(link); | document.head.appendChild(link); | ||
})(); | })(); | ||
// ===================================================== | |||
// Users by Country Counter - Admin Page Widget | |||
// ===================================================== | |||
(function() { | |||
'use strict'; | |||
// Only run on the User:Admin page | |||
if (mw.config.get('wgPageName') !== 'User:Admin') return; | |||
// Language code to country/region mapping | |||
var langToCountry = { | |||
'en': '🇺🇸 English-speaking', | |||
'de': '🇩🇪 Germany / German-speaking', | |||
'fr': '🇫🇷 France / French-speaking', | |||
'es': '🇪🇸 Spain / Spanish-speaking', | |||
'pt': '🇵🇹 Portugal / Portuguese-speaking', | |||
'it': '🇮🇹 Italy / Italian-speaking', | |||
'nl': '🇳🇱 Netherlands / Dutch-speaking', | |||
'pl': '🇵🇱 Poland / Polish-speaking', | |||
'ru': '🇷🇺 Russia / Russian-speaking', | |||
'ja': '🇯🇵 Japan', | |||
'zh': '🇨🇳 China / Chinese-speaking', | |||
'ar': '🇸🇦 Arabic-speaking', | |||
'tr': '🇹🇷 Turkey / Turkish-speaking', | |||
'sv': '🇸🇪 Sweden / Swedish-speaking', | |||
'da': '🇩🇰 Denmark / Danish-speaking', | |||
'fi': '🇫🇮 Finland / Finnish-speaking', | |||
'nb': '🇳🇴 Norway / Norwegian-speaking', | |||
'cs': '🇨🇿 Czech Republic', | |||
'hu': '🇭🇺 Hungary', | |||
'ro': '🇷🇴 Romania', | |||
'el': '🇬🇷 Greece / Greek-speaking', | |||
'ko': '🇰🇷 Korea / Korean-speaking', | |||
'uk': '🇺🇦 Ukraine / Ukrainian-speaking', | |||
'he': '🇮🇱 Israel / Hebrew-speaking', | |||
'id': '🇮🇩 Indonesia', | |||
'vi': '🇻🇳 Vietnam', | |||
'th': '🇹🇭 Thailand', | |||
'hi': '🇮🇳 India / Hindi-speaking', | |||
'bn': '🇧🇩 Bangladesh / Bengali-speaking', | |||
'fa': '🇮🇷 Iran / Persian-speaking' | |||
}; | |||
function buildCounter() { | |||
var container = document.getElementById('country-user-counter'); | |||
if (!container) return; | |||
container.innerHTML = '<p style="color:#aaa; font-style:italic;">⏳ Fetching user data...</p>'; | |||
// Step 1: Get all users | |||
var allUsers = []; | |||
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); | |||
} | |||
}); | |||
} | |||
// Step 2: For each user, get their language preference | |||
function fetchUserLanguages() { | |||
var langCounts = {}; | |||
var totalFetched = 0; | |||
var batchSize = 50; | |||
var userNames = allUsers.map(function(u) { return u.name; }); | |||
var batches = []; | |||
for (var i = 0; i < userNames.length; i += batchSize) { | |||
batches.push(userNames.slice(i, i + batchSize)); | |||
} | |||
function processBatch(idx) { | |||
if (idx >= batches.length) { | |||
renderResults(langCounts, allUsers.length); | |||
return; | |||
} | |||
var batch = batches[idx]; | |||
return $.getJSON(mw.util.wikiScript('api'), { | |||
action: 'query', | |||
list: 'users', | |||
ususers: batch.join('|'), | |||
usprop: 'groups', | |||
format: 'json' | |||
}).then(function() { | |||
// Language isn't directly available via users list | |||
// Use userinfo for current user only, so we fallback to babel categories | |||
processBatch(idx + 1); | |||
}); | |||
} | |||
processBatch(0); | |||
} | |||
// Step 3: Get language data from Babel extension categories | |||
function fetchBabelData() { | |||
var langCounts = {}; | |||
var promises = []; | |||
// Fetch members of language categories (e.g., Category:User_en, Category:User_de, etc.) | |||
var langs = Object.keys(langToCountry); | |||
function fetchLangCategory(lang, offset) { | |||
return $.getJSON(mw.util.wikiScript('api'), { | |||
action: 'query', | |||
list: 'categorymembers', | |||
cmtitle: 'Category:User_' + lang, | |||
cmtype: 'page', | |||
cmnamespace: 2, | |||
cmlimit: 500, | |||
cmcontinue: offset || undefined, | |||
format: 'json' | |||
}).then(function(data) { | |||
var members = (data.query && data.query.categorymembers) || []; | |||
if (!langCounts[lang]) langCounts[lang] = 0; | |||
langCounts[lang] += members.length; | |||
if (data.continue && data.continue.cmcontinue) { | |||
return fetchLangCategory(lang, data.continue.cmcontinue); | |||
} | |||
}).catch(function() { | |||
// Category may not exist, ignore | |||
}); | |||
} | |||
var chain = $.Deferred().resolve(); | |||
langs.forEach(function(lang) { | |||
chain = chain.then(function() { | |||
return fetchLangCategory(lang); | |||
}); | |||
}); | |||
return chain.then(function() { | |||
return langCounts; | |||
}); | |||
} | |||
// Render the results table | |||
function renderResults(langCounts, totalUsers) { | |||
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 += '#country-counter-table { width:100%; border-collapse:collapse; margin-top:10px; }'; | |||
html += '#country-counter-table th { background:#2a2a2a; color:#e07000; padding:8px 12px; text-align:left; border:1px solid #444; }'; | |||
html += '#country-counter-table td { padding:7px 12px; border:1px solid #333; color:#ddd; }'; | |||
html += '#country-counter-table tr:nth-child(even) td { background:#1a1a1a; }'; | |||
html += '#country-counter-table tr:hover td { background:#252525; }'; | |||
html += '.bar-cell { width:40%; }'; | |||
html += '.bar { height:14px; background:#e07000; border-radius:3px; display:inline-block; min-width:2px; }'; | |||
html += '</style>'; | |||
html += '<p style="color:#ccc; margin-bottom:8px;">📊 <strong>Total registered users:</strong> ' + totalUsers + '</p>'; | |||
if (sorted.length === 0) { | |||
html += '<p style="color:#aaa;">No Babel language categories found. Users can add their language to their profile using <code>{{#babel:en}}</code> on their user page.</p>'; | |||
html += '<p style="color:#888; font-size:0.9em;">💡 Tip: To enable country tracking, ask users to add babel tags to their user pages.</p>'; | |||
} else { | |||
var maxCount = langCounts[sorted[0]] || 1; | |||
html += '<table id="country-counter-table"><thead><tr><th>#</th><th>Country / Region</th><th>Users</th><th class="bar-cell">Distribution</th></tr></thead><tbody>'; | |||
sorted.forEach(function(lang, idx) { | |||
var count = langCounts[lang]; | |||
var label = langToCountry[lang] || lang.toUpperCase(); | |||
var barWidth = Math.round((count / maxCount) * 200); | |||
html += '<tr>'; | |||
html += '<td>' + (idx + 1) + '</td>'; | |||
html += '<td>' + label + '</td>'; | |||
html += '<td style="text-align:center; font-weight:bold;">' + count + '</td>'; | |||
html += '<td class="bar-cell"><span class="bar" style="width:' + barWidth + 'px"></span></td>'; | |||
html += '</tr>'; | |||
}); | |||
html += '</tbody></table>'; | |||
} | |||
html += '<p style="color:#666; font-size:0.85em; margin-top:10px;">ℹ️ Based on Babel language categories. Last loaded: ' + new Date().toLocaleString() + '</p>'; | |||
container.innerHTML = html; | |||
} | |||
// Main flow: fetch users, then babel categories | |||
fetchUsers().then(function() { | |||
return fetchBabelData(); | |||
}).then(function(langCounts) { | |||
renderResults(langCounts, allUsers.length); | |||
}).catch(function(err) { | |||
container.innerHTML = '<p style="color:#f66;">Error loading data: ' + (err && err.message ? err.message : String(err)) + '</p>'; | |||
}); | |||
} | |||
// Run after DOM is ready | |||
$(document).ready(function() { | |||
buildCounter(); | |||
}); | |||
})(); | |||
// ===================================================== | |||
// End: Users by Country Counter | |||
// ===================================================== | |||