Wiki-Foto-System: Gallery-Flow, Community-Fotos, Wiki-Fotos-Badge

- review_submission: Fotos ins gallery/-Verzeichnis statt breeds/ kopieren;
  foto_url der Rasse nur überschreiben wenn noch keins vorhanden (Erstbild)
- Rassen-Detail-API: user_fotos (approved submissions) mitliefern
- Rassen-Listen-API: user_foto-Subquery als Fallback wenn foto_url leer
- achievements: neue Badge-Kategorie "Wiki-Fotos" (bronze 1, silber 3, gold 10)
  mit wiki_fotos-Metrik in check_and_award und my_achievements
- Badge-Check + Push nach Foto-Approval
- wiki.js: Karten-Bild nutzt r.foto_url || r.user_foto
- wiki.js: Detail-Ansicht zeigt Community-Foto-Galerie (scrollbar, clickable)
- Dockerfile: breeds/gallery + breeds/submissions im Image anlegen
- SW by-v366, APP_VER 351
This commit is contained in:
rene 2026-04-25 09:53:24 +02:00
parent b608d5635f
commit 6064a1d750
6 changed files with 111 additions and 43 deletions

View file

@ -371,10 +371,11 @@ window.Page_wiki = (() => {
const _DOG_SILHOUETTE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="48" height="48" fill="currentColor" aria-hidden="true"><path d="M200,56H180V48a12,12,0,0,0-24,0v8H132a44,44,0,0,0-43.3,36.3L80,140H60a28,28,0,0,0-28,28v16a12,12,0,0,0,12,12H204a12,12,0,0,0,12-12V100A44.05,44.05,0,0,0,200,56Zm20,128H44V168a4,4,0,0,1,4-4H84.9l4.16-41.57A20,20,0,0,1,108.86,104H156a4,4,0,0,1,0,8H128a4,4,0,0,0,0,8h28a12,12,0,0,0,12-12,11.84,11.84,0,0,0-.1-1.52A20,20,0,0,1,196,124Zm0-60a20,20,0,0,1-7.77,15.82A27.84,27.84,0,0,0,180,132V120a12,12,0,0,1,12-12h16a4,4,0,0,0,4-4V100A20,20,0,0,1,220,124ZM168,48a4,4,0,0,1,8,0V72H168Z"/></svg>`;
function _breedCardHtml(r) {
const photoHtml = r.foto_url
? `<img class="wiki-breed-photo" src="${_esc(r.foto_url)}" loading="lazy" alt="${_esc(r.name)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">`
const fotoUrl = r.foto_url || r.user_foto || '';
const photoHtml = fotoUrl
? `<img class="wiki-breed-photo" src="${_esc(fotoUrl)}" loading="lazy" alt="${_esc(r.name)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">`
: '';
const fallbackHtml = `<div class="wiki-breed-photo-fallback" style="${r.foto_url ? 'display:none' : ''}">${_DOG_SILHOUETTE}</div>`;
const fallbackHtml = `<div class="wiki-breed-photo-fallback" style="${fotoUrl ? 'display:none' : ''}">${_DOG_SILHOUETTE}</div>`;
return `
<div class="wiki-breed-card" data-slug="${_esc(r.slug)}">
@ -727,10 +728,32 @@ window.Page_wiki = (() => {
const berichteHtml = _renderBerichteHtml(rasse.berichte || [], slug);
const userFotosHtml = (rasse.user_fotos || []).length
? `<div style="margin-top:var(--space-3)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
font-weight:700;text-transform:uppercase;letter-spacing:.05em;
margin-bottom:var(--space-2)">📸 Community-Fotos</div>
<div style="display:flex;gap:var(--space-2);overflow-x:auto;padding-bottom:4px">
${rasse.user_fotos.map(f => `
<div style="flex-shrink:0">
<img src="${_esc(f.foto_url)}" alt="${_esc(f.user_name)}"
style="height:80px;width:80px;object-fit:cover;
border-radius:var(--radius-md);cursor:pointer"
onclick="document.querySelector('.wiki-detail-photo')?.setAttribute('src','${_esc(f.foto_url)}')">
<div style="font-size:9px;color:var(--c-text-muted);text-align:center;
margin-top:2px;max-width:80px;overflow:hidden;text-overflow:ellipsis;
white-space:nowrap">von ${_esc(f.user_name)}</div>
</div>
`).join('')}
</div>
</div>`
: '';
const body = `
${/* 1. Hero */ ''}
<div class="wiki-detail-hero" style="text-align:center;margin-bottom:var(--space-4)">
${photoHtml}
${userFotosHtml}
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${_esc(rasse.name)}</h1>
${rasse.herkunft ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${_esc(rasse.herkunft)}</div>` : ''}
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${_esc(rasse.gruppe)}</div>` : ''}