MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 608: Zeile 608:




/* --- Whisky Top-5 (rekursiv über Unterkategorien) ----------------------- */
/* --- Whisky Top-5 (mehrere Root-Kategorien, rekursiv, ES5) ---------------- */
mw.loader.using(['mediawiki.api']).then(function () {
mw.loader.using(['mediawiki.api']).then(function () {


   // kleines Hilfs-get ohne optional chaining
   // Utility: sicheres get(obj, ['a','b'])
   function get(obj, path) {
   function get(obj, path) {
     var cur = obj, i;
     var cur = obj, i;
Zeile 638: Zeile 638:
   }
   }


   // Holt Artikel (NS0) aus einer Kategorie inkl. ALLER Unterkategorien (BFS), bis limit erreicht ist
   // Eine Kategorie (inkl. Subkategorien) einsammeln – BFS
   function fetchCategoryMembersRecursive(rootCat, limit) {
   function fetchCategoryMembersRecursiveSingle(rootCat, limit, outSet, pages) {
     var api = new mw.Api();
     var api = new mw.Api();
     var visitedCats = {};
     var visited = {};
     var queue = ['Kategorie:' + rootCat]; // Startkategorie (mit Präfix)
     var queue = ['Kategorie:' + rootCat];
    var pages = [];                        // { pageid, title }


     function fetchOneCat(catTitle, cmcontinue) {
     function fetchOne(catTitle, cmcontinue) {
       var params = {
       var params = {
         action: 'query',
         action: 'query',
         list: 'categorymembers',
         list: 'categorymembers',
         cmtitle: catTitle,
         cmtitle: catTitle,
         cmnamespace: '0|14',   // 0=Seiten, 14=Kategorien
         cmnamespace: '0|14',       // Artikel + Kategorien
         cmtype: 'page|subcat',
         cmtype: 'page|subcat',
         cmlimit: Math.min(200, limit),
         cmlimit: Math.min(200, limit),
Zeile 659: Zeile 658:
       return api.get(params).then(function (data) {
       return api.get(params).then(function (data) {
         var cms = get(data, ['query', 'categorymembers']) || [];
         var cms = get(data, ['query', 'categorymembers']) || [];
         var i;
         var i, it;
         for (i = 0; i < cms.length; i++) {
         for (i = 0; i < cms.length; i++) {
           var item = cms[i];
           it = cms[i];
           if (item.ns === 0) {
           if (it.ns === 0) { // Artikel
             if (pages.length < limit) pages.push({ pageid: String(item.pageid), title: item.title });
            var pid = String(it.pageid);
           } else if (item.ns === 14) {
             if (!outSet[pid] && pages.length < limit) {
             var subcatTitle = item.title; // enthält schon "Kategorie:"
              outSet[pid] = true;
             if (!visitedCats[subcatTitle]) {
              pages.push({ pageid: pid, title: it.title });
               visitedCats[subcatTitle] = true;
            }
               queue.push(subcatTitle);
           } else if (it.ns === 14) { // Subkat
             var sub = it.title;
             if (!visited[sub]) {
               visited[sub] = true;
               queue.push(sub);
             }
             }
           }
           }
         }
         }
         var cont = get(data, ['continue', 'cmcontinue']);
         var cont = get(data, ['continue', 'cmcontinue']);
         if (cont && pages.length < limit) {
         if (cont && pages.length < limit) return fetchOne(catTitle, cont);
          return fetchOneCat(catTitle, cont);
        }
         return null;
         return null;
       });
       });
Zeile 682: Zeile 682:


     function loop() {
     function loop() {
       if (pages.length >= limit || queue.length === 0) {
       if (pages.length >= limit || !queue.length) return Promise.resolve();
      var next = queue.shift();
      if (visited[next]) return loop();
      visited[next] = true;
      return fetchOne(next).then(loop);
    }
 
    return loop();
  }
 
  // Mehrere Root-Kategorien verarbeiten
  function fetchCategoryMembersRecursiveMulti(rootCats, limit) {
    var pages = [];              // {pageid, title}
    var outSet = {};              // dedupe per PageID
    var i = 0;
 
    function nextCat() {
      if (i >= rootCats.length || pages.length >= limit) {
         return Promise.resolve(pages);
         return Promise.resolve(pages);
       }
       }
       var nextCat = queue.shift();
       var cat = rootCats[i++];
       if (visitedCats[nextCat]) return loop();
       if (!cat) return nextCat();
      visitedCats[nextCat] = true;
       return fetchCategoryMembersRecursiveSingle(cat, limit, outSet, pages).then(nextCat);
       return fetchOneCat(nextCat).then(loop);
     }
     }


     return loop();
     return nextCat();
   }
   }


   // Holt für pageIds die RatePage-Daten zu einem Contest
   // RatePage-Daten für einen Contest holen (pageIds batched)
  // includeHidden=true => zählt auch, wenn canSee=0 (nur für Ranking)
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
     var api = new mw.Api();
     var api = new mw.Api();
     var res = {}; // pageId -> { avg, total }
     var res = {}; // pageId -> {avg, total}
     var i, chunk = 50, chunks = [];
     var i, chunk = 50, chunks = [];
     for (i = 0; i < pageIds.length; i += chunk) chunks.push(pageIds.slice(i, i + chunk));
     for (i = 0; i < pageIds.length; i += chunk) chunks.push(pageIds.slice(i, i + chunk));
Zeile 705: Zeile 720:
       if (idx >= chunks.length) return Promise.resolve(res);
       if (idx >= chunks.length) return Promise.resolve(res);
       var ids = chunks[idx];
       var ids = chunks[idx];
       return api.get({
       return api.get({
         action: 'query',
         action: 'query',
Zeile 719: Zeile 733:
           if (!res[pid]) res[pid] = { avg: null, total: 0 };
           if (!res[pid]) res[pid] = { avg: null, total: 0 };
           if (pr && (includeHidden || !('canSee' in pr) || pr.canSee !== 0)) {
           if (pr && (includeHidden || !('canSee' in pr) || pr.canSee !== 0)) {
             hist = pr.pageRating || {};
             hist = pr.pageRating || {};
             total = 0; sum = 0;
             total = 0; sum = 0;
             for (k in hist) if (Object.prototype.hasOwnProperty.call(hist, k)) {
             for (k in hist) if (Object.prototype.hasOwnProperty.call(hist, k)) {
Zeile 725: Zeile 739:
               if (!isNaN(s) && !isNaN(c)) { total += c; sum += s * c; }
               if (!isNaN(s) && !isNaN(c)) { total += c; sum += s * c; }
             }
             }
             if (total > 0) {
             if (total > 0) res[pid] = { avg: Math.round((sum / total) * 10) / 10, total: total };
              res[pid] = { avg: Math.round((sum / total) * 10) / 10, total: total };
            }
           }
           }
         }
         }
         return step(idx + 1);
         return step(idx + 1);
       }, function () {
       }, function () { return step(idx + 1); });
        // Fehler in diesem Chunk: einfach weitermachen
        return step(idx + 1);
      });
     }
     }
     return step(0);
     return step(0);
   }
   }


   // Gesamt berechnen (gewichtet) + Stimmen summieren
   // Gesamtwertung + Stimmen summieren
   function computeOverall(entry, contests, weights) {
   function computeOverall(entry, contests, weights) {
     var wSum = 0, wAvgSum = 0, present = 0, totalVotes = 0, i;
     var wSum = 0, wAvgSum = 0, present = 0, totalVotes = 0, i, sc, w;
     for (i = 0; i < contests.length; i++) {
     for (i = 0; i < contests.length; i++) {
       var c = contests[i];
       sc = entry.scores[contests[i]];
      var sc = entry.scores[c];
       if (sc && sc.avg !== null) {
       if (sc && sc.avg !== null) {
         var w = (typeof weights[c] === 'number') ? weights[c] : 1;
         w = (typeof weights[contests[i]] === 'number') ? weights[contests[i]] : 1;
         wSum += w;
         wSum += w;
         wAvgSum += sc.avg * w;
         wAvgSum += sc.avg * w;
Zeile 757: Zeile 766:
   }
   }


   // Rendering der Top-N Liste (kompakte Karte mit Mini-Balken)
   // Rendern
   function renderTopN(container, rows, N, minVotes) {
   function renderTopN(container, rows, N, minVotes) {
    // nur Seiten mit Stimmen >= minVotes
     rows = rows.filter(function (r) {
     rows = rows.filter(function (r) {
       return (r.overall !== null) && (r.totalVotes >= minVotes);
       return (r.overall !== null) && (r.totalVotes >= minVotes);
     });
     });


    // Sortierung: Gesamt desc, Stimmen desc, Titel asc
     rows.sort(function (a, b) {
     rows.sort(function (a, b) {
       if (a.overall === null && b.overall !== null) return 1;
       if (a.overall === null && b.overall !== null) return 1;
       if (a.overall !== null && b.overall === null) return -1;
       if (a.overall !== null && b.overall === null) return -1;
       if (b.overall !== a.overall) return (b.overall - a.overall);
       if (b.overall !== a.overall) return b.overall - a.overall;
       if (b.totalVotes !== a.totalVotes) return (b.totalVotes - a.totalVotes);
       if (b.totalVotes !== a.totalVotes) return b.totalVotes - a.totalVotes;
       return a.title.localeCompare(b.title);
       return a.title.localeCompare(b.title);
     });
     });
Zeile 833: Zeile 840:
   }
   }


   // Boot: findet .whisky-top5 Container und rendert sie
   // Boot: findet .whisky-top5 und rendert sie
   function bootTop5(root) {
   function bootTop5(root) {
     var nodes = (root || document).querySelectorAll('.whisky-top5');
     var nodes = (root || document).querySelectorAll('.whisky-top5');
     if (!nodes.length) return;
     if (!nodes.length) return;


    var n;
     for (var n = 0; n < nodes.length; n++) (function (container) {
     for (n = 0; n < nodes.length; n++) (function (container) {
       if (container.getAttribute('data-top5-init') === '1') return;
       if (container.getAttribute('data-top5-init') === '1') return;
       container.setAttribute('data-top5-init', '1');
       container.setAttribute('data-top5-init', '1');


       var cat = container.getAttribute('data-category') || 'Whisky'; // ohne "Kategorie:"
      // Kategorienliste einlesen (Zeilenumbruch oder Semikolon-getrennt)
       var lim = parseInt(container.getAttribute('data-limit') || '300', 10);
       var raw = container.getAttribute('data-categories') || container.getAttribute('data-category') || '';
      var parts = raw.split(/\n|;/);
      var rootCats = [];
      for (var i = 0; i < parts.length; i++) {
        var name = parts[i].replace(/^\s+|\s+$/g, '');
        if (name) rootCats.push(name);
      }
      if (!rootCats.length) { container.textContent = 'Keine Kategorien angegeben.'; return; }
 
       var lim = parseInt(container.getAttribute('data-limit') || '2000', 10);
       var cnt = parseInt(container.getAttribute('data-count') || '5', 10);
       var cnt = parseInt(container.getAttribute('data-count') || '5', 10);
       var minVotes = parseInt(container.getAttribute('data-min-votes') || '1', 10);
       var minVotes = parseInt(container.getAttribute('data-min-votes') || '1', 10);
      var includeHidden = (container.getAttribute('data-include-hidden') === 'true');


      // Contests + Gewichte
       var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
       var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
       var includeHidden = (container.getAttribute('data-include-hidden') === 'true'); // canSee=0 trotzdem zählen?
       var cParts = rawC.split(','), contests = [], seen = {};
      var parts = rawC.split(','), contests = [], seen = {}, i;
       for (i = 0; i < cParts.length; i++) {
       for (i = 0; i < parts.length; i++) {
         var c = cParts[i].replace(/^\s+|\s+$/g, '');
         var c = parts[i].replace(/^\s+|\s+$/g, '');
         if (c && !seen[c]) { contests.push(c); seen[c] = 1; }
         if (c && !seen[c]) { contests.push(c); seen[c] = 1; }
       }
       }
Zeile 860: Zeile 874:
       container.textContent = 'Lade Topliste …';
       container.textContent = 'Lade Topliste …';


       // 1) Alle Artikel aus Kategorie + Subkategorien holen
       // 1) Alle Artikel aus allen Root-Kats rekursiv einsammeln
       fetchCategoryMembersRecursive(cat, lim).then(function (members) {
       fetchCategoryMembersRecursiveMulti(rootCats, lim).then(function (members) {
         if (!members || !members.length) {
         if (!members || !members.length) {
           container.textContent = 'Keine Seiten in Kategorie „' + cat + '“.';
           container.textContent = 'Keine passenden Seiten gefunden.';
           return;
           return;
         }
         }
Zeile 872: Zeile 886:
         }
         }


         // 2) Je Contest die Werte holen (nacheinander, um Last zu senken)
         // 2) nacheinander je Contest Ratings holen
         function loopContest(idx) {
         function loopContest(idx) {
           if (idx >= contests.length) return Promise.resolve();
           if (idx >= contests.length) return Promise.resolve();
Zeile 886: Zeile 900:


         loopContest(0).then(function () {
         loopContest(0).then(function () {
           // 3) Gesamt berechnen + Array bauen
           // 3) Gesamt berechnen + rendern
           var rows = [], pid, e;
           var rows = [], pid, e;
           for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
           for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
Zeile 893: Zeile 907:
             rows.push(e);
             rows.push(e);
           }
           }
          // 4) Rendern
           renderTopN(container, rows, cnt, minVotes);
           renderTopN(container, rows, cnt, minVotes);
         }).catch(function () {
         }).catch(function () {