MediaWiki:Common.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 608: | Zeile 608: | ||
/* --- Whisky Top-5 (multi-root, recursive, | /* --- 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 | function makeStatus(container){ | ||
var | return function(msg, append){ | ||
var | if (append) { | ||
var | 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( | function fetchOne(title, cont){ | ||
var | var params = { | ||
action: 'query', list: 'categorymembers', | action:'query', list:'categorymembers', cmtitle:title, | ||
cmnamespace:'0|14', cmtype:'page|subcat', cmlimit:Math.min(200, limit), | |||
format:'json' | |||
}; | }; | ||
if ( | if (cont) params.cmcontinue = cont; | ||
return api.get( | return api.get(params).then(function(d){ | ||
var cms = | 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] = | 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] = | if (!visited[sub]) { visited[sub]=1; queue.push(sub); } | ||
} | } | ||
} | } | ||
var | var next = d.continue && d.continue.cmcontinue; | ||
if ( | 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] = | 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 = {} | var api = new mw.Api(); | ||
function next() { | var pages = [], outSet = {}; | ||
if ( | var idx = 0; | ||
var | |||
return | 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 | // 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); | ||
var status = makeStatus(container); | |||
if (!rootCats.length) { status('Keine Kategorien angegeben.'); return; } | |||
status('Sammle Seiten …'); | status('Sammle Seiten …'); | ||
// 1) Seiten | // 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: {} }; } | ||
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) { | ||
status('Keine sichtbaren Stimmen – zweiter Versuch (versteckte Ergebnisse mitzählen) …', true); | |||
includeHidden = true; | includeHidden = true; | ||
return loopContest(0).then(buildAndRender); | return loopContest(0).then(buildAndRender); | ||
} | } | ||
| Zeile 806: | Zeile 885: | ||
} | } | ||
loopContest(0).then(buildAndRender).catch(function(){ status('Topliste konnte nicht geladen werden.', true); }); | |||
}).catch(function(){ status('Topliste konnte nicht geladen werden.'); }); | |||
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]); }); | ||
}); | }); | ||