MediaWiki:Common.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung Markierung: Manuelle Zurücksetzung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 220: | Zeile 220: | ||
/* Whisky-Ratings – | /* ADOS Whisky-Ratings – RatePage Frontend (ES5-kompatibel, Widgets + Stats + Summary) */ | ||
mw.loader.using(['mediawiki.api', 'mediawiki.user']).then(function () { | mw.loader.using(['mediawiki.api', 'mediawiki.user']).then(function () { | ||
function | // -------- Hilfsfunktion ohne modernen Syntax -------- | ||
document.querySelectorAll('.whisky-rating__item'). | function get(obj, path) { | ||
initMetaOnly(); // | var cur = obj, i; | ||
for (i = 0; i < path.length; i++) { | |||
if (!cur || typeof cur !== 'object') return undefined; | |||
cur = cur[path[i]]; | |||
} | |||
return cur; | |||
} | |||
// -------- Bootstrapping -------- | |||
function boot(root) { | |||
var scope = root || document; | |||
var i, nodes; | |||
// Interaktive Widgets | |||
nodes = scope.querySelectorAll('.whisky-rating__item'); | |||
for (i = 0; i < nodes.length; i++) setupWidget(nodes[i]); | |||
// Meta-only Anzeige(n) | |||
initMetaOnly(scope); | |||
// Summary-Blocks (Übersichtstabelle + Gesamt) | |||
nodes = scope.querySelectorAll('[data-ratepage-summary="true"]'); | |||
for (i = 0; i < nodes.length; i++) renderSummary(nodes[i]); | |||
} | } | ||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', function(){ boot(document); }); | |||
} else { | |||
boot(document); | |||
} | |||
// Falls Inhalte später nachgeladen werden (z. B. VisualEditor) | |||
mw.hook('wikipage.content').add(function($content){ | |||
if ($content && $content[0]) boot($content[0]); | |||
}); | |||
// -------- Interaktives Widget (Whiskygläser) -------- | |||
function setupWidget(box) { | function setupWidget(box) { | ||
var pageId = mw.config.get('wgArticleId'); | |||
var contest = box.dataset.ratepageContest || undefined; // "NASE" | "GESCHMACK" | "ABGANG" | |||
var scale = parseInt(box.dataset.ratepageScale || '10', 10); | |||
var widget = box.querySelector('.whisky-rating__widget'); | |||
var meta = box.querySelector('.whisky-rating__meta'); | |||
var isAnon = mw.user.isAnon(); | |||
if (isAnon) { | if (isAnon) { | ||
box.classList.add('whisky-rating--disabled'); | box.classList.add('whisky-rating--disabled'); | ||
meta.textContent = 'Bitte einloggen, um zu bewerten.'; | if (meta) meta.textContent = 'Bitte einloggen, um zu bewerten.'; | ||
} | } | ||
var buttons = []; | |||
var i; | |||
for ( | for (i = 1; i <= scale; i++) { | ||
(function(iVal){ | |||
var btn = document.createElement('button'); | |||
btn.type = 'button'; | |||
btn.className = 'whisky-glass'; | |||
btn.setAttribute('aria-label', iVal + ' von ' + scale); | |||
btn.setAttribute('aria-pressed', 'false'); | |||
if (isAnon) { | |||
btn.disabled = true; | |||
btn.title = 'Nur für registrierte Benutzer'; | |||
} else { | |||
btn.title = iVal + ' / ' + scale; | |||
btn.addEventListener('mouseenter', function(){ highlight(iVal); }); | |||
btn.addEventListener('mouseleave', function(){ highlight(current); }); | |||
btn.addEventListener('click', function(){ vote(iVal); }); | |||
btn.addEventListener('keydown', function(e){ | |||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); vote(iVal); } | |||
if (e.key === 'ArrowRight' && iVal < scale) buttons[iVal].focus(); | |||
if (e.key === 'ArrowLeft' && iVal > 1) buttons[iVal-2].focus(); | |||
}); | |||
} | |||
widget.appendChild(btn); | |||
buttons.push(btn); | |||
})(i); | |||
} | } | ||
var current = 0; | |||
highlight(current); | highlight(current); | ||
function highlight(n) { | function highlight(n) { | ||
buttons. | var j; | ||
for (j = 0; j < buttons.length; j++) { | |||
var active = (j < n); | |||
buttons[j].classList.toggle('is-active', active); | |||
} | buttons[j].setAttribute('aria-pressed', active ? 'true' : 'false'); | ||
} | |||
} | } | ||
// --- Ø & Stimmen laden, eigene Stimme/Erlaubnis berücksichtigen --- | |||
function updateStats() { | |||
var api = new mw.Api(); | |||
api.get({ | |||
action: 'query', | |||
prop: 'pagerating', | |||
pageids: pageId, | |||
prcontest: contest || undefined, | |||
format: 'json', | |||
errorformat: 'plaintext' | |||
}).done(function (data) { | |||
try { | |||
var pages = get(data, ['query','pages']) || {}; | |||
var keys = []; | |||
for (var k in pages) if (Object.prototype.hasOwnProperty.call(pages, k)) keys.push(k); | |||
var pid = keys.length ? keys[0] : String(pageId); | |||
var page = pages[pid] || {}; | |||
var pr = page.pagerating; | |||
if (!meta) return; | |||
if (!pr) { | |||
if (!meta.textContent) meta.textContent = 'Noch keine Bewertungen'; | |||
return; | |||
} | |||
if (typeof pr.canSee !== 'undefined' && pr.canSee === 0) { | |||
meta.textContent = 'Bewertungen sind verborgen.'; | |||
} else { | |||
var hist = pr.pageRating || {}; | |||
var total = 0, sum = 0; | |||
for (var key in hist) { | |||
if (Object.prototype.hasOwnProperty.call(hist, key)) { | |||
var s = parseInt(key, 10), c = parseInt(hist[key], 10); | |||
if (!isNaN(s) && !isNaN(c)) { total += c; sum += s * c; } | |||
} | |||
} | |||
meta.textContent = total | |||
? ('Ø ' + (Math.round((sum/total)*10)/10) + ' (' + total + ' Stimmen)') | |||
: 'Noch keine Bewertungen'; | |||
} | |||
if (pr.userVote) { | |||
current = pr.userVote; | |||
highlight(current); | |||
} | |||
if (typeof pr.canVote !== 'undefined' && pr.canVote === 0) { | |||
box.classList.add('whisky-rating--disabled'); | |||
var gls = widget.querySelectorAll('.whisky-glass'); | |||
for (var i2 = 0; i2 < gls.length; i2++) gls[i2].disabled = true; | |||
if (meta.textContent.indexOf('nicht abstimmen') === -1) { | |||
meta.textContent += (meta.textContent ? ' • ' : '') + 'Du darfst hier nicht abstimmen.'; | |||
} | |||
} | |||
} catch (e) { | |||
if (window.console && console.error) console.error(e); | |||
if (meta && !meta.textContent) meta.textContent = 'Bewertungen konnten nicht geladen werden.'; | |||
} | } | ||
} | }).fail(function (xhr) { | ||
if (window.console && console.error) console.error('Pagerating-Load-Error', xhr); | |||
if (meta && !meta.textContent) meta.textContent = 'Bewertungen konnten nicht geladen werden.'; | |||
}); | |||
} | } | ||
// --- Vote senden | // --- Vote senden --- | ||
function vote(value) { | function vote(value) { | ||
var api = new mw.Api(); | |||
if (meta) meta.textContent = 'Wird gespeichert …'; | |||
var saved = false; | |||
var failTimer = setTimeout(function () { | |||
if (!saved && meta) meta.textContent = 'Speichern dauert ungewöhnlich lange … bitte Seite neu laden.'; | |||
}, 8000); | |||
api.postWithToken('csrf', { | |||
action: 'ratepage', | |||
pageid: pageId, | |||
answer: value, | |||
contest: contest || undefined, | |||
format: 'json' | |||
}).done(function () { | |||
saved = true; | |||
clearTimeout(failTimer); | |||
current = value; | |||
highlight(current); | |||
if (meta) meta.textContent = 'Danke! Deine Bewertung: ' + value + ' / ' + scale; | |||
updateStats(); | |||
}).fail(function (xhr) { | |||
clearTimeout(failTimer); | |||
var msg = 'Unbekannter Fehler'; | |||
try { | |||
var j = xhr && xhr.responseJSON ? xhr.responseJSON : xhr; | |||
if (j && j.error) { | |||
msg = (j.error.code ? j.error.code + ': ' : '') + (j.error.info || ''); | |||
} | |||
} catch(e){} | |||
if (window.console && console.error) console.error('RatePage-API-Fehler:', xhr); | |||
if (meta) meta.textContent = 'Speichern fehlgeschlagen: ' + msg; | |||
}); | |||
} | |||
updateStats(); | updateStats(); | ||
} | } | ||
/ | // -------- Meta-only Ø-Ausgaben irgendwo im Text -------- | ||
function initMetaOnly() { | function initMetaOnly(scope) { | ||
document.querySelectorAll('.whisky-rating__meta-only'). | var root = scope || document; | ||
var nodes = root.querySelectorAll('.whisky-rating__meta-only'); | |||
var i; | |||
for (i = 0; i < nodes.length; i++) (function(box){ | |||
var pageId = parseInt(box.dataset.ratepagePageid || mw.config.get('wgArticleId'), 10); | |||
var contest = box.dataset.ratepageContest || undefined; | |||
new mw.Api().get({ | |||
action: 'query', | action: 'query', | ||
prop: 'pagerating', | prop: 'pagerating', | ||
| Zeile 400: | Zeile 437: | ||
format: 'json' | format: 'json' | ||
}).done(function (data) { | }).done(function (data) { | ||
var pages = get(data, ['query','pages']) || {}; | |||
var keys = []; | |||
for (var k in pages) if (Object.prototype.hasOwnProperty.call(pages, k)) keys.push(k); | |||
var pid = keys.length ? keys[0] : String(pageId); | |||
var pr = pages[pid] && pages[pid].pagerating; | |||
if (!pr) { box.textContent = ''; return; } | if (!pr) { box.textContent = ''; return; } | ||
if (typeof pr.canSee !== 'undefined' && pr.canSee === 0) { | if (typeof pr.canSee !== 'undefined' && pr.canSee === 0) { box.textContent = 'Bewertung verborgen'; return; } | ||
var hist = pr.pageRating || {}; | |||
var total = 0, sum = 0; | |||
for (var key in hist) { | |||
if (Object.prototype.hasOwnProperty.call(hist, key)) { | |||
var s = Number(key), c = Number(hist[key]); | |||
if (s && c) { total += c; sum += s * c; } | |||
} | |||
} | } | ||
box.textContent = total ? ('Ø ' + (Math.round((sum/total)*10)/10) + ' (' + total + ' Stimmen)') : 'Noch keine Bewertungen'; | |||
box.textContent = total | |||
}); | }); | ||
}); | })(nodes[i]); | ||
} | } | ||
if (document. | // -------- Kompakte Übersichtstabelle inkl. "Gesamt" -------- | ||
function renderSummary(container) { | |||
var pageId = mw.config.get('wgArticleId'); | |||
var raw = container.dataset.ratepageContests || 'NASE,GESCHMACK,ABGANG'; | |||
var parts = raw.split(','); | |||
var i; | |||
// Trim | |||
for (i = 0; i < parts.length; i++) parts[i] = parts[i].replace(/^\s+|\s+$/g, ''); | |||
// Mapping Anzeigenamen -> IDs | |||
var nameToId = { 'nase':'NASE', 'geschmack':'GESCHMACK', 'abgang':'ABGANG' }; | |||
var contests = []; | |||
for (i = 0; i < parts.length; i++) { | |||
var key = parts[i]; | |||
if (!key) continue; | |||
var norm = key.toLowerCase(); | |||
contests.push(nameToId[norm] ? nameToId[norm] : key); | |||
} | |||
// Doppelte entfernen | |||
var seen = {}; | |||
contests = contests.filter(function(c){ if (seen[c]) return false; seen[c]=true; return true; }); | |||
var labels = { NASE: 'Nase', GESCHMACK: 'Geschmack', ABGANG: 'Abgang' }; | |||
container.textContent = 'Lade Bewertungen …'; | |||
function fetchContest(contest) { | |||
return new mw.Api().get({ | |||
action: 'query', | |||
prop: 'pagerating', | |||
pageids: pageId, | |||
prcontest: contest, | |||
format: 'json', | |||
errorformat: 'plaintext' | |||
}).then(function (data) { | |||
var pages = get(data, ['query','pages']) || {}; | |||
var keys = []; | |||
for (var k in pages) if (Object.prototype.hasOwnProperty.call(pages, k)) keys.push(k); | |||
var pid = keys.length ? keys[0] : String(pageId); | |||
var pr = pages[pid] && pages[pid].pagerating; | |||
if (!pr || (typeof pr.canSee !== 'undefined' && pr.canSee === 0)) { | |||
return { contest: contest, label: (labels[contest] || contest), avg: null, total: 0 }; | |||
} | |||
var hist = pr.pageRating || {}; | |||
var total = 0, sum = 0; | |||
for (var key in hist) { | |||
if (Object.prototype.hasOwnProperty.call(hist, key)) { | |||
var s = Number(key), c = Number(hist[key]); | |||
if (s && c) { total += c; sum += s * c; } | |||
} | |||
} | |||
var avg = total ? Math.round((sum / total) * 10) / 10 : null; | |||
return { contest: contest, label: (labels[contest] || contest), avg: avg, total: total }; | |||
}, function () { | |||
// Einzelner Contest fehlgeschlagen -> nicht blockieren | |||
return { contest: contest, label: (labels[contest] || contest), avg: null, total: 0, _error: true }; | |||
}); | |||
} | |||
var promises = []; | |||
for (i = 0; i < contests.length; i++) promises.push(fetchContest(contests[i])); | |||
Promise.all(promises).then(function (rows) { | |||
if (!rows || !rows.length) { | |||
container.textContent = 'Konnte Bewertungen nicht laden.'; | |||
return; | |||
} | |||
var table = document.createElement('table'); | |||
table.className = 'whisky-summary__table'; | |||
var thead = document.createElement('thead'); | |||
thead.innerHTML = '<tr><th>Kategorie</th><th>Ø</th><th>Stimmen</th></tr>'; | |||
table.appendChild(thead); | |||
var tbody = document.createElement('tbody'); | |||
// Einzelzeilen | |||
var r; | |||
for (r = 0; r < rows.length; r++) { | |||
var row = rows[r]; | |||
var avgText = (row.avg !== null) | |||
? (row.avg.toFixed ? row.avg.toFixed(1) : (Math.round(row.avg*10)/10)) | |||
: '–'; | |||
var totalText = row.total ? String(row.total) : '0'; | |||
var tr = document.createElement('tr'); | |||
tr.innerHTML = '<td>' + row.label + '</td><td>' + avgText + '</td><td>' + totalText + '</td>'; | |||
tbody.appendChild(tr); | |||
} | |||
// Gesamt (ungewichtet; bei Bedarf Gewichte einführen) | |||
var present = 0, sumAvg = 0; | |||
for (r = 0; r < rows.length; r++) { | |||
if (rows[r].avg !== null) { present++; sumAvg += rows[r].avg; } | |||
} | |||
var overall = (present > 0) ? Math.round((sumAvg / present) * 10) / 10 : null; | |||
var trG = document.createElement('tr'); | |||
var overallText = (overall !== null) | |||
? (overall.toFixed ? overall.toFixed(1) : (Math.round(overall*10)/10)) | |||
: '–'; | |||
trG.innerHTML = '<td><strong>Gesamt</strong></td><td><strong>' + overallText + '</strong></td><td>–</td>'; | |||
tbody.appendChild(trG); | |||
table.appendChild(tbody); | |||
while (container.firstChild) container.removeChild(container.firstChild); | |||
container.appendChild(table); | |||
// Optionales Badge füllen, falls vorhanden | |||
var badge = document.getElementById('whisky-overall-badge'); | |||
if (badge && overall !== null) { | |||
badge.textContent = overallText; | |||
} | |||
}).catch(function(){ | |||
container.textContent = 'Konnte Bewertungen nicht laden.'; | |||
}); | |||
} | } | ||
}); | }); | ||