MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 608: Zeile 608:




/* --- Whisky Top-5 auf der Hauptseite ------------------------------------ */
/* --- Whisky Top-5 (rekursiv über Unterkategorien) ----------------------- */
mw.loader.using(['mediawiki.api']).then(function () {
mw.loader.using(['mediawiki.api']).then(function () {


   function get(obj, path) { var c=obj,i; for(i=0;i<path.length;i++){ if(!c||typeof c!=='object') return; c=c[path[i]]; } return c; }
  // kleines Hilfs-get ohne optional chaining
   function get(obj, path) {
    var cur = obj, i;
    for (i = 0; i < path.length; i++) {
      if (!cur || typeof cur !== 'object') return;
      cur = cur[path[i]];
    }
    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(':'); if (kv.length!==2) continue;
       var kv = parts[i].split(':');
      var k = kv[0].replace(/^\s+|\s+$/g,''); var v = parseFloat(kv[1]);
      if (kv.length === 2) {
      if (!isNaN(v)) map[k]=v;
        var k = kv[0].replace(/^\s+|\s+$/g, '');
        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;
   }
   }


   function fetchCategoryMembers(cat, limit) {
  // Holt Artikel (NS0) aus einer Kategorie inkl. ALLER Unterkategorien (BFS), bis limit erreicht ist
     var api = new mw.Api(), out = [];
   function fetchCategoryMembersRecursive(rootCat, limit) {
     function loop(cmcontinue){
     var api = new mw.Api();
       var p = { action:'query', list:'categorymembers', cmtitle:'Kategorie:'+cat,
    var visitedCats = {};
                cmnamespace:0, cmlimit: Math.min(limit,200), format:'json' };
    var queue = ['Kategorie:' + rootCat];  // Startkategorie (mit Präfix)
       if (cmcontinue) p.cmcontinue = cmcontinue;
    var pages = [];                       // { pageid, title }
       return api.get(p).then(function(d){
 
         var arr = get(d,['query','categorymembers'])||[], i;
     function fetchOneCat(catTitle, cmcontinue) {
         for (i=0;i<arr.length;i++){ out.push(arr[i]); if (out.length>=limit) break; }
       var params = {
         var cont = get(d,['continue','cmcontinue']);
        action: 'query',
         if (cont && out.length<limit) return loop(cont);
        list: 'categorymembers',
         return out;
        cmtitle: catTitle,
        cmnamespace: '0|14',   // 0=Seiten, 14=Kategorien
        cmtype: 'page|subcat',
        cmlimit: Math.min(200, limit),
        format: 'json'
      };
       if (cmcontinue) params.cmcontinue = cmcontinue;
 
       return api.get(params).then(function (data) {
         var cms = get(data, ['query', 'categorymembers']) || [];
        var i;
         for (i = 0; i < cms.length; i++) {
          var item = cms[i];
          if (item.ns === 0) {
            if (pages.length < limit) pages.push({ pageid: String(item.pageid), title: item.title });
          } else if (item.ns === 14) {
            var subcatTitle = item.title; // enthält schon "Kategorie:"
            if (!visitedCats[subcatTitle]) {
              visitedCats[subcatTitle] = true;
              queue.push(subcatTitle);
            }
          }
        }
 
         var cont = get(data, ['continue', 'cmcontinue']);
         if (cont && pages.length < limit) {
          return fetchOneCat(catTitle, cont);
        }
         return null;
       });
       });
     }
     }
    function loop() {
      if (pages.length >= limit || queue.length === 0) {
        return Promise.resolve(pages);
      }
      var nextCat = queue.shift();
      if (visitedCats[nextCat]) return loop();
      visitedCats[nextCat] = true;
      return fetchOneCat(nextCat).then(loop);
    }
     return loop();
     return loop();
   }
   }


   function fetchRatingsForContest(pageIds, contest) {
  // Holt für pageIds die RatePage-Daten zu einem Contest
     var api = new mw.Api(), res = {}, i, chunk=50, chunks=[];
  // includeHidden=true => zählt auch, wenn canSee=0 (nur für Ranking)
     for (i=0;i<pageIds.length;i+=chunk) chunks.push(pageIds.slice(i,i+chunk));
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
     function step(idx){
     var api = new mw.Api();
       if (idx>=chunks.length) return Promise.resolve(res);
    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));
 
     function step(idx) {
       if (idx >= chunks.length) return Promise.resolve(res);
       var ids = chunks[idx];
       var ids = chunks[idx];
       return api.get({
       return api.get({
         action:'query', prop:'pagerating', pageids: ids.join('|'),
         action: 'query',
         prcontest: contest, format:'json'
        prop: 'pagerating',
       }).then(function(d){
        pageids: ids.join('|'),
         var pages = get(d,['query','pages'])||{};
         prcontest: contest,
         for (var pid in pages) if (Object.prototype.hasOwnProperty.call(pages,pid)) {
        format: 'json'
           var pr = pages[pid].pagerating;
       }).then(function (d) {
           if (!res[pid]) res[pid] = { avg:null, total:0 };
         var pages = get(d, ['query', 'pages']) || {};
           if (pr && (!('canSee' in pr) || pr.canSee!==0)) {
        var pid, pr, hist, k, total, sum, s, c;
             var hist = pr.pageRating || {}, total=0, sum=0, k;
         for (pid in pages) if (Object.prototype.hasOwnProperty.call(pages, pid)) {
             for (k in hist) if (Object.prototype.hasOwnProperty.call(hist,k)) {
           pr = pages[pid].pagerating;
               var s = parseInt(k,10), c = parseInt(hist[k],10);
           if (!res[pid]) res[pid] = { avg: null, total: 0 };
               if (!isNaN(s)&&!isNaN(c)){ total+=c; sum+=s*c; }
           if (pr && (includeHidden || !('canSee' in pr) || pr.canSee !== 0)) {
             hist = pr.pageRating || {};
            total = 0; sum = 0;
             for (k in hist) if (Object.prototype.hasOwnProperty.call(hist, k)) {
               s = parseInt(k, 10); c = parseInt(hist[k], 10);
               if (!isNaN(s) && !isNaN(c)) { total += c; sum += s * c; }
            }
            if (total > 0) {
              res[pid] = { avg: Math.round((sum / total) * 10) / 10, total: total };
             }
             }
            if (total>0) { res[pid] = { avg: Math.round((sum/total)*10)/10, total: total }; }
           }
           }
         }
         }
         return step(idx+1);
         return step(idx + 1);
       }, function(){ return step(idx+1); });
       }, function () {
        // Fehler in diesem Chunk: einfach weitermachen
        return step(idx + 1);
      });
     }
     }
     return step(0);
     return step(0);
   }
   }


  // Gesamt berechnen (gewichtet) + 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;
     for (i=0;i<contests.length;i++){
     for (i = 0; i < contests.length; i++) {
       var c = contests[i], sc = entry.scores[c];
       var c = contests[i];
       if (sc && sc.avg!==null) { var w = weights[c]||1; wSum+=w; wAvgSum+=sc.avg*w; present++; }
      var sc = entry.scores[c];
       if (sc && sc.avg !== null) {
        var w = (typeof weights[c] === 'number') ? weights[c] : 1;
        wSum += w;
        wAvgSum += sc.avg * w;
        present++;
      }
       if (sc && sc.total) totalVotes += sc.total;
       if (sc && sc.total) totalVotes += sc.total;
     }
     }
     entry.totalVotes = totalVotes;
     entry.totalVotes = totalVotes;
     entry.overall = (present>0 && wSum>0) ? Math.round((wAvgSum/wSum)*10)/10 : null;
     entry.overall = (present > 0 && wSum > 0) ? Math.round((wAvgSum / wSum) * 10) / 10 : null;
   }
   }


   function renderTopN(container, rows, N) {
  // Rendering der Top-N Liste (kompakte Karte mit Mini-Balken)
     // nur Seiten mit Stimmen
   function renderTopN(container, rows, N, minVotes) {
     rows = rows.filter(function(r){ return (r.overall!==null) && (r.totalVotes>0); });
     // nur Seiten mit Stimmen >= minVotes
     rows.sort(function(a,b){
     rows = rows.filter(function (r) {
       if (a.overall===null && b.overall!==null) return 1;
      return (r.overall !== null) && (r.totalVotes >= minVotes);
       if (a.overall!==null && b.overall===null) return -1;
    });
       if (b.overall!==a.overall) return (b.overall - a.overall);
 
       if (b.totalVotes!==a.totalVotes) return (b.totalVotes - a.totalVotes);
    // Sortierung: Gesamt desc, Stimmen desc, Titel asc
     rows.sort(function (a, b) {
       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.totalVotes !== a.totalVotes) return (b.totalVotes - a.totalVotes);
       return a.title.localeCompare(b.title);
       return a.title.localeCompare(b.title);
     });
     });
     rows = rows.slice(0, N);
     rows = rows.slice(0, N);


    // bauen
     var frag = document.createDocumentFragment();
     var frag = document.createDocumentFragment();
     for (var i=0;i<rows.length;i++){
     var i, r, item, rank, name, a, right, mini, track, fill, val, votes;
      var r = rows[i];
      var item = document.createElement('div'); item.className='whisky-top5__item';


      var rank = document.createElement('div'); rank.className='whisky-top5__rank'; rank.textContent = (i+1);
    for (i = 0; i < rows.length; i++) {
      var name = document.createElement('div'); name.className='whisky-top5__name';
       r = rows[i];
       var a = document.createElement('a'); a.href = mw.util.getUrl(r.title); a.textContent = r.title; name.appendChild(a);


       var right = document.createElement('div'); right.style.minWidth='160px';
       item = document.createElement('div');
       var mini = document.createElement('div'); mini.className='whisky-mini';
       item.className = 'whisky-top5__item';
      var track = document.createElement('div'); track.className='whisky-mini__track';
      var fill = document.createElement('div'); fill.className='whisky-mini__fill';
      fill.style.width = Math.max(0, Math.min(100, (r.overall/10)*100)) + '%';
      var val = document.createElement('span'); val.className='whisky-mini__val';
      val.textContent = (r.overall.toFixed ? r.overall.toFixed(1) : (Math.round(r.overall*10)/10));
      track.appendChild(fill); mini.appendChild(track); mini.appendChild(val);


       var votes = document.createElement('div'); votes.className='whisky-top5__votes';
       rank = document.createElement('div');
      rank.className = 'whisky-top5__rank';
      rank.textContent = (i + 1);
 
      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);
 
      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));
      track.appendChild(fill);
      mini.appendChild(track);
      mini.appendChild(val);
 
      votes = document.createElement('div');
      votes.className = 'whisky-top5__votes';
       votes.textContent = r.totalVotes + ' Stimmen';
       votes.textContent = r.totalVotes + ' Stimmen';


Zeile 724: Zeile 825:
     }
     }


    // ersetzen
     while (container.firstChild) container.removeChild(container.firstChild);
     while (container.firstChild) container.removeChild(container.firstChild);
     if (rows.length) {
     if (rows.length) {
Zeile 733: Zeile 833:
   }
   }


   function bootTop5(root){
  // Boot: findet .whisky-top5 Container und rendert sie
     var nodes = (root||document).querySelectorAll('.whisky-top5');
   function bootTop5(root) {
     var nodes = (root || document).querySelectorAll('.whisky-top5');
     if (!nodes.length) return;
     if (!nodes.length) return;
    for (var n=0;n<nodes.length;n++){
      (function(container){
        if (container.getAttribute('data-top5-init')==='1') return;
        container.setAttribute('data-top5-init','1');


        var cat = container.getAttribute('data-category') || 'Whisky';
    var n;
        var lim = parseInt(container.getAttribute('data-limit') || '300', 10);
    for (n = 0; n < nodes.length; n++) (function (container) {
        var cnt = parseInt(container.getAttribute('data-count') || '5', 10);
      if (container.getAttribute('data-top5-init') === '1') return;
        var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
      container.setAttribute('data-top5-init', '1');
        var parts = rawC.split(','), contests=[], seen={}, i;
 
        for (i=0;i<parts.length;i++){ var c=parts[i].replace(/^\s+|\s+$/g,''); if(c && !seen[c]){ contests.push(c); seen[c]=1; } }
      var cat = container.getAttribute('data-category') || 'Whisky'; // ohne "Kategorie:"
        var weights = parseWeights(container.getAttribute('data-weights')||'', contests);
      var lim = parseInt(container.getAttribute('data-limit') || '300', 10);
      var cnt = parseInt(container.getAttribute('data-count') || '5', 10);
      var minVotes = parseInt(container.getAttribute('data-min-votes') || '1', 10);
 
      // Contests + Gewichte
      var rawC = container.getAttribute('data-contests') || 'NASE,GESCHMACK,ABGANG,GESAMTEINDRUCK';
      var includeHidden = (container.getAttribute('data-include-hidden') === 'true'); // canSee=0 trotzdem zählen?
      var parts = rawC.split(','), contests = [], seen = {}, i;
      for (i = 0; i < parts.length; i++) {
        var c = parts[i].replace(/^\s+|\s+$/g, '');
        if (c && !seen[c]) { contests.push(c); seen[c] = 1; }
      }
      var weights = parseWeights(container.getAttribute('data-weights') || '', contests);
 
      container.textContent = 'Lade Topliste …';


         container.textContent = 'Lade Topliste …';
      // 1) Alle Artikel aus Kategorie + Subkategorien holen
      fetchCategoryMembersRecursive(cat, lim).then(function (members) {
         if (!members || !members.length) {
          container.textContent = 'Keine Seiten in Kategorie „' + cat + '“.';
          return;
        }
        var pageIds = [], byId = {}, 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: {} };
        }


         fetchCategoryMembers(cat, lim).then(function(members){
         // 2) Je Contest die Werte holen (nacheinander, um Last zu senken)
           if (!members || !members.length){ container.textContent='Keine Seiten in Kategorie „'+cat+'“.'; return; }
        function loopContest(idx) {
           var pageIds=[], byId={}, i;
           if (idx >= contests.length) return Promise.resolve();
           for (i=0;i<members.length;i++){ pageIds.push(String(members[i].pageid)); byId[String(members[i].pageid)]={ pageid:String(members[i].pageid), title:members[i].title, scores:{} }; }
           var contest = contests[idx];
           return fetchRatingsForContest(pageIds, contest, includeHidden).then(function (map) {
            var pid;
            for (pid in map) if (Object.prototype.hasOwnProperty.call(map, pid)) {
              byId[pid].scores[contest] = map[pid];
            }
            return loopContest(idx + 1);
          });
        }


          // nacheinander je Contest
        loopContest(0).then(function () {
          function loopContest(idx){
          // 3) Gesamt berechnen + Array bauen
            if (idx>=contests.length) return Promise.resolve();
          var rows = [], pid, e;
            var contest = contests[idx];
          for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
            return fetchRatingsForContest(pageIds, contest).then(function(map){
            e = byId[pid];
              for (var pid in map) if (Object.prototype.hasOwnProperty.call(map,pid)) {
            computeOverall(e, contests, weights);
                byId[pid].scores[contest] = map[pid];
             rows.push(e);
              }
              return loopContest(idx+1);
             });
           }
           }
          // 4) Rendern
          renderTopN(container, rows, cnt, minVotes);
        }).catch(function () {
          container.textContent = 'Topliste konnte nicht geladen werden.';
        });
      }).catch(function () {
        container.textContent = 'Topliste konnte nicht geladen werden.';
      });


          loopContest(0).then(function(){
    })(nodes[n]);
            var rows=[], pid;
            for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId,pid)) {
              var e = byId[pid]; computeOverall(e, contests, weights); rows.push(e);
            }
            renderTopN(container, rows, cnt);
          }).catch(function(){ container.textContent='Topliste konnte nicht geladen werden.'; });
        }).catch(function(){ container.textContent='Topliste konnte nicht geladen werden.'; });
      })(nodes[n]);
    }
   }
   }


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


});
});