Article Overview: Difference between revisions

Created page with "<div id="article-overview-wrapper"> <div style="margin-bottom:12px;"> <button id="download-csv-btn" style="background-color:#e07b00;color:#fff;padding:8px 18px;border:none;border-radius:4px;font-size:14px;font-weight:bold;cursor:pointer;">⬇ Download CSV</button> <span id="overview-status" style="margin-left:14px;font-size:13px;color:#aaa;"></span> </div> <table id="article-overview-table" style="width:100%;border-collapse:collapse;font-size:13px;"> <thead> <tr style=..."
 
No edit summary
Line 1: Line 1:
<div id="article-overview-wrapper">
== Article Overview ==


<div style="margin-bottom:12px;">
This page displays all wiki articles organised by category and subcategory, including word count and focus area. Use the button below to download the full list as a CSV file.
<button id="download-csv-btn" style="background-color:#e07b00;color:#fff;padding:8px 18px;border:none;border-radius:4px;font-size:14px;font-weight:bold;cursor:pointer;">⬇ Download CSV</button>
 
<span id="overview-status" style="margin-left:14px;font-size:13px;color:#aaa;"></span>
<div id="article-overview-wrapper" style="margin-top:16px;">
 
<div style="margin-bottom:14px;">
<button id="download-csv-btn" style="background-color:#e07b00;color:#fff;padding:9px 20px;border:none;border-radius:5px;font-size:14px;font-weight:bold;cursor:pointer;letter-spacing:0.3px;">⬇ Download CSV</button>
<span id="overview-status" style="margin-left:14px;font-size:13px;color:#aaa;vertical-align:middle;"></span>
</div>
</div>


<table id="article-overview-table" style="width:100%;border-collapse:collapse;font-size:13px;">
<table id="article-overview-table" style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<thead>
<tr style="background-color:#2a2a2a;color:#f0a500;">
<tr style="background-color:#2a2a2a;color:#f0a500;text-align:left;">
<th style="padding:8px 10px;border:1px solid #444;text-align:left;">Category</th>
<th style="padding:9px 12px;border:1px solid #444;">Category</th>
<th style="padding:8px 10px;border:1px solid #444;text-align:left;">Subcategory</th>
<th style="padding:9px 12px;border:1px solid #444;">Subcategory</th>
<th style="padding:8px 10px;border:1px solid #444;text-align:left;">Title</th>
<th style="padding:9px 12px;border:1px solid #444;">Title</th>
<th style="padding:8px 10px;border:1px solid #444;text-align:right;">Words</th>
<th style="padding:9px 12px;border:1px solid #444;text-align:right;">Words</th>
<th style="padding:8px 10px;border:1px solid #444;text-align:left;">Focus</th>
<th style="padding:9px 12px;border:1px solid #444;">Focus</th>
</tr>
</tr>
</thead>
</thead>
<tbody id="article-overview-body">
<tbody id="article-overview-body">
<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">Loading data…</td></tr>
<tr><td colspan="5" style="padding:12px;text-align:center;color:#aaa;font-style:italic;">Loading articles… please wait.</td></tr>
</tbody>
</tbody>
</table>
</table>


</div>
</div>
<script>
(function() {
  var apiBase = '/api.php';
  var allRows = [];
  function fetchAllCategories(continueParam, accumulated, callback) {
    var url = apiBase + '?action=query&list=allcategories&aclimit=500&format=json&origin=*';
    if (continueParam) url += '&accontinue=' + encodeURIComponent(continueParam);
    fetch(url)
      .then(function(r){ return r.json(); })
      .then(function(data) {
        var cats = data.query && data.query.allcategories ? data.query.allcategories : [];
        cats.forEach(function(c){ accumulated.push(c['*']); });
        if (data.continue && data.continue.accontinue) {
          fetchAllCategories(data.continue.accontinue, accumulated, callback);
        } else {
          callback(accumulated);
        }
      })
      .catch(function(){ callback(accumulated); });
  }
  function getCategoryMembers(catName, type, continueParam, accumulated, callback) {
    var cmtype = type || 'page|subcat';
    var url = apiBase + '?action=query&list=categorymembers&cmtitle=Category:' + encodeURIComponent(catName) + '&cmlimit=500&cmtype=' + cmtype + '&format=json&origin=*';
    if (continueParam) url += '&cmcontinue=' + encodeURIComponent(continueParam);
    fetch(url)
      .then(function(r){ return r.json(); })
      .then(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, type, data.continue.cmcontinue, accumulated, callback);
        } else {
          callback(accumulated);
        }
      })
      .catch(function(){ callback(accumulated); });
  }
  function getPageWordCount(pageTitle, callback) {
    var url = apiBase + '?action=query&titles=' + encodeURIComponent(pageTitle) + '&prop=revisions&rvprop=content&rvslots=main&format=json&origin=*';
    fetch(url)
      .then(function(r){ return r.json(); })
      .then(function(data) {
        var pages = data.query && data.query.pages ? data.query.pages : {};
        var pageData = Object.values(pages)[0];
        var content = pageData && pageData.revisions && pageData.revisions[0] && pageData.revisions[0].slots && pageData.revisions[0].slots.main && pageData.revisions[0].slots.main['*'] ? pageData.revisions[0].slots.main['*'] : '';
        var text = content.replace(/\[\[.*?\]\]/g,'$1').replace(/{{.*?}}/gs,'').replace(/<.*?>/g,'').replace(/[^a-zA-ZäöüÄÖÜß\s]/g,' ');
        var words = text.trim().split(/\s+/).filter(function(w){ return w.length > 0; }).length;
        callback(words);
      })
      .catch(function(){ callback(0); });
  }
  function getPageCategories(pageTitle, callback) {
    var url = apiBase + '?action=query&titles=' + encodeURIComponent(pageTitle) + '&prop=categories&cllimit=50&format=json&origin=*';
    fetch(url)
      .then(function(r){ return r.json(); })
      .then(function(data) {
        var pages = data.query && data.query.pages ? data.query.pages : {};
        var pageData = Object.values(pages)[0];
        var cats = pageData && pageData.categories ? pageData.categories.map(function(c){ return c.title.replace('Category:',''); }) : [];
        callback(cats);
      })
      .catch(function(){ callback([]); });
  }
  function buildTable() {
    var status = document.getElementById('overview-status');
    status.textContent = 'Fetching categories…';
    fetch(apiBase + '?action=query&list=allcategories&aclimit=500&format=json&origin=*')
      .then(function(r){ return r.json(); })
      .then(function(data) {
        var topCats = data.query && data.query.allcategories ? data.query.allcategories.map(function(c){ return c['*']; }) : [];
        var rows = [];
        var pending = 0;
        var totalCats = topCats.length;
        var processed = 0;
        if (totalCats === 0) {
          document.getElementById('article-overview-body').innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No categories found.</td></tr>';
          status.textContent = '';
          return;
        }
        function checkDone() {
          processed++;
          status.textContent = 'Loading… (' + processed + '/' + totalCats + ' categories)';
          if (processed === totalCats) {
            rows.sort(function(a,b){
              if (a.category < b.category) return -1;
              if (a.category > b.category) return 1;
              if (a.sub < b.sub) return -1;
              if (a.sub > b.sub) return 1;
              return a.title.localeCompare(b.title);
            });
            allRows = rows;
            renderTable(rows);
            status.textContent = rows.length + ' articles 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 subPending = 0;
            function trySubDone() {
              subPending--;
              if (subPending <= 0) checkDone();
            }
            // Direct pages in this category (no subcat)
            pages.forEach(function(page) {
              subPending++;
              var focus = catName;
              rows.push({ category: catName, sub: '—', title: page.title, words: '…', focus: focus, _title: page.title });
            });
            // Pages in subcategories
            subcats.forEach(function(subcat) {
              subPending++;
              var subName = subcat.title.replace('Category:','');
              getCategoryMembers(subName, 'page', null, [], function(subPages) {
                subPages.filter(function(m){ return m.ns === 0; }).forEach(function(page) {
                  subPending++;
                  rows.push({ category: catName, sub: subName, title: page.title, words: '…', focus: catName, _title: page.title });
                });
                trySubDone();
              });
            });
            if (subPending === 0) checkDone();
          });
        });
      })
      .catch(function() {
        document.getElementById('article-overview-body').innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#f00;">Error loading data.</td></tr>';
      });
  }
  function renderTable(rows) {
    var tbody = document.getElementById('article-overview-body');
    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 ? '#1a1a1a' : '#222';
      html += '<tr style="background:' + bg + ';">';
      html += '<td style="padding:6px 10px;border:1px solid #333;">' + escHtml(row.category) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #333;">' + escHtml(row.sub) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #333;"><a href="/wiki/' + encodeURIComponent(row.title.replace(/ /g,'_')) + '">' + escHtml(row.title) + '</a></td>';
      html += '<td style="padding:6px 10px;border:1px solid #333;text-align:right;">' + escHtml(String(row.words)) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #333;">' + escHtml(row.focus) + '</td>';
      html += '</tr>';
    });
    tbody.innerHTML = html;
  }
  function escHtml(str) {
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }
  function downloadCSV() {
    var rows = allRows;
    if (!rows || rows.length === 0) { alert('No data to download yet. Please wait for the table to load.'); return; }
    var csv = 'Category,Subcategory,Title,Words,Focus\n';
    rows.forEach(function(row) {
      csv += [row.category, row.sub, row.title, row.words, row.focus].map(function(v){
        return '"' + String(v).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.addEventListener('DOMContentLoaded', function() {
    var btn = document.getElementById('download-csv-btn');
    if (btn) btn.addEventListener('click', downloadCSV);
    buildTable();
  });
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function() {
      var btn = document.getElementById('download-csv-btn');
      if (btn) btn.addEventListener('click', downloadCSV);
      buildTable();
    });
  } else {
    var btn = document.getElementById('download-csv-btn');
    if (btn) btn.addEventListener('click', downloadCSV);
    buildTable();
  }
})();
</script>