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
// =====================================================