MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 608: Zeile 608:




/* --- Whisky Top-5 (mehrere Root-Kategorien, rekursiv, ES5) ---------------- */
/* --- Whisky Top-5 (multi-root, recursive, with self-diagnostics) --------- */
mw.loader.using(['mediawiki.api']).then(function () {
mw.loader.using(['mediawiki.api']).then(function () {


  // Utility: sicheres get(obj, ['a','b'])
   function get(obj, path) {
   function get(obj, path) {
     var cur = obj, i;
     var cur = obj, i;
     for (i = 0; i < path.length; i++) {
     for (i = 0; i < path.length; i++) { if (!cur || typeof cur !== 'object') return; cur = cur[path[i]]; }
      if (!cur || typeof cur !== 'object') return;
      cur = cur[path[i]];
    }
     return cur;
     return cur;
   }
   }


  // Gewichte parsen, z. B. "NASE:1,GESCHMACK:2,ABGANG:1"
   function parseWeights(raw, contests) {
   function parseWeights(raw, contests) {
     var map = {}, parts = (raw || '').split(','), i;
     var map = {}, parts = (raw || '').split(','), i;
     for (i = 0; i < parts.length; i++) {
     for (i = 0; i < parts.length; i++) {
       var kv = parts[i].split(':');
       var kv = parts[i].split(':'); if (kv.length !== 2) continue;
      if (kv.length === 2) {
      var k = kv[0].replace(/^\s+|\s+$/g, ''), v = parseFloat(kv[1]);
        var k = kv[0].replace(/^\s+|\s+$/g, '');
      if (!isNaN(v)) map[k] = v;
        var v = parseFloat(kv[1]);
        if (!isNaN(v)) map[k] = v;
      }
    }
    for (i = 0; i < contests.length; i++) {
      if (typeof map[contests[i]] !== 'number') map[contests[i]] = 1;
     }
     }
    for (i = 0; i < contests.length; i++) if (typeof map[contests[i]] !== 'number') map[contests[i]] = 1;
     return map;
     return map;
   }
   }


  // Eine Kategorie (inkl. Subkategorien) einsammeln – BFS
   function fetchCategoryMembersRecursiveSingle(rootCat, limit, outSet, pages) {
   function fetchCategoryMembersRecursiveSingle(rootCat, limit, outSet, pages) {
     var api = new mw.Api();
     var api = new mw.Api();
Zeile 645: Zeile 634:


     function fetchOne(catTitle, cmcontinue) {
     function fetchOne(catTitle, cmcontinue) {
       var params = {
       var p = {
         action: 'query',
         action: 'query', list: 'categorymembers',
        list: 'categorymembers',
         cmtitle: catTitle, cmnamespace: '0|14', cmtype: 'page|subcat',
         cmtitle: catTitle,
         cmlimit: Math.min(200, limit), format: 'json'
        cmnamespace: '0|14',       // Artikel + Kategorien
        cmtype: 'page|subcat',
         cmlimit: Math.min(200, limit),
        format: 'json'
       };
       };
       if (cmcontinue) params.cmcontinue = cmcontinue;
       if (cmcontinue) p.cmcontinue = cmcontinue;


       return api.get(params).then(function (data) {
       return api.get(p).then(function (d) {
         var cms = get(data, ['query', 'categorymembers']) || [];
         var cms = get(d, ['query', 'categorymembers']) || [], i, it;
        var i, it;
         for (i = 0; i < cms.length; i++) {
         for (i = 0; i < cms.length; i++) {
           it = cms[i];
           it = cms[i];
           if (it.ns === 0) { // Artikel
           if (it.ns === 0) {
             var pid = String(it.pageid);
             var pid = String(it.pageid);
             if (!outSet[pid] && pages.length < limit) {
             if (!outSet[pid] && pages.length < limit) { outSet[pid] = true; pages.push({ pageid: pid, title: it.title }); }
              outSet[pid] = true;
           } else if (it.ns === 14) {
              pages.push({ pageid: pid, title: it.title });
            }
           } else if (it.ns === 14) { // Subkat
             var sub = it.title;
             var sub = it.title;
             if (!visited[sub]) {
             if (!visited[sub]) { visited[sub] = true; queue.push(sub); }
              visited[sub] = true;
              queue.push(sub);
            }
           }
           }
         }
         }
         var cont = get(data, ['continue', 'cmcontinue']);
         var cont = get(d, ['continue', 'cmcontinue']);
         if (cont && pages.length < limit) return fetchOne(catTitle, cont);
         if (cont && pages.length < limit) return fetchOne(catTitle, cont);
        return null;
       });
       });
     }
     }
Zeile 692: Zeile 669:
   }
   }


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


  // RatePage-Daten für einen Contest holen (pageIds batched)
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
     var api = new mw.Api();
     var api = new mw.Api(), res = {}, i, chunk = 50, chunks = [];
    var res = {}; // pageId -> {avg, total}
    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 721: Zeile 687:
       var ids = chunks[idx];
       var ids = chunks[idx];
       return api.get({
       return api.get({
         action: 'query',
         action: 'query', prop: 'pagerating', pageids: ids.join('|'),
        prop: 'pagerating',
         prcontest: contest, format: 'json'
        pageids: ids.join('|'),
         prcontest: contest,
        format: 'json'
       }).then(function (d) {
       }).then(function (d) {
         var pages = get(d, ['query', 'pages']) || {};
         var pages = get(d, ['query', 'pages']) || {}, pid, pr, hist, k, total, sum, s, c;
        var pid, pr, hist, k, total, sum, s, c;
         for (pid in pages) if (Object.prototype.hasOwnProperty.call(pages, pid)) {
         for (pid in pages) if (Object.prototype.hasOwnProperty.call(pages, pid)) {
          if (!res[pid]) res[pid] = { avg: null, total: 0 };
           pr = pages[pid].pagerating;
           pr = pages[pid].pagerating;
          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)) {
               s = parseInt(k, 10); c = parseInt(hist[k], 10);
               s = parseInt(k, 10); c = parseInt(hist[k], 10);
Zeile 745: Zeile 706:
       }, function () { return step(idx + 1); });
       }, function () { return step(idx + 1); });
     }
     }
     return step(0);
     return step(0);
   }
   }


  // Gesamtwertung + Stimmen summieren
   function computeOverall(entry, contests, weights) {
   function computeOverall(entry, contests, weights) {
     var wSum = 0, wAvgSum = 0, present = 0, totalVotes = 0, i, sc, w;
     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++) {
       sc = entry.scores[contests[i]];
       sc = entry.scores[contests[i]];
       if (sc && sc.avg !== null) {
       if (sc && sc.avg !== null) { w = (typeof weights[contests[i]] === 'number') ? weights[contests[i]] : 1; wSum += w; wAvgSum += sc.avg * w; present++; }
        w = (typeof weights[contests[i]] === 'number') ? weights[contests[i]] : 1;
        wSum += w;
        wAvgSum += sc.avg * w;
        present++;
      }
       if (sc && sc.total) totalVotes += sc.total;
       if (sc && sc.total) totalVotes += sc.total;
     }
     }
Zeile 766: Zeile 720:
   }
   }


  // Rendern
   function renderTopN(container, rows, N, minVotes) {
   function renderTopN(container, rows, N, 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);
    });
 
     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;
Zeile 779: Zeile 729:
       return a.title.localeCompare(b.title);
       return a.title.localeCompare(b.title);
     });
     });
     rows = rows.slice(0, N);
     rows = rows.slice(0, N);


     var frag = document.createDocumentFragment();
     var frag = document.createDocumentFragment(), i, r, item, rank, name, a, right, mini, track, fill, val, votes;
    var i, r, item, rank, name, a, right, mini, track, fill, val, votes;
 
     for (i = 0; i < rows.length; i++) {
     for (i = 0; i < rows.length; i++) {
       r = rows[i];
       r = rows[i];
 
       item = document.createElement('div'); item.className = 'whisky-top5__item';
       item = document.createElement('div');
       rank = document.createElement('div'); rank.className = 'whisky-top5__rank'; rank.textContent = (i + 1);
      item.className = 'whisky-top5__item';
       name = document.createElement('div'); name.className = 'whisky-top5__name';
 
       a = document.createElement('a'); a.href = mw.util.getUrl(r.title); a.textContent = r.title; name.appendChild(a);
       rank = document.createElement('div');
       right = document.createElement('div'); right.style.minWidth = '160px';
      rank.className = 'whisky-top5__rank';
       mini = document.createElement('div'); mini.className = 'whisky-mini';
      rank.textContent = (i + 1);
       track = document.createElement('div'); track.className = 'whisky-mini__track';
 
       fill = document.createElement('div'); fill.className = 'whisky-mini__fill'; fill.style.width = Math.max(0, Math.min(100, (r.overall / 10) * 100)) + '%';
       name = document.createElement('div');
       val = document.createElement('span'); val.className = 'whisky-mini__val';
      name.className = 'whisky-top5__name';
       a = document.createElement('a');
      a.href = mw.util.getUrl(r.title);
      a.textContent = r.title;
      name.appendChild(a);
 
       right = document.createElement('div');
      right.style.minWidth = '160px';
 
       mini = document.createElement('div');
      mini.className = 'whisky-mini';
       track = document.createElement('div');
      track.className = 'whisky-mini__track';
       fill = document.createElement('div');
      fill.className = 'whisky-mini__fill';
      fill.style.width = Math.max(0, Math.min(100, (r.overall / 10) * 100)) + '%';
       val = document.createElement('span');
      val.className = 'whisky-mini__val';
       val.textContent = (r.overall.toFixed ? r.overall.toFixed(1) : (Math.round(r.overall * 10) / 10));
       val.textContent = (r.overall.toFixed ? r.overall.toFixed(1) : (Math.round(r.overall * 10) / 10));
       track.appendChild(fill);
       track.appendChild(fill); mini.appendChild(track); mini.appendChild(val);
      mini.appendChild(track);
       votes = document.createElement('div'); votes.className = 'whisky-top5__votes'; votes.textContent = r.totalVotes + ' Stimmen';
      mini.appendChild(val);
       right.appendChild(mini); right.appendChild(votes);
 
       item.appendChild(rank); item.appendChild(name); item.appendChild(right);
       votes = document.createElement('div');
      votes.className = 'whisky-top5__votes';
      votes.textContent = r.totalVotes + ' Stimmen';
 
       right.appendChild(mini);
      right.appendChild(votes);
 
       item.appendChild(rank);
      item.appendChild(name);
      item.appendChild(right);
       frag.appendChild(item);
       frag.appendChild(item);
     }
     }
     while (container.firstChild) container.removeChild(container.firstChild);
     while (container.firstChild) container.removeChild(container.firstChild);
     if (rows.length) {
     if (rows.length) container.appendChild(frag); else container.textContent = 'Noch keine Bewertungen vorhanden.';
      container.appendChild(frag);
    } else {
      container.textContent = 'Noch keine Bewertungen vorhanden.';
    }
   }
   }


  // 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');
Zeile 849: Zeile 762:
       container.setAttribute('data-top5-init', '1');
       container.setAttribute('data-top5-init', '1');


       // Kategorienliste einlesen (Zeilenumbruch oder Semikolon-getrennt)
       // Kategorien einlesen (Zeilenumbruch oder Semikolon-getrennt)
       var raw = container.getAttribute('data-categories') || container.getAttribute('data-category') || '';
       var rawCats = container.getAttribute('data-categories') || container.getAttribute('data-category') || '';
       var parts = raw.split(/\n|;/);
       var parts = rawCats.split(/\n|;/), rootCats = [], i;
      var rootCats = [];
       for (i = 0; i < parts.length; i++) { var nm = parts[i].replace(/^\s+|\s+$/g, ''); if (nm) rootCats.push(nm); }
       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 lim = parseInt(container.getAttribute('data-limit') || '2000', 10);
Zeile 866: Zeile 774:
       var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
       var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
       var cParts = rawC.split(','), contests = [], seen = {};
       var cParts = rawC.split(','), contests = [], seen = {};
       for (i = 0; i < cParts.length; i++) {
       for (i = 0; i < cParts.length; i++) { var c = cParts[i].replace(/^\s+|\s+$/g, ''); if (c && !seen[c]) { contests.push(c); seen[c] = 1; } }
        var c = cParts[i].replace(/^\s+|\s+$/g, '');
        if (c && !seen[c]) { contests.push(c); seen[c] = 1; }
      }
       var weights = parseWeights(container.getAttribute('data-weights') || '', contests);
       var weights = parseWeights(container.getAttribute('data-weights') || '', contests);


       container.textContent = 'Lade Topliste …';
       function status(msg) { container.textContent = msg; }
 
      if (!rootCats.length) { status('Keine Kategorien angegeben.'); return; }
      status('Sammle Seiten …');


       // 1) Alle Artikel aus allen Root-Kats rekursiv einsammeln
       // 1) Seiten sammeln
       fetchCategoryMembersRecursiveMulti(rootCats, lim).then(function (members) {
       fetchCategoryMembersRecursiveMulti(rootCats, lim).then(function (members) {
         if (!members || !members.length) {
        status('Gefundene Seiten: ' + (members ? members.length : 0) + ' – lade Bewertungen …');
          container.textContent = 'Keine passenden Seiten gefunden.';
         if (!members || !members.length) { status('Keine passenden Seiten gefunden.'); return; }
          return;
 
        }
         var pageIds = [], byId = {}, i;
         var pageIds = [], byId = {}, i;
         for (i = 0; i < members.length; i++) {
         for (i = 0; i < members.length; i++) { pageIds.push(members[i].pageid); byId[members[i].pageid] = { pageid: members[i].pageid, title: members[i].title, scores: {} }; }
          pageIds.push(members[i].pageid);
 
          byId[members[i].pageid] = { pageid: members[i].pageid, title: members[i].title, scores: {} };
        // Wrapper, damit wir bei 0 Stimmen automatisch mit includeHidden=true nachladen können
        function buildAndRender() {
          var rows = [], pid, e, withVotes = 0;
          for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
            e = byId[pid]; computeOverall(e, contests, weights); rows.push(e);
            if (e.totalVotes > 0 && e.overall !== null) withVotes++;
          }
          if (withVotes === 0 && !includeHidden) {
            // Auto-Retry mit includeHidden=true
            includeHidden = true;
            status('Keine sichtbaren Stimmen – zweiter Versuch (versteckte Ergebnisse mitzählen) …');
            return loopContest(0).then(buildAndRender);
          }
          renderTopN(container, rows, cnt, minVotes);
         }
         }


        // 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 892: Zeile 811:
           return fetchRatingsForContest(pageIds, contest, includeHidden).then(function (map) {
           return fetchRatingsForContest(pageIds, contest, includeHidden).then(function (map) {
             var pid;
             var pid;
             for (pid in map) if (Object.prototype.hasOwnProperty.call(map, pid)) {
             for (pid in map) if (Object.prototype.hasOwnProperty.call(map, pid)) byId[pid].scores[contest] = map[pid];
              byId[pid].scores[contest] = map[pid];
            }
             return loopContest(idx + 1);
             return loopContest(idx + 1);
           });
           });
         }
         }


         loopContest(0).then(function () {
         loopContest(0).then(buildAndRender).catch(function () { status('Topliste konnte nicht geladen werden.'); });
          // 3) Gesamt berechnen + rendern
       }).catch(function () { status('Topliste konnte nicht geladen werden.'); });
          var rows = [], pid, e;
          for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
            e = byId[pid];
            computeOverall(e, contests, weights);
            rows.push(e);
          }
          renderTopN(container, rows, cnt, minVotes);
        }).catch(function () {
          container.textContent = 'Topliste konnte nicht geladen werden.';
        });
       }).catch(function () {
        container.textContent = 'Topliste konnte nicht geladen werden.';
      });


     })(nodes[n]);
     })(nodes[n]);
Zeile 920: Zeile 824:
   if (document.readyState === 'loading') {
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', function () { bootTop5(document); });
     document.addEventListener('DOMContentLoaded', function () { bootTop5(document); });
   } else {
   } else { bootTop5(document); }
    bootTop5(document);
  }
   mw.hook('wikipage.content').add(function ($c) { if ($c && $c[0]) bootTop5($c[0]); });
   mw.hook('wikipage.content').add(function ($c) { if ($c && $c[0]) bootTop5($c[0]); });


});
});