feat: Rubrik-Dropdown beim Live-Stellen — freie Kategoriewahl

- Neuer <select id='rubricSelect'> neben dem 🔴 Live Toggle
- Alle 10 Rubriken: Nachrichten, Angebot, Crowdfunding, Branche,
  Neuerscheinungen, Ankündigungen, Zubehör, Brettspieltest, Magazin, Video, Podcast
- Bei Live+Haken: Rubrik-Wahl pflicht (Toast wenn 'Rubrik wählen…' gesetzt)
- Backend _handle_publish: nutzt rubric vom Client statt meta category-id
- SAFETY GATE bleibt: ohne Live-Haken → immer Korrektur (catid=61)
- Confirm-Dialog zeigt gewählte Rubrik an
This commit is contained in:
Hermes Agent
2026-06-19 00:38:33 +02:00
parent ced413710f
commit 3e6db33027
+32 -6
View File
@@ -307,6 +307,9 @@ def build_index(articles: list[dict]) -> str:
.live-toggle-label:hover {{ border-color: var(--accent); }}
.live-toggle-label:has(input:checked) {{ background: #fef2f2; border-color: #dc3545; color: #dc3545; }}
.live-toggle-label input {{ accent-color: #dc3545; }}
.rubric-live-select {{ font-family: var(--font); font-size: 0.8rem; padding: 0.35rem 0.7rem; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--text); cursor: pointer; }}
.rubric-live-select:focus {{ outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(32,34,138,0.15); }}
.upload-label {{ display: inline-flex; }}
.token-indicator {{ font-size: 0.7rem; margin-left: 0.6rem; opacity: 0.8; }}
@@ -434,6 +437,20 @@ def build_index(articles: list[dict]) -> str:
<input type="checkbox" id="liveToggle" onchange="updateLiveMode()">
<span class="live-toggle-text">🔴 Live</span>
</label>
<select id="rubricSelect" class="rubric-live-select" onchange="updateLiveMode()">
<option value="0">📂 Rubrik wählen…</option>
<option value="9">📰 Nachrichten</option>
<option value="64" class="sub">└ Angebot</option>
<option value="65" class="sub">└ Crowdfunding</option>
<option value="66" class="sub">└ Branche</option>
<option value="67" class="sub">└ Neuerscheinungen</option>
<option value="68" class="sub">└ Ankündigungen</option>
<option value="69" class="sub">└ Zubehör</option>
<option value="8">🎲 Brettspieltest</option>
<option value="11">📖 Magazin</option>
<option value="10">🎬 Video</option>
<option value="55">🎙 Podcast</option>
</select>
<button class="btn btn-publish" id="publishBtn" onclick="publishSelected()" disabled{publish_title}>📢 Veröffentlichen</button>
<button class="btn btn-delete" id="deleteBtn" onclick="deleteSelected()" disabled>Markierte löschen</button>
<span class="counter" id="counter">0 markiert</span>
@@ -612,6 +629,12 @@ def build_index(articles: list[dict]) -> str:
const selected = Array.from(document.querySelectorAll('.article-check:checked')).map(cb => cb.value);
if (selected.length === 0) return;
const live = document.getElementById('liveToggle').checked;
const rubric = document.getElementById('rubricSelect').value;
if (live && rubric === '0') {{
showToast('❌ Bitte eine Rubrik wählen (Dropdown rechts neben 🔴 Live)', true);
return;
}}
// Readiness-Warnung (soft, blockiert nicht)
const notReady = [];
@@ -623,7 +646,8 @@ def build_index(articles: list[dict]) -> str:
}});
let confirmMsg;
if (live) {{
confirmMsg = '🔴 LIVE: ' + selected.length + ' Artikel ÖFFENTLICH veröffentlichen?\\n\\nDies ist für 10.000+ Leser sichtbar!';
const rubricName = document.getElementById('rubricSelect').selectedOptions[0].text;
confirmMsg = '🔴 LIVE in »' + rubricName + '«: ' + selected.length + ' Artikel ÖFFENTLICH veröffentlichen?\\n\\nDies ist für 10.000+ Leser sichtbar!';
}} else {{
confirmMsg = selected.length + ' Artikel auf KORREKTUR hochladen?\\n\\nNur Redaktion sichtbar. Sicher.';
}}
@@ -640,7 +664,7 @@ def build_index(articles: list[dict]) -> str:
const resp = await fetch('/publish', {{
method: 'POST',
headers: {{'Content-Type': 'application/json', 'Authorization': 'Basic ' + auth}},
body: JSON.stringify({{files: selected, live: live}})
body: JSON.stringify({{files: selected, live: live, rubric: parseInt(rubric)}})
}});
const data = await resp.json();
if (data.ok) {{
@@ -1011,6 +1035,7 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
data = json.loads(body)
files = data.get("files", [])
allow_live = data.get("live", False) # SAFETY GATE: Nur wenn explizit true
rubric = data.get("rubric", 0) # 0 = nicht gesetzt
except json.JSONDecodeError:
self._json_reply(400, {"ok": False, "error": "Invalid JSON"})
return
@@ -1091,10 +1116,11 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
jid_match = re.search(r'<meta\s+name="joomla-id"\s+content="(\d+)"', html_content, re.IGNORECASE)
joomla_id = jid_match.group(1) if jid_match else None
# ── SAFETY GATE: Ohne live=true ALLES auf Korrektur ─────
# Extract category from meta NUR wenn live explizit erlaubt
catid = 61 # Default: Korrektur (Redaktion-only, nicht öffentlich)
if allow_live:
# ── Kategorie: Rubrik vom Dropdown oder meta ──────────
catid = 61 # Default: Korrektur
if allow_live and rubric > 0:
catid = rubric
elif allow_live:
cat_match = re.search(r'<meta\s+name="category-id"\s+content="(\d+)"', html_content, re.IGNORECASE)
if cat_match:
catid = int(cat_match.group(1))