Social: Vorschläge merken (📌), Post-Link nachträglich eintragen, Quick-Post ohne prompt(), SW by-v369

This commit is contained in:
rene 2026-04-25 10:23:17 +02:00
parent 092230c4e1
commit e2bb1a4b2d
3 changed files with 130 additions and 16 deletions

View file

@ -1052,6 +1052,27 @@ async def get_suggestions(user=Depends(require_social_media)):
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# POST /api/social/ideas/save — Vorschlag als Idee speichern (zurückstellen)
# ------------------------------------------------------------------
@router.post("/ideas/save", status_code=201)
async def save_idea(data: dict, user=Depends(require_social_media)):
"""Speichert einen KI-Vorschlag als Idee in der DB (status='idea')."""
topic = (data.get("topic") or data.get("thema") or "").strip()
platform = data.get("platform", "both")
fmt = data.get("format", "post")
category = data.get("category")
if not topic:
raise HTTPException(400, "topic fehlt.")
with db() as conn:
cur = conn.execute(
"""INSERT INTO social_content
(created_by, platform, format, topic, status, category, source)
VALUES (?,?,?,?,'idea',?,'saved_suggestion')""",
(user["id"], platform, fmt, topic, category),
)
return {"id": cur.lastrowid, "topic": topic, "status": "idea"}
# GET /api/social/content — alle Einträge # GET /api/social/content — alle Einträge
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@router.get("/content") @router.get("/content")

View file

@ -422,13 +422,21 @@ window.Page_social = (() => {
padding:2px 6px;border-radius:4px">${_PL[idea.platform]||idea.platform}</span> padding:2px 6px;border-radius:4px">${_PL[idea.platform]||idea.platform}</span>
</div> </div>
</div> </div>
<button class="btn btn-primary btn-sm sm-use" <div style="display:flex;flex-direction:column;gap:5px;flex-shrink:0">
style="flex-shrink:0;font-size:11px;padding:6px 10px;min-height:34px; <button class="btn btn-primary btn-sm sm-use"
white-space:nowrap" style="font-size:11px;padding:6px 10px;min-height:34px;white-space:nowrap"
data-thema="${_esc(idea.thema)}" data-thema="${_esc(idea.thema)}"
data-format="${idea.format||'post'}" data-format="${idea.format||'post'}"
data-platform="${idea.platform||'both'}"> data-platform="${idea.platform||'both'}">
Nutzen </button> Nutzen </button>
<button class="btn btn-secondary btn-sm sm-save-idea"
style="font-size:10px;padding:4px 8px;min-height:26px;white-space:nowrap"
data-thema="${_esc(idea.thema)}"
data-format="${idea.format||'post'}"
data-platform="${idea.platform||'both'}"
data-category="${_esc(idea.category||'')}">
📌 Merken</button>
</div>
</div> </div>
</div>`).join(''); </div>`).join('');
@ -453,6 +461,28 @@ window.Page_social = (() => {
} }
}); });
}); });
box.querySelectorAll('.sm-save-idea').forEach(btn => {
btn.addEventListener('click', async e => {
e.stopPropagation();
btn.disabled = true;
btn.textContent = '…';
try {
await API.post('/social/ideas/save', {
topic: btn.dataset.thema,
format: btn.dataset.format,
platform: btn.dataset.platform,
category: btn.dataset.category || undefined,
});
btn.textContent = '✓ Gemerkt';
btn.style.background = 'var(--c-success)';
btn.style.color = '#fff';
btn.style.borderColor = 'var(--c-success)';
} catch {
btn.textContent = '📌 Merken';
btn.disabled = false;
}
});
});
box.querySelectorAll('.sm-idea').forEach(card => { box.querySelectorAll('.sm-idea').forEach(card => {
card.addEventListener('mouseenter', () => card.style.borderColor = 'var(--c-primary)'); card.addEventListener('mouseenter', () => card.style.borderColor = 'var(--c-primary)');
card.addEventListener('mouseleave', () => card.style.borderColor = 'transparent'); card.addEventListener('mouseleave', () => card.style.borderColor = 'transparent');
@ -1126,7 +1156,19 @@ window.Page_social = (() => {
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:3px; <div style="font-weight:600;font-size:var(--text-sm);margin-bottom:3px;
line-height:1.3">${_esc(c.topic)}</div> line-height:1.3">${_esc(c.topic)}</div>
${c.hook ? `<div style="font-size:11px;color:var(--c-text-secondary); ${c.hook ? `<div style="font-size:11px;color:var(--c-text-secondary);
font-style:italic">🎣 ${_esc(c.hook)}</div>` : ''} font-style:italic;margin-bottom:2px">🎣 ${_esc(c.hook)}</div>` : ''}
${c.post_url
? `<a href="${_esc(c.post_url)}" target="_blank" rel="noopener"
style="font-size:10px;color:var(--c-primary);display:inline-flex;
align-items:center;gap:3px;margin-top:2px">
🔗 Post ansehen</a>`
: c.status === 'published'
? `<button class="sm-add-url" data-id="${c.id}"
style="font-size:10px;color:var(--c-text-muted);background:none;
border:none;cursor:pointer;padding:0;margin-top:2px;
text-align:left;text-decoration:underline">
+ Link eintragen</button>`
: ''}
</div> </div>
<div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0"> <div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0">
${c.status !== 'published' ? ` ${c.status !== 'published' ? `
@ -1173,14 +1215,65 @@ window.Page_social = (() => {
</div>`).join('')}`; </div>`).join('')}`;
el.querySelectorAll('[data-f]').forEach(b => b.addEventListener('click', () => load(b.dataset.f))); el.querySelectorAll('[data-f]').forEach(b => b.addEventListener('click', () => load(b.dataset.f)));
el.querySelectorAll('.sm-quick-post').forEach(b => b.addEventListener('click', async () => {
const url = prompt('Post-URL (optional, leer lassen wenn keine):', '') ?? null; // Quick-Post: Inline-Form statt prompt()
await API.patch(`/social/content/${b.dataset.id}`, { el.querySelectorAll('.sm-quick-post').forEach(b => b.addEventListener('click', () => {
status: 'published', const id = b.dataset.id;
published_at: new Date().toISOString().slice(0,16), UI.modal.open({
post_url: url || undefined, title: '📤 Als gepostet markieren',
body: `
<div class="form-group">
<label class="form-label">Datum</label>
<input class="form-control" type="date" id="qp-date-${id}"
value="${new Date().toISOString().slice(0,10)}">
</div>
<div class="form-group">
<label class="form-label">Post-Link <span style="color:var(--c-text-muted)">(optional)</span></label>
<input class="form-control" type="url" id="qp-url-${id}"
placeholder="https://www.instagram.com/p/…">
</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="qp-cancel">Abbrechen</button>
<button class="btn btn-primary flex-1" id="qp-ok-${id}"> Bestätigen</button>`,
});
document.getElementById('qp-cancel')?.addEventListener('click', UI.modal.close);
document.getElementById(`qp-ok-${id}`)?.addEventListener('click', async () => {
const date = document.getElementById(`qp-date-${id}`)?.value
|| new Date().toISOString().slice(0,10);
const url = document.getElementById(`qp-url-${id}`)?.value || undefined;
await API.patch(`/social/content/${id}`, {
status: 'published',
published_at: date,
post_url: url,
});
UI.modal.close();
load(filter);
});
}));
// "Link eintragen" für bereits veröffentlichte Posts ohne URL
el.querySelectorAll('.sm-add-url').forEach(b => b.addEventListener('click', () => {
const id = b.dataset.id;
UI.modal.open({
title: '🔗 Post-Link eintragen',
body: `
<div class="form-group">
<label class="form-label">Link zum veröffentlichten Post</label>
<input class="form-control" type="url" id="au-url-${id}"
placeholder="https://www.instagram.com/p/…" autofocus>
</div>`,
footer: `
<button class="btn btn-secondary flex-1" id="au-cancel">Abbrechen</button>
<button class="btn btn-primary flex-1" id="au-ok-${id}">💾 Speichern</button>`,
});
document.getElementById('au-cancel')?.addEventListener('click', UI.modal.close);
document.getElementById(`au-ok-${id}`)?.addEventListener('click', async () => {
const url = document.getElementById(`au-url-${id}`)?.value;
if (!url) return;
await API.patch(`/social/content/${id}`, { post_url: url });
UI.modal.close();
load(filter);
}); });
load(filter);
})); }));
el.querySelectorAll('.sm-exp').forEach(b => b.addEventListener('click', () => { el.querySelectorAll('.sm-exp').forEach(b => b.addEventListener('click', () => {
const d = el.querySelector(`#sm-d-${b.dataset.id}`); const d = el.querySelector(`#sm-d-${b.dataset.id}`);

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v368'; const CACHE_VERSION = 'by-v369';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten