MediaWiki:Common.js: Difference between revisions

Improve Users by Country counter with better UI and empty state handling
No edit summary
Line 835: Line 835:
// End: Users by Country Counter
// End: Users by Country Counter
// =====================================================
// =====================================================
// ===== Article Overview Page - Category/Subcategory/Title/Words/Focus Table =====
(function() {
  if (mw.config.get('wgPageName') !== 'Article_Overview') return;
  var apiBase = mw.util.wikiScript('api');
  var allRows = [];
  function getCategoryMembers(catName, cmtype, continueParam, accumulated, callback) {
    var params = {
      action: 'query', list: 'categorymembers',
      cmtitle: 'Category:' + catName,
      cmlimit: 500, cmtype: cmtype, format: 'json'
    };
    if (continueParam) params.cmcontinue = continueParam;
    $.getJSON(apiBase, params).done(function(data) {
      var members = (data.query && data.query.categorymembers) ? data.query.categorymembers : [];
      members.forEach(function(m){ accumulated.push(m); });
      if (data.continue && data.continue.cmcontinue) {
        getCategoryMembers(catName, cmtype, data.continue.cmcontinue, accumulated, callback);
      } else {
        callback(accumulated);
      }
    }).fail(function(){ callback(accumulated); });
  }
  function escHtml(str) {
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }
  function renderTable(rows) {
    var tbody = document.getElementById('article-overview-body');
    if (!tbody) return;
    if (!rows || rows.length === 0) {
      tbody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No articles found.</td></tr>';
      return;
    }
    var html = '';
    rows.forEach(function(row, i) {
      var bg = i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.07)';
      html += '<tr style="background:' + bg + ';">';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.category) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.sub) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;"><a href="' + mw.util.getUrl(row.title) + '">' + escHtml(row.title) + '</a></td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;text-align:right;">' + escHtml(String(row.words)) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.focus) + '</td>';
      html += '</tr>';
    });
    tbody.innerHTML = html;
  }
  function buildTable() {
    var status = document.getElementById('overview-status');
    if (status) status.textContent = 'Fetching categories…';
    $.getJSON(apiBase, { action:'query', list:'allcategories', aclimit:500, format:'json' })
      .done(function(data) {
        var topCats = (data.query && data.query.allcategories) ? data.query.allcategories.map(function(c){ return c['*']; }) : [];
        var rows = [];
        var totalCats = topCats.length;
        var processed = 0;
        if (totalCats === 0) {
          var tbody = document.getElementById('article-overview-body');
          if (tbody) tbody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No categories found.</td></tr>';
          if (status) status.textContent = '';
          return;
        }
        function checkDone() {
          processed++;
          if (status) status.textContent = 'Loading… (' + processed + '/' + totalCats + ' categories processed)';
          if (processed === totalCats) {
            rows.sort(function(a,b){
              var ca = a.category.toLowerCase(), cb = b.category.toLowerCase();
              if (ca < cb) return -1; if (ca > cb) return 1;
              var sa = a.sub.toLowerCase(), sb = b.sub.toLowerCase();
              if (sa < sb) return -1; if (sa > sb) return 1;
              return a.title.localeCompare(b.title);
            });
            allRows = rows;
            renderTable(rows);
            if (status) status.textContent = rows.length + ' article entries loaded.';
          }
        }
        topCats.forEach(function(catName) {
          getCategoryMembers(catName, 'page|subcat', null, [], function(members) {
            var pages = members.filter(function(m){ return m.ns === 0; });
            var subcats = members.filter(function(m){ return m.ns === 14; });
            if (pages.length === 0 && subcats.length === 0) {
              checkDone(); return;
            }
            var subTotal = pages.length + subcats.length;
            var subProcessed = 0;
            function subDone() {
              subProcessed++;
              if (subProcessed >= subTotal) checkDone();
            }
            pages.forEach(function(page) {
              rows.push({ category: catName, sub: '—', title: page.title, words: 'n/a', focus: catName });
              subDone();
            });
            subcats.forEach(function(subcat) {
              var subName = subcat.title.replace('Category:','');
              getCategoryMembers(subName, 'page', null, [], function(subPages) {
                subPages.filter(function(m){ return m.ns === 0; }).forEach(function(page) {
                  rows.push({ category: catName, sub: subName, title: page.title, words: 'n/a', focus: catName });
                });
                subDone();
              });
            });
          });
        });
      })
      .fail(function() {
        var tbody = document.getElementById('article-overview-body');
        if (tbody) tbody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#f55;">Error loading data. Check API access.</td></tr>';
      });
  }
  function downloadCSV() {
    if (!allRows || allRows.length === 0) {
      alert('No data to download yet. Please wait for the table to finish loading.');
      return;
    }
    var csv = 'Category,Subcategory,Title,Words,Focus\n';
    allRows.forEach(function(row) {
      csv += ['category','sub','title','words','focus'].map(function(k){
        return '"' + String(row[k]).replace(/"/g,'""') + '"';
      }).join(',') + '\n';
    });
    var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url; a.download = 'article-overview.csv';
    document.body.appendChild(a); a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }
  $(document).ready(function() {
    var btn = document.getElementById('download-csv-btn');
    if (btn) btn.addEventListener('click', downloadCSV);
    buildTable();
  });
})();