MediaWiki:Common.js: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 608: Zeile 608:




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


  /* ========= Utilities ========= */
   function get(obj, path) {
   function get(obj, path) {
     var cur = obj, i;
     var cur = obj, i;
Zeile 628: Zeile 629:
   }
   }


   function fetchCategoryMembersRecursiveSingle(rootCat, limit, outSet, pages) {
   function makeStatus(container){
     var api = new mw.Api();
    return function(msg, append){
     var visited = {};
      if (append) {
     var queue = ['Kategorie:' + rootCat];
        container.innerHTML += '<br>' + mw.html.escape(msg);
      } else {
        container.textContent = msg;
      }
      // Debug zusätzlich in der Konsole aktivieren:
      // console.log('[Top5]', msg);
    };
  }
 
  /* ========= Robuste Kategorie-Auflösung ========= */
  function categoryCandidates(name){
     var n = (name || '').replace(/^\s+|\s+$/g,'');
     var variants = {};
    function add(s){ if (s && !variants[s]) variants[s]=1; }
    add(n);
    add(n.replace(/\u2026/g, '...')); // … -> ...
    add(n.replace(/\.{3}/g, '…'));    // ... -> …
    add(n.replace(/\u2013/g, '-'));  // – -> -
    add(n.replace(/-/g, '–'));        // - -> –
    add(n.replace(/\s+/g,' '));      // Mehrfach-Leerzeichen glätten
    return Object.keys(variants);
  }
 
  // liefert z. B. "Kategorie:Alle A Dream of …" oder null
  function resolveCategoryTitle(api, rawName){
     var cands = categoryCandidates(rawName).map(function(n){ return 'Kategorie:' + n; });
    return api.get({ action:'query', titles: cands.join('|'), format:'json' })
      .then(function(d){
        var pages = (d && d.query && d.query.pages) || {}, pid, p;
        for (pid in pages) if (Object.prototype.hasOwnProperty.call(pages,pid)) {
          p = pages[pid];
          if (p && p.pageid && p.ns === 14) return p.title;
        }
        return null;
      });
  }
 
  /* ========= Kategorien rekursiv einsammeln (inkl. Subkats) ========= */
  function fetchCategoryMembersRecursiveSingleResolved(api, catTitle, limit, outSet, pages){
    var visited = {}, queue = [catTitle];


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


       return api.get(p).then(function (d) {
       return api.get(params).then(function(d){
         var cms = get(d, ['query', 'categorymembers']) || [], i, it;
         var cms = (d.query && d.query.categorymembers) || [], 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) {
           if (it.ns === 0) {
             var pid = String(it.pageid);
             var pid = String(it.pageid);
             if (!outSet[pid] && pages.length < limit) { outSet[pid] = true; pages.push({ pageid: pid, title: it.title }); }
             if (!outSet[pid] && pages.length < limit) { outSet[pid]=1; pages.push({ pageid:pid, title:it.title }); }
           } else if (it.ns === 14) {
           } else if (it.ns === 14) {
             var sub = it.title;
             var sub = it.title;
             if (!visited[sub]) { visited[sub] = true; queue.push(sub); }
             if (!visited[sub]) { visited[sub]=1; queue.push(sub); }
           }
           }
         }
         }
         var cont = get(d, ['continue', 'cmcontinue']);
         var next = d.continue && d.continue.cmcontinue;
         if (cont && pages.length < limit) return fetchOne(catTitle, cont);
         if (next && pages.length < limit) return fetchOne(title, next);
       });
       });
     }
     }


     function loop() {
     function loop(){
       if (pages.length >= limit || !queue.length) return Promise.resolve();
       if (pages.length >= limit || !queue.length) return Promise.resolve();
       var next = queue.shift();
       var next = queue.shift();
       if (visited[next]) return loop();
       if (visited[next]) return loop();
       visited[next] = true;
       visited[next]=1;
       return fetchOne(next).then(loop);
       return fetchOne(next).then(loop);
     }
     }
Zeile 669: Zeile 709:
   }
   }


   function fetchCategoryMembersRecursiveMulti(rootCats, limit) {
   function fetchCategoryMembersRecursiveMulti(rootCats, limit, status){
     var pages = [], outSet = {}, i = 0;
    var api = new mw.Api();
     function next() {
     var pages = [], outSet = {};
       if (i >= rootCats.length || pages.length >= limit) return Promise.resolve(pages);
    var idx = 0;
       var cat = rootCats[i++]; if (!cat) return next();
 
       return fetchCategoryMembersRecursiveSingle(cat, limit, outSet, pages).then(next);
     function next(){
       if (idx >= rootCats.length || pages.length >= limit) return Promise.resolve(pages);
       var raw = rootCats[idx++];
      if (!raw) return next();
 
       return resolveCategoryTitle(api, raw).then(function(resolved){
        if (!resolved) {
          status('Kategorie nicht gefunden: "' + raw + '" (Schreibweise prüfen)', true);
          return next();
        }
        status('Kategorie erkannt: ' + resolved + ' – sammle …', true);
        var before = pages.length;
        return fetchCategoryMembersRecursiveSingleResolved(api, resolved, limit, outSet, pages).then(function(){
          var added = pages.length - before;
          status('→ gefunden in "' + resolved + '": ' + added + ' Seiten (kumuliert: ' + pages.length + ')', true);
          return next();
        });
      });
     }
     }
     return next();
     return next();
   }
   }


  /* ========= Ratings laden / auswerten ========= */
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
   function fetchRatingsForContest(pageIds, contest, includeHidden) {
     var api = new mw.Api(), res = {}, i, chunk = 50, chunks = [];
     var api = new mw.Api(), res = {}, i, chunk = 50, chunks = [];
Zeile 687: Zeile 746:
       var ids = chunks[idx];
       var ids = chunks[idx];
       return api.get({
       return api.get({
         action: 'query', prop: 'pagerating', pageids: ids.join('|'),
         action:'query', prop:'pagerating', pageids: ids.join('|'),
         prcontest: contest, format: 'json'
         prcontest: contest, format:'json'
       }).then(function (d) {
       }).then(function(d){
         var pages = get(d, ['query', 'pages']) || {}, pid, pr, hist, k, total, sum, s, c;
         var pages = get(d,['query','pages']) || {}, 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 };
           if (!res[pid]) res[pid] = { avg:null, total:0 };
           pr = pages[pid].pagerating;
           pr = pages[pid].pagerating;
           if (pr && (includeHidden || !('canSee' in pr) || pr.canSee !== 0)) {
           if (pr && (includeHidden || !('canSee' in pr) || pr.canSee !== 0)) {
             hist = pr.pageRating || {}; total = 0; sum = 0;
             hist = pr.pageRating || {}; 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);
               if (!isNaN(s) && !isNaN(c)) { total += c; sum += s * c; }
               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(){ return step(idx + 1); });
     }
     }
     return step(0);
     return step(0);
Zeile 710: Zeile 769:


   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) { w = (typeof weights[contests[i]] === 'number') ? weights[contests[i]] : 1; wSum += w; wAvgSum += sc.avg * w; present++; }
       if (sc && sc.avg !== null) { 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;
     }
     }
     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;
   }
   }


  /* ========= Render ========= */
   function renderTopN(container, rows, N, minVotes) {
   function renderTopN(container, rows, N, minVotes) {
     rows = rows.filter(function (r) { return (r.overall !== null) && (r.totalVotes >= minVotes); });
     rows = rows.filter(function(r){ 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;
       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 732: Zeile 792:


     var frag = document.createDocumentFragment(), i, r, item, rank, name, a, right, mini, track, fill, val, votes;
     var frag = document.createDocumentFragment(), 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'); item.className = 'whisky-top5__item';
       rank = document.createElement('div'); rank.className = 'whisky-top5__rank'; rank.textContent = (i + 1);
 
       rank = document.createElement('div'); rank.className = 'whisky-top5__rank'; rank.textContent = (i+1);
 
       name = document.createElement('div'); name.className = 'whisky-top5__name';
       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);
       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';
       right = document.createElement('div'); right.style.minWidth = '160px';
       mini = document.createElement('div'); mini.className = 'whisky-mini';
       mini = document.createElement('div'); mini.className = 'whisky-mini';
       track = document.createElement('div'); track.className = 'whisky-mini__track';
       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)) + '%';
       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 = 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); mini.appendChild(track); mini.appendChild(val);
       track.appendChild(fill); mini.appendChild(track); mini.appendChild(val);
       votes = document.createElement('div'); votes.className = 'whisky-top5__votes'; votes.textContent = r.totalVotes + ' Stimmen';
 
       votes = document.createElement('div'); votes.className = 'whisky-top5__votes';
      votes.textContent = r.totalVotes + ' Stimmen';
 
       right.appendChild(mini); right.appendChild(votes);
       right.appendChild(mini); right.appendChild(votes);
       item.appendChild(rank); item.appendChild(name); item.appendChild(right);
       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) container.appendChild(frag); else container.textContent = 'Noch keine Bewertungen vorhanden.';
     if (rows.length) container.appendChild(frag); else container.textContent = 'Noch keine Bewertungen vorhanden.';
   }
   }


  /* ========= Boot ========= */
   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;


     for (var n = 0; n < nodes.length; n++) (function (container) {
     for (var 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');


       // Kategorien einlesen (Zeilenumbruch oder Semikolon-getrennt)
       // Kategorien einlesen (Zeilenumbruch ODER Semikolon getrennt)
       var rawCats = container.getAttribute('data-categories') || container.getAttribute('data-category') || '';
       var rawCats = container.getAttribute('data-categories') || container.getAttribute('data-category') || '';
       var parts = rawCats.split(/\n|;/), rootCats = [], i;
       var parts = rawCats.split(/\n|;/), rootCats = [], i;
       for (i = 0; i < parts.length; i++) { var nm = parts[i].replace(/^\s+|\s+$/g, ''); if (nm) rootCats.push(nm); }
       for (i=0;i<parts.length;i++){ var nm = parts[i].replace(/^\s+|\s+$/g,''); if (nm) rootCats.push(nm); }


       var lim = parseInt(container.getAttribute('data-limit') || '2000', 10);
       var lim = parseInt(container.getAttribute('data-limit') || '2000', 10);
Zeile 774: Zeile 846:
       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++) { var c = cParts[i].replace(/^\s+|\s+$/g, ''); if (c && !seen[c]) { contests.push(c); seen[c] = 1; } }
       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 weights = parseWeights(container.getAttribute('data-weights') || '', contests);
       var weights = parseWeights(container.getAttribute('data-weights') || '', contests);


       function status(msg) { container.textContent = msg; }
       var status = makeStatus(container);
      if (!rootCats.length) { status('Keine Kategorien angegeben.'); return; }


      if (!rootCats.length) { status('Keine Kategorien angegeben.'); return; }
       status('Sammle Seiten …');
       status('Sammle Seiten …');


       // 1) Seiten sammeln
       // 1) Seiten aus allen Root-Kategorien (rekursiv) einsammeln
       fetchCategoryMembersRecursiveMulti(rootCats, lim).then(function (members) {
       fetchCategoryMembersRecursiveMulti(rootCats, lim, status).then(function(members){
         status('Gefundene Seiten: ' + (members ? members.length : 0) + ' – lade Bewertungen …');
         status('Gefundene Seiten gesamt: ' + (members ? members.length : 0) + ' – lade Bewertungen …', true);
         if (!members || !members.length) { status('Keine passenden Seiten gefunden.'); return; }
         if (!members || !members.length) { status('Keine passenden Seiten gefunden.', true); return; }


         var pageIds = [], byId = {}, i;
         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: {} }; }
         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: {} }; }


         // Wrapper, damit wir bei 0 Stimmen automatisch mit includeHidden=true nachladen können
         function loopContest(idx){
         function buildAndRender() {
          if (idx >= contests.length) return Promise.resolve();
          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);
          });
        }
 
         function buildAndRender(){
           var rows = [], pid, e, withVotes = 0;
           var rows = [], pid, e, withVotes = 0;
           for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId, pid)) {
           for (pid in byId) if (Object.prototype.hasOwnProperty.call(byId,pid)) {
             e = byId[pid]; computeOverall(e, contests, weights); rows.push(e);
             e = byId[pid]; computeOverall(e, contests, weights); rows.push(e);
             if (e.totalVotes > 0 && e.overall !== null) withVotes++;
             if (e.totalVotes > 0 && e.overall !== null) withVotes++;
           }
           }
           if (withVotes === 0 && !includeHidden) {
           if (withVotes === 0 && !includeHidden) {
             // Auto-Retry mit includeHidden=true
             status('Keine sichtbaren Stimmen – zweiter Versuch (versteckte Ergebnisse mitzählen) …', true);
             includeHidden = true;
             includeHidden = true;
            status('Keine sichtbaren Stimmen – zweiter Versuch (versteckte Ergebnisse mitzählen) …');
             return loopContest(0).then(buildAndRender);
             return loopContest(0).then(buildAndRender);
           }
           }
Zeile 806: Zeile 885:
         }
         }


        function loopContest(idx) {
         loopContest(0).then(buildAndRender).catch(function(){ status('Topliste konnte nicht geladen werden.', true); });
          if (idx >= contests.length) return Promise.resolve();
       }).catch(function(){ status('Topliste konnte nicht geladen werden.'); });
          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);
          });
        }
 
         loopContest(0).then(buildAndRender).catch(function () { status('Topliste konnte nicht geladen werden.'); });
       }).catch(function () { status('Topliste konnte nicht geladen werden.'); });


     })(nodes[n]);
     })(nodes[n]);
Zeile 823: Zeile 892:


   if (document.readyState === 'loading') {
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', function () { bootTop5(document); });
     document.addEventListener('DOMContentLoaded', function(){ bootTop5(document); });
   } else { bootTop5(document); }
   } else { 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]); });


});
});