// Composer v2 (Vorbild: message.style / embed-generator): voller Embed-Builder // mit Live-Vorschau im Discord-Look + speicherbaren Vorlagen. import { useEffect, useState } from 'react'; import { apiGet, apiPost, apiPut, apiDelete } from '../api.js'; import { Markdown } from '../markdown.jsx'; const EMPTY_EMBED = { author_name: '', author_icon: '', title: '', url: '', description: '', color: '#f5c518', thumbnail: '', image: '', footer: '', timestamp: true, fields: [], }; /** Builder-State → Discord-Embed-Objekt (nur gesetzte Felder) */ function toApiEmbed(e) { const out = {}; if (e.author_name.trim()) { out.author = { name: e.author_name.trim() }; if (e.author_icon.trim()) out.author.icon_url = e.author_icon.trim(); } if (e.title.trim()) out.title = e.title.trim(); if (e.url.trim()) out.url = e.url.trim(); if (e.description.trim()) out.description = e.description; out.color = parseInt(e.color.replace('#', ''), 16) || 0xf5c518; if (e.thumbnail.trim()) out.thumbnail = { url: e.thumbnail.trim() }; if (e.image.trim()) out.image = { url: e.image.trim() }; if (e.footer.trim()) out.footer = { text: e.footer.trim() }; if (e.timestamp) out.timestamp = new Date().toISOString(); const fields = e.fields.filter((f) => f.name.trim() && f.value.trim()); if (fields.length) out.fields = fields; return out; } /** Discord-Embed-Objekt (aus Vorlage) → Builder-State */ function fromApiEmbed(embed) { if (!embed) return { ...EMPTY_EMBED, fields: [] }; return { author_name: embed.author?.name ?? '', author_icon: embed.author?.icon_url ?? '', title: embed.title ?? '', url: embed.url ?? '', description: embed.description ?? '', color: `#${Number(embed.color ?? 0xf5c518).toString(16).padStart(6, '0')}`, thumbnail: embed.thumbnail?.url ?? '', image: embed.image?.url ?? '', footer: embed.footer?.text ?? '', timestamp: Boolean(embed.timestamp), fields: (embed.fields ?? []).map((f) => ({ name: f.name, value: f.value, inline: Boolean(f.inline) })), }; } /** Live-Vorschau im Discord-Look */ function DiscordPreview({ content, embed, useEmbed }) { const hasEmbed = useEmbed && (embed.title || embed.description || embed.author_name || embed.fields.some((f) => f.name)); return (
🤖
d4rkbot APP heute um {new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} Uhr
{content.trim() &&
} {hasEmbed && (
{embed.author_name && (
{embed.author_icon && } {embed.author_name}
)} {embed.title &&
{embed.title}
} {embed.description &&
} {embed.fields.filter((f) => f.name && f.value).length > 0 && (
{embed.fields.filter((f) => f.name && f.value).map((f, i) => (
{f.name}
))}
)} {embed.image && } {(embed.footer || embed.timestamp) && (
{embed.footer}{embed.footer && embed.timestamp ? ' • ' : ''}{embed.timestamp ? 'heute' : ''}
)}
{embed.thumbnail && }
)}
); } export default function EmbedComposer({ channelOptions, flash }) { const [channel, setChannel] = useState(''); const [messageId, setMessageId] = useState(''); const [content, setContent] = useState(''); const [useEmbed, setUseEmbed] = useState(true); const [embed, setEmbed] = useState({ ...EMPTY_EMBED, fields: [] }); const [templates, setTemplates] = useState([]); const [templateName, setTemplateName] = useState(''); useEffect(() => { apiGet('/api/templates').then((d) => setTemplates(d.templates)).catch(() => {}); }, []); const setE = (patch) => setEmbed({ ...embed, ...patch }); const setField = (i, patch) => setE({ fields: embed.fields.map((f, j) => (j === i ? { ...f, ...patch } : f)) }); async function send() { if (!channel) return flash('✗ Kanal wählen'); const apiEmbed = useEmbed ? toApiEmbed(embed) : undefined; if (!content.trim() && !useEmbed) return flash('✗ Text oder Embed nötig'); try { const res = await apiPost('/api/compose', { channel_id: channel, message_id: messageId.trim() || undefined, content, embed: apiEmbed, }); flash(res.edited ? '✓ Aktualisiert' : `✓ Gesendet (ID ${res.message_id})`); if (!res.edited) setMessageId(res.message_id); } catch { flash('✗ Senden fehlgeschlagen'); } } async function saveTemplate() { if (!templateName.trim()) return flash('✗ Vorlagen-Name nötig'); try { const res = await apiPut(`/api/templates/${encodeURIComponent(templateName.trim())}`, { content, embed: useEmbed ? toApiEmbed(embed) : null, }); setTemplates(res.templates); flash('✓ Vorlage gespeichert'); } catch { flash('✗ Vorlage fehlgeschlagen'); } } function loadTemplate(name) { const t = templates.find((x) => x.name === name); if (!t) return; setContent(t.payload.content ?? ''); setUseEmbed(Boolean(t.payload.embed)); setEmbed(fromApiEmbed(t.payload.embed)); setTemplateName(name); } async function removeTemplate() { if (!templateName.trim() || !window.confirm(`Vorlage „${templateName}" löschen?`)) return; const res = await apiDelete(`/api/templates/${encodeURIComponent(templateName.trim())}`).catch(() => null); if (res) { setTemplates(res.templates); setTemplateName(''); flash('✓ Vorlage gelöscht'); } } return (

// Post verfassen

setMessageId(e.target.value)} />
setTemplateName(e.target.value)} />