|
|
| (2 intermediate revisions by the same user not shown) |
| 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. The table and download button are generated automatically by JavaScript. |
| <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;"> | | <div id="article-overview-wrapper" style="margin-top:16px;"></div> |
| <thead>
| |
| <tr style="background-color:#2a2a2a;color:#f0a500;">
| |
| <th style="padding:8px 10px;border:1px solid #444;text-align:left;">Category</th>
| |
| <th style="padding:8px 10px;border:1px solid #444;text-align:left;">Subcategory</th>
| |
| <th style="padding:8px 10px;border:1px solid #444;text-align:left;">Title</th>
| |
| <th style="padding:8px 10px;border:1px solid #444;text-align:right;">Words</th>
| |
| <th style="padding:8px 10px;border:1px solid #444;text-align:left;">Focus</th>
| |
| </tr>
| |
| </thead>
| |
| <tbody id="article-overview-body">
| |
| <tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">Loading data…</td></tr>
| |
| </tbody>
| |
| </table>
| |
| | |
| </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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
| |
| }
| |
| | |
| 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>
| |