fix: Like-JS neu (kein innerHTML-Überschreiben), Hallenbadge Anthrazit, Datum links in Meta-Zeile
Quality Gate / 🛡️ Lint + Type Check + Tests (push) Failing after 14m37s

This commit is contained in:
Hermes Agent
2026-06-25 09:51:02 +02:00
parent a94dbe2659
commit 4257a11e25
+26 -27
View File
@@ -2877,20 +2877,17 @@ def public_frontend():
except: except:
pass pass
# Metadata-Leiste: Avatar + Autor · Views · Comments · Like # Metadata-Leiste: Avatar + Autor · Zeit · Views · Comments · Like
meta_html = f"""<div class="card-meta"> meta_html = f"""<div class="card-meta">
<div class="meta-author">{avatar_html} {name}</div> <div class="meta-author">{avatar_html} <span class="meta-author-name">{name}</span><span class="meta-date">{time_html}</span></div>
<div class="meta-spacer"></div> <div class="meta-spacer"></div>
<span class="card-meta-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>{r['views'] or 0}</span> <span class="card-meta-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>{r['views'] or 0}</span>
<span class="card-meta-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>{r['comment_count'] or 0}</span> <span class="card-meta-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>{r['comment_count'] or 0}</span>
<button class="stat-like-btn" data-id="{r['id']}" data-likes="{r['likes'] or 0}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D1D5DB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg> {r['likes'] or 0}</button> <button class="stat-like-btn" data-id="{r['id']}" data-likes="{r['likes'] or 0}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D1D5DB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg> <span class="like-count">{r['likes'] or 0}</span></button>
</div>""" </div>"""
is_ausverkauft = r["category"] == "ausverkauft" and r["spiel_titel"] is_ausverkauft = r["category"] == "ausverkauft" and r["spiel_titel"]
# Zeit-Block oben (für Text-only Karten)
time_top = f'<div class="story-time">{time_html}</div>' if time_html else ''
# Badge-Titel vorbereiten (vermeidet Escaping-Probleme) # Badge-Titel vorbereiten (vermeidet Escaping-Probleme)
badge_title = f'<span class="badge-title"> · {r["spiel_titel"]}</span>' if r["spiel_titel"] else "" badge_title = f'<span class="badge-title"> · {r["spiel_titel"]}</span>' if r["spiel_titel"] else ""
@@ -2898,8 +2895,7 @@ def public_frontend():
# Text-only card # Text-only card
cards += f"""<a href="/beitrag/{r['id']}" class="card-link"> cards += f"""<a href="/beitrag/{r['id']}" class="card-link">
<article class="story-card text-only"> <article class="story-card text-only">
{time_top} {f'<div class="story-halle-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#D1D5DB" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> {r["halle"]}</div>' if r["halle"] else ''}
{f'<div class="story-halle-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> {r["halle"]}</div>' if r["halle"] else ''}
{f'<div class="story-ausverkauft-badge"><span class="badge-icon">🚫</span> AUSVERKAUFT{badge_title}</div>' if r["category"] == "ausverkauft" else ''} {f'<div class="story-ausverkauft-badge"><span class="badge-icon">🚫</span> AUSVERKAUFT{badge_title}</div>' if r["category"] == "ausverkauft" else ''}
<div class="card-content"> <div class="card-content">
<p class="story-preview">{preview}</p> <p class="story-preview">{preview}</p>
@@ -2911,8 +2907,7 @@ def public_frontend():
# Media card # Media card
cards += f"""<article class="story-card"> cards += f"""<article class="story-card">
{media_first} {media_first}
{f'<div class="story-time-overlay">{time_html}</div>' if time_html else ''} {f'<div class="story-halle-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#D1D5DB" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> {r["halle"]}</div>' if r["halle"] else ''}
{f'<div class="story-halle-badge"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> {r["halle"]}</div>' if r["halle"] else ''}
{f'<div class="story-ausverkauft-badge"><span class="badge-icon">🚫</span> AUSVERKAUFT{badge_title}</div>' if r["category"] == "ausverkauft" else ''} {f'<div class="story-ausverkauft-badge"><span class="badge-icon">🚫</span> AUSVERKAUFT{badge_title}</div>' if r["category"] == "ausverkauft" else ''}
<div class="card-content"> <div class="card-content">
<p class="story-preview">{preview}</p> <p class="story-preview">{preview}</p>
@@ -2977,9 +2972,9 @@ def public_frontend():
.story-card video { width:100%; height:auto; max-height:600px; object-fit:contain; display:block; background:#000; } .story-card video { width:100%; height:auto; max-height:600px; object-fit:contain; display:block; background:#000; }
.story-card audio { width:100%; margin-top:0.5rem; } .story-card audio { width:100%; margin-top:0.5rem; }
/* Hall badge subtle gray outline */ /* Hall badge anthracite, light text */
.story-halle-badge { position:absolute; top:0.5rem; right:0.5rem; background:rgba(255,255,255,0.92); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); color:#6B7280; padding:0.2rem 0.55rem; border-radius:6px; font-size:0.72rem; font-weight:600; letter-spacing:0.02em; z-index:5; border:1px solid rgba(0,0,0,0.06); } .story-halle-badge { position:absolute; top:0.5rem; right:0.5rem; background:#1F2937; color:#F9FAFB; padding:0.2rem 0.55rem; border-radius:6px; font-size:0.72rem; font-weight:600; letter-spacing:0.02em; z-index:5; }
.story-halle-badge svg { stroke:#9CA3AF !important; } .story-halle-badge svg { stroke:#D1D5DB !important; }
/* Ausverkauft badge keep red but cleaner */ /* Ausverkauft badge keep red but cleaner */
.story-ausverkauft-badge { position:absolute; top:0; left:0; right:0; z-index:7; background:linear-gradient(180deg,#EF4444 0%,#DC2626 100%); color:#fff; padding:0.4rem 1rem; font-size:0.75rem; font-weight:700; letter-spacing:0.08em; text-align:center; box-shadow:0 2px 8px rgba(220,38,38,0.3); display:flex; align-items:center; justify-content:center; gap:0.4rem; flex-wrap:wrap; } .story-ausverkauft-badge { position:absolute; top:0; left:0; right:0; z-index:7; background:linear-gradient(180deg,#EF4444 0%,#DC2626 100%); color:#fff; padding:0.4rem 1rem; font-size:0.75rem; font-weight:700; letter-spacing:0.08em; text-align:center; box-shadow:0 2px 8px rgba(220,38,38,0.3); display:flex; align-items:center; justify-content:center; gap:0.4rem; flex-wrap:wrap; }
@@ -3015,6 +3010,8 @@ def public_frontend():
.card-meta-item { display:flex; align-items:center; gap:0.2rem; font-size:0.72rem; color:#9CA3AF; } .card-meta-item { display:flex; align-items:center; gap:0.2rem; font-size:0.72rem; color:#9CA3AF; }
.card-meta-item svg { stroke:#9CA3AF; } .card-meta-item svg { stroke:#9CA3AF; }
.meta-author { display:flex; align-items:center; gap:0.35rem; color:#6B7280; font-size:0.75rem; font-weight:500; } .meta-author { display:flex; align-items:center; gap:0.35rem; color:#6B7280; font-size:0.75rem; font-weight:500; }
.meta-author-name { font-weight:500; }
.meta-date { font-size:0.68rem; color:#9CA3AF; font-weight:400; margin-left:0.5rem; }
.meta-spacer { flex:1; } .meta-spacer { flex:1; }
/* Like button subtle outline */ /* Like button subtle outline */
@@ -3512,14 +3509,14 @@ def public_frontend():
var likedPosts = JSON.parse(localStorage.getItem('likedPosts') || '[]'); var likedPosts = JSON.parse(localStorage.getItem('likedPosts') || '[]');
// Restore liked state on page load // Restore liked state on page load
if (likedPosts.length) { document.querySelectorAll('.stat-like-btn').forEach(function(btn) {
document.querySelectorAll('.stat-like-btn').forEach(function(btn) { var id = parseInt(btn.getAttribute('data-id'));
var id = parseInt(btn.getAttribute('data-id')); if (likedPosts.indexOf(id) !== -1) {
if (likedPosts.indexOf(id) !== -1) { btn.classList.add('liked');
btn.classList.add('liked'); }
} // Touch-Event für Mobile
}); btn.addEventListener('touchend', function(e) { e.preventDefault(); btn.click(); });
} });
document.querySelectorAll('.stat-like-btn').forEach(function(btn) { document.querySelectorAll('.stat-like-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) { btn.addEventListener('click', function(e) {
@@ -3528,19 +3525,21 @@ def public_frontend():
var id = this.getAttribute('data-id'); var id = this.getAttribute('data-id');
if (this.classList.contains('liked')) return; if (this.classList.contains('liked')) return;
this.disabled = true; this.disabled = true;
var self = this;
fetch('/api/like/' + id, {method:'POST'}) fetch('/api/like/' + id, {method:'POST'})
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(data) { .then(function(data) {
if (!data.already) { if (!data.already) {
this.classList.add('liked'); self.classList.add('liked');
this.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#20228a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;margin-right:2px;"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg> ' + data.likes; var countSpan = self.querySelector('.like-count');
this.setAttribute('data-likes', data.likes); if (countSpan) countSpan.textContent = data.likes;
else self.setAttribute('data-likes', data.likes);
likedPosts.push(parseInt(id)); likedPosts.push(parseInt(id));
localStorage.setItem('likedPosts', JSON.stringify(likedPosts)); localStorage.setItem('likedPosts', JSON.stringify(likedPosts));
} }
this.disabled = false; self.disabled = false;
}.bind(this)) })
.catch(function() { this.disabled = false; }.bind(this)); .catch(function() { self.disabled = false; });
}); });
}); });
})(); })();