{% extends "base.html" %} {% block title %}Redax-WP — Studio{% endblock %} {% block extra_head %} {% endblock %} {% block content %}
{% if last_published %} Letzter Post: {{ last_published.wp_url[:50] if last_published.wp_url else last_published.title[:40] }} — {{ last_published.published_at[:16] if last_published.published_at else '' }} {% endif %} {% if queue_count > 0 %} 📥 {{ queue_count }} RSS-Artikel in Queue {% endif %}

✍️ Artikel-Studio

Vorschau erscheint beim Tippen...
💬 KI-Chat
Schreibe eine Nachricht – die KI kennt deinen Artikel.
🔍 SEO
✨ KI verbessern:
{% set draft_arts = [] %} {% for d in plan_days %} {% for a in plan_articles.get(d, []) %} {% if a.status == 'draft' %}{% set _ = draft_arts.append(a) %}{% endif %} {% endfor %} {% endfor %} {% set undated_drafts = undated_drafts if undated_drafts is defined else [] %} {% if undated_drafts %}

📝 Entwürfe ohne Datum

{% for art in undated_drafts %}
🤖 {{ (art.title or 'Kein Titel')[:55] }} Entwurf
{% endfor %}
{% endif %}

📅 Redaktionsplan — 7 Tage

Artikel per Drag & Drop auf einen anderen Tag ziehen zum Umplanen.

{% set type_icons = {'ki':'🤖','rss':'📡'} %} {% for d in plan_days %} {% set arts = plan_articles.get(d, []) %} {% set is_today = (d == today) %}
{{ d[8:] }}.{{ d[5:7] }}. {% if is_today %} Heute {% endif %} {% if not arts %} — leer {% endif %}
{% for art in arts %}
{{ type_icons.get(art.article_type, '📝') }}
{{ art.post_time }} {{ {'draft':'Entwurf','scheduled':'Geplant','published':'✓ Live'}.get(art.status, art.status) }}
{{ (art.title or 'Kein Titel')[:70] }}
{% if art.seo_description %}
{{ art.seo_description[:100] }}…
{% endif %}
{% if art.wp_post_id %} 🌐 WP-Editor 👁 Vorschau {% endif %} {% if art.status != 'published' %} {% endif %}
{% endfor %}
{% endfor %}
{% endblock %} {% block extra_js %} let currentArticleId = null; let currentWpPostId = null; function updatePreview() { const content = document.getElementById('article-content').value; const title = document.getElementById('article-title').value; const preview = document.getElementById('wp-preview'); preview.innerHTML = (title ? `

${title}

` : '') + content; } function insertImageAtCursor() { const url = document.getElementById('inline-image-url').value.trim(); const ta = document.getElementById('article-content'); if (!url) { showToast('⚠️ Bild-URL eingeben'); return; } const img = `

Bild

`; const start = ta.selectionStart, end = ta.selectionEnd; const before = ta.value.substring(0, start), after = ta.value.substring(end); ta.value = before + img + after; ta.selectionStart = ta.selectionEnd = start + img.length; ta.focus(); updatePreview(); showToast('🖼️ Bild eingefügt'); } function setButtonLoading(btnId, loading) { const btn = document.getElementById(btnId); if (!btn) return; const labels = { 'btn-generate': '🤖 KI generieren', 'btn-polish': 'Verbessern', 'btn-chat': 'Senden' }; if (loading) { btn.dataset.origText = btn.textContent; btn.disabled = true; btn.textContent = 'Bitte warten...'; } else { btn.disabled = false; btn.textContent = btn.dataset.origText || labels[btnId] || 'OK'; } } let chatHistory = []; let lastSuggested = null; function renderChatMessages() { const el = document.getElementById('chat-messages'); if (!el) return; if (chatHistory.length === 0) { el.innerHTML = 'Schreibe eine Nachricht – die KI kennt deinen Artikel.'; return; } el.innerHTML = chatHistory.map(m => { const isUser = m.role === 'user'; return `
${(m.content || '').replace(//g, '>').replace(/\n/g, '
')}
`; }).join(''); el.scrollTop = el.scrollHeight; } async function sendChat() { const input = document.getElementById('chat-input'); const msg = (input?.value || '').trim(); if (!msg) return; const ctx = getArticleData(); const history = chatHistory.slice(-12).map(m => ({ role: m.role, content: m.content })); chatHistory.push({ role: 'user', content: msg }); input.value = ''; renderChatMessages(); document.getElementById('chat-apply-row').classList.add('hidden'); lastSuggested = null; setButtonLoading('btn-chat', true); try { const r = await fetch('/api/chat', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ message: msg, history: history, context: { title: ctx.title, content: ctx.content, seo_title: ctx.seo_title, seo_description: ctx.seo_description, focus_keyword: ctx.focus_keyword } }) }); const d = await r.json().catch(() => ({})); if (d.error) { showToast('❌ ' + d.error); chatHistory.pop(); renderChatMessages(); return; } chatHistory.push({ role: 'assistant', content: d.reply }); if (chatHistory.length > 12) chatHistory = chatHistory.slice(-12); if (d.suggested_content) { lastSuggested = d.suggested_content; document.getElementById('chat-apply-row').classList.remove('hidden'); } renderChatMessages(); } catch (e) { showToast('❌ ' + (e.message || 'Fehler')); chatHistory.pop(); renderChatMessages(); } finally { setButtonLoading('btn-chat', false); } } function applyChatSuggestion() { if (!lastSuggested) return; if (lastSuggested.title) document.getElementById('article-title').value = lastSuggested.title; if (lastSuggested.content) document.getElementById('article-content').value = lastSuggested.content; if (lastSuggested.seo_title) document.getElementById('seo-title').value = lastSuggested.seo_title; if (lastSuggested.seo_description) document.getElementById('seo-description').value = lastSuggested.seo_description; if (lastSuggested.focus_keyword) document.getElementById('focus-keyword').value = lastSuggested.focus_keyword; const c1 = document.getElementById('seo-title-count'); if (c1) c1.textContent = (lastSuggested.seo_title || '').length + '/60'; const c2 = document.getElementById('seo-desc-count'); if (c2) c2.textContent = (lastSuggested.seo_description || '').length + '/155'; updatePreview(); document.getElementById('chat-apply-row').classList.add('hidden'); lastSuggested = null; showToast('✓ Übernommen'); } async function polishArticle() { const instruction = document.getElementById('polish-instruction').value.trim(); const title = document.getElementById('article-title').value; const content = document.getElementById('article-content').value; if (!instruction) { showToast('⚠️ Anweisung eingeben'); return; } if (!content && !title) { showToast('⚠️ Kein Inhalt zum Verbessern'); return; } setButtonLoading('btn-polish', true); const backup = setTimeout(() => setButtonLoading('btn-polish', false), 130000); try { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 120000); const r = await fetch('/api/polish', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ title, content, instruction, seo_title: document.getElementById('seo-title').value, seo_description: document.getElementById('seo-description').value, focus_keyword: document.getElementById('focus-keyword').value }), signal: ctrl.signal }); clearTimeout(t); const d = await r.json().catch(() => ({})); if (d.error) { showToast('❌ ' + d.error); return; } document.getElementById('article-title').value = d.title || ''; document.getElementById('article-content').value = d.content || ''; if (d.seo_title) document.getElementById('seo-title').value = d.seo_title; if (d.seo_description) document.getElementById('seo-description').value = d.seo_description; if (d.focus_keyword) document.getElementById('focus-keyword').value = d.focus_keyword; const c1 = document.getElementById('seo-title-count'); if (c1) c1.textContent = (d.seo_title || '').length + '/60'; const c2 = document.getElementById('seo-desc-count'); if (c2) c2.textContent = (d.seo_description || '').length + '/155'; updatePreview(); document.getElementById('polish-instruction').value = ''; showToast('✨ Text verbessert'); } catch (e) { showToast('❌ ' + (e.name === 'AbortError' ? 'Timeout' : (e.message || 'Fehler'))); } finally { clearTimeout(backup); setButtonLoading('btn-polish', false); } } async function generateArticle() { const source = document.getElementById('source-input').value.trim(); const tone = document.getElementById('tone-select').value; const promptId = document.getElementById('prompt-select')?.value || null; if (!source) { showToast('⚠️ Bitte Quelle eingeben'); return; } setButtonLoading('btn-generate', true); const backup = setTimeout(() => setButtonLoading('btn-generate', false), 130000); try { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 120000); const r = await fetch('/api/generate', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({source, tone, prompt_id: promptId ? parseInt(promptId, 10) : null}), signal: ctrl.signal }); clearTimeout(t); const d = await r.json().catch(() => ({})); if (d.error) { showToast('❌ ' + d.error); return; } document.getElementById('article-title').value = d.title || ''; document.getElementById('article-content').value = d.content || ''; document.getElementById('seo-title').value = d.seo_title || ''; document.getElementById('seo-description').value = d.seo_description || ''; document.getElementById('focus-keyword').value = d.focus_keyword || ''; updatePreview(); if (source.startsWith('http')) fetchOgImage(source); showToast('✅ Artikel generiert'); } catch (e) { showToast('❌ ' + (e.name === 'AbortError' ? 'Timeout (>2 Min)' : (e.message || 'Fehler'))); } finally { clearTimeout(backup); setButtonLoading('btn-generate', false); } } async function fetchOgImage(url) { try { const r = await fetch('/api/og-image', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({url}) }); const d = await r.json(); if (d.image) document.getElementById('featured-image').value = d.image; } catch(e) {} } function getArticleData() { return { id: currentArticleId, title: document.getElementById('article-title').value, content: document.getElementById('article-content').value, source_url: document.getElementById('source-input').value, tone: document.getElementById('tone-select').value, seo_title: document.getElementById('seo-title').value, seo_description: document.getElementById('seo-description').value, focus_keyword: document.getElementById('focus-keyword').value, featured_image_url: document.getElementById('featured-image').value, category_id: document.getElementById('category-select').value || null, article_type: 'ki', }; } async function saveDraft() { const r = await fetch('/api/article/save', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({...getArticleData(), id: currentArticleId, wp_post_id: currentWpPostId, status: 'draft'}) }); const d = await r.json(); if (d.success) { currentArticleId = d.id; if (d.wp_post_id) currentWpPostId = d.wp_post_id; // Vorschau- und Editor-Links anzeigen const linkBox = document.getElementById('wp-draft-link'); if ((d.wp_preview_url || d.wp_edit_url) && linkBox) { const links = []; if (d.wp_edit_url) links.push(` ✎ Im WP-Editor bearbeiten →`); if (d.wp_preview_url) links.push(` 👁 Vorschau ansehen`); linkBox.innerHTML = links.join(''); linkBox.classList.remove('hidden'); } showToast('💾 Entwurf gespeichert & nach WordPress gepusht'); } } async function publishNow() { if (!document.getElementById('article-title').value) { showToast('⚠️ Titel fehlt'); return; } const r = await fetch('/api/article/schedule', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ ...getArticleData(), status: 'scheduled', post_date: new Date().toISOString().split('T')[0], post_time: new Date().toTimeString().slice(0,5), }) }); const d = await r.json(); if (d.success) { currentArticleId = d.id; showToast('🚀 Wird sofort veröffentlicht!'); setTimeout(() => location.reload(), 2000); } } function toggleSchedulePanel() { const p = document.getElementById('schedule-panel'); p.classList.toggle('hidden'); if (!p.classList.contains('hidden')) { const today = new Date().toISOString().split('T')[0]; document.getElementById('schedule-date').value = today; } } async function checkSlot() { const date = document.getElementById('schedule-date').value; const time = document.getElementById('schedule-time').value; if (!date || !time) return; const r = await fetch(`/api/slots/${date}`); const d = await r.json(); const statusEl = document.getElementById('slot-status'); statusEl.classList.remove('hidden'); if (d.taken && d.taken.includes(time)) { statusEl.className = 'text-xs mt-2 text-red-400'; statusEl.textContent = `❌ Slot ${date} ${time} bereits belegt`; document.getElementById('schedule-confirm-btn').disabled = true; } else { statusEl.className = 'text-xs mt-2 text-green-400'; statusEl.textContent = `✅ Slot ${date} ${time} ist frei`; document.getElementById('schedule-confirm-btn').disabled = false; } } async function confirmSchedule() { const post_date = document.getElementById('schedule-date').value; const post_time = document.getElementById('schedule-time').value; if (!post_date || !post_time) { showToast('⚠️ Datum und Uhrzeit wählen'); return; } if (!document.getElementById('article-title').value) { showToast('⚠️ Titel fehlt'); return; } const r = await fetch('/api/article/schedule', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({...getArticleData(), post_date, post_time}) }); const d = await r.json(); if (d.success) { showToast(`📅 Eingeplant: ${post_date} ${post_time}`); setTimeout(() => location.reload(), 1500); } else { showToast('❌ Fehler beim Einplanen'); } } async function loadArticle(id) { if (currentArticleId !== id) { chatHistory = []; lastSuggested = null; if (document.getElementById('chat-apply-row')) document.getElementById('chat-apply-row').classList.add('hidden'); renderChatMessages(); } const r = await fetch(`/api/article/${id}`); const d = await r.json(); currentArticleId = id; currentWpPostId = d.wp_post_id || null; document.getElementById('article-title').value = d.title || ''; document.getElementById('article-content').value = d.content || ''; document.getElementById('source-input').value = d.source_url || ''; document.getElementById('seo-title').value = d.seo_title || ''; document.getElementById('seo-description').value = d.seo_description || ''; document.getElementById('focus-keyword').value = d.focus_keyword || ''; document.getElementById('featured-image').value = d.featured_image_url || ''; if (d.category_id) document.getElementById('category-select').value = d.category_id; // WP-Links wiederherstellen wenn vorhanden const linkBox = document.getElementById('wp-draft-link'); if (d.wp_post_id && linkBox) { const wrap = document.querySelector('[data-wp-admin]'); const wpAdmin = wrap ? wrap.dataset.wpAdmin : ''; const wpBase = wrap ? wrap.dataset.wpUrl : ''; const links = []; if (wpAdmin) links.push(` ✎ Im WP-Editor bearbeiten →`); if (wpBase) links.push(` 👁 Vorschau ansehen`); linkBox.innerHTML = links.join(''); linkBox.classList.remove('hidden'); } else if (linkBox) { linkBox.classList.add('hidden'); } updatePreview(); loadMirrorStatus(id); window.scrollTo({top: 0, behavior: 'smooth'}); } // ── Drag & Drop ── let dragId = null, dragTime = null; function onDragStart(event, id, time) { dragId = id; dragTime = time; event.dataTransfer.effectAllowed = 'move'; const el = document.getElementById(`plan-row-${id}`); setTimeout(() => { if(el) el.style.opacity = '0.4'; }, 0); } function onDragEnd(event) { if (dragId) { const el = document.getElementById(`plan-row-${dragId}`); if (el) el.style.opacity = '1'; } document.querySelectorAll('.drop-zone').forEach(z => z.style.background = ''); } async function onDrop(event, newDate) { event.preventDefault(); document.querySelectorAll('.drop-zone').forEach(z => z.style.background = ''); if (!dragId) return; const r = await fetch(`/api/article/${dragId}/reschedule`, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({post_date: newDate, post_time: dragTime}) }); const d = await r.json(); if (d.success) { showToast(`📅 Verschoben auf ${newDate}`); setTimeout(() => location.reload(), 800); } else { showToast('❌ ' + (d.error || 'Fehler')); } dragId = null; } // ── Board: Umplanen ── function openReschedule(id, date, time) { document.querySelectorAll('[id^="rs-panel-"]').forEach(el => el.classList.add('hidden')); document.getElementById(`rs-panel-${id}`).classList.remove('hidden'); } function closeReschedule(id) { document.getElementById(`rs-panel-${id}`).classList.add('hidden'); } async function confirmReschedule(id) { const date = document.getElementById(`rs-date-${id}`).value; const time = document.getElementById(`rs-time-${id}`).value; const r = await fetch(`/api/article/${id}/reschedule`, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({post_date: date, post_time: time}) }); const d = await r.json(); if (d.success) { showToast(`✅ Umgeplant: ${date} ${time}`); setTimeout(() => location.reload(), 1200); } else showToast('❌ ' + (d.error || 'Fehler')); } async function deleteArticle(id) { if (!confirm('Artikel wirklich löschen?')) return; const r = await fetch(`/api/article/${id}/delete`, {method: 'POST'}); const d = await r.json(); if (d.success) { showToast('🗑️ Gelöscht'); setTimeout(() => location.reload(), 1000); } } // ── Mirror-Status nach Publish anzeigen ── async function loadMirrorStatus(articleId) { try { const r = await fetch(`/api/article/${articleId}/mirrors`); const mirrors = await r.json(); const box = document.getElementById('mirror-status-box'); if (!mirrors.length) { box.classList.add('hidden'); return; } box.innerHTML = '
📡 Mirror-Status:
'; for (const m of mirrors) { const ok = m.status === 'ok'; box.innerHTML += `
${ok ? '✅' : '❌'} ${m.mirror_label} ${ok && m.mirror_url ? `→ ansehen` : ''} ${!ok && m.error ? `(${m.error})` : ''}
`; } box.classList.remove('hidden'); } catch(e) {} } document.addEventListener('DOMContentLoaded', () => { const today = new Date().toISOString().split('T')[0]; document.getElementById('schedule-date').value = today; }); {% endblock %}