Coverbilder (bild_url) + responsives Layout für Mobil/Tablet
Teil 1 — Coverbilder: - Migration 0004_bild_url (neuheiten), 0002_bild_url (planung, archiv mit beiden Spiegel-Tabellen); nullable VARCHAR(500), SQLite↔Postgres portabel, bestehende Zeilen bleiben erhalten - BGG-Client extrahiert Cover aus der Thing-Antwort (bevorzugt <image>, sonst <thumbnail>; protokoll-relative URLs → https) - Sync-Upsert pflegt bild_url beim Update; fehlendes Bild löscht keines - Web-Quellen übernehmen img-URLs aus den Listenelementen (src/data-src, relative Pfade gegen die Seiten-URL aufgelöst); SPIEL-Essen nutzt das BILDER-Feld des JSON-Endpunkts - Verschiebung Neuheiten→Planung und Archivierung/Wiederherstellen schreiben bild_url mit - Anzeige: kleine Vorschaubilder (40x56) in den Listen, groß (max 200px) in Planung-Bearbeiten/Prüf-Dialog; Platzhalter-Div ohne Bild; Hotlinking mit loading=lazy + decoding=async Teil 2 — Responsives Layout: - Basislayout: Burger-Menü (Alpine.js) auf Mobil, Desktop-Leiste ab md: - Listen kollabieren <768px zu Karten-Ansichten (Cover links, Titel + Metadaten rechts, Status-Badge oben); Desktop behält Tabellenform - Touch-Ziele >= 44px auf Mobil, Formulare vollbreit mit Labels oben - Sync-Übersicht/Audit-Log bleiben horizontal scrollbare Tabellen Tests: 15 neue Fälle (tests/test_covers.py) — Migration inkl. Daten-Erhalt, BGG-Parsing gemockt, Quellen-Übernahme, Template-Render; 316 Tests grün. README aktualisiert.
This commit is contained in:
@@ -255,10 +255,32 @@ class NeuheitenPlugin(BasePlugin):
|
||||
def quellen_status_tabelle(conn) -> None:
|
||||
QuellenStatus.__table__.create(conn, checkfirst=True)
|
||||
|
||||
def bild_url_spalte(conn) -> None:
|
||||
"""Migration 0004: Spalte bild_url für Coverbilder (nullable).
|
||||
|
||||
`ALTER TABLE … ADD COLUMN` mit nullable VARCHAR ohne Default ist
|
||||
SQLite- und Postgres-portabel und erhält bestehende Zeilen.
|
||||
"""
|
||||
info = conn.exec_driver_sql("PRAGMA table_info(neuheiten)").fetchall()
|
||||
vorhanden = any(zeile[1] == "bild_url" for zeile in info)
|
||||
if conn.dialect.name == "sqlite" and vorhanden:
|
||||
return
|
||||
if conn.dialect.name != "sqlite":
|
||||
existiert = conn.exec_driver_sql(
|
||||
"SELECT column_name FROM information_schema.columns "
|
||||
"WHERE table_name = 'neuheiten' AND column_name = 'bild_url'"
|
||||
).scalar()
|
||||
if existiert is not None:
|
||||
return
|
||||
conn.exec_driver_sql(
|
||||
"ALTER TABLE neuheiten ADD COLUMN bild_url VARCHAR(500)"
|
||||
)
|
||||
|
||||
return [
|
||||
Migration(version="0001_neuheiten_tabelle", up=neuheiten_tabelle),
|
||||
Migration(version="0002_bgg_id_nullable", up=bgg_id_nullable),
|
||||
Migration(version="0003_quellen_status", up=quellen_status_tabelle),
|
||||
Migration(version="0004_bild_url", up=bild_url_spalte),
|
||||
]
|
||||
|
||||
def navigation(self) -> list[NavEntry]:
|
||||
|
||||
Reference in New Issue
Block a user