All checks were successful
Production Build & Deploy / build-and-deploy (push) Successful in 1m3s
185 lines
8.4 KiB
Plaintext
185 lines
8.4 KiB
Plaintext
---
|
|
// src/pages/admin/editor.astro
|
|
import Layout from '../../layouts/Layout.astro';
|
|
import { loadWaasConfigs } from '../../utils/configLoader';
|
|
import { THEME_PRESETS } from '../../utils/themePresets';
|
|
|
|
export const prerender = false;
|
|
|
|
const { searchParams } = Astro.url;
|
|
const pageId = searchParams.get('page_id');
|
|
|
|
// Lade den aktuellen Entwurf oder Fallback auf Live Config
|
|
const userAgent = Astro.request.headers.get('user-agent') ?? '';
|
|
const { siteConfig: draftConfig } = loadWaasConfigs(userAgent, true);
|
|
const pages = (draftConfig as any).pages || [];
|
|
const targetPage = pages.find((p: any) => p.id === pageId) || pages[0];
|
|
const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|
---
|
|
|
|
<Layout title={`Editor: ${targetPage?.title || 'Baukasten'} | N&D Admin`}>
|
|
<div class="h-screen w-screen flex overflow-hidden bg-slate-950 font-sans">
|
|
|
|
<aside class="w-80 border-r border-slate-800 bg-slate-900 flex flex-col justify-between z-20">
|
|
|
|
<div class="p-4 space-y-6 overflow-y-auto">
|
|
<div class="flex items-center justify-between border-b border-slate-800 pb-4">
|
|
<div>
|
|
<a href="/admin/pages" class="text-xs text-slate-400 hover:text-sky-400 transition-colors">← Zurück zur Übersicht</a>
|
|
<h1 class="text-base font-bold text-white mt-1">{targetPage?.title || 'Seite bearbeiten'}</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label class="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Design-Preset</label>
|
|
<select id="preset-selector" class="w-full bg-slate-800 border border-slate-700 rounded-lg p-2.5 text-xs text-white focus:outline-none focus:border-sky-500">
|
|
{Object.entries(THEME_PRESETS).map(([key, preset]) => (
|
|
<option value={key}>{preset.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<label class="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Farbschema</label>
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<button id="btn-mode-dark" class="px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-xs font-bold text-white hover:border-sky-500 transition-colors">🌙 Dark Mode</button>
|
|
<button id="btn-mode-light" class="px-3 py-2 bg-slate-800 border border-slate-700 rounded-lg text-xs font-bold text-slate-300 hover:border-sky-500 transition-colors">☀️ Light Mode</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2 pt-4 border-t border-slate-800">
|
|
<label class="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Sektion hinzufügen</label>
|
|
<div class="flex gap-2">
|
|
<select id="select-new-section-type" class="flex-1 bg-slate-800 border border-slate-700 rounded-lg p-2 text-xs text-white focus:outline-none focus:border-sky-500">
|
|
<option value="HeroSection">Hero Section</option>
|
|
<option value="BentoGrid">Bento Grid</option>
|
|
<option value="ContactSection">Kontakt Formular</option>
|
|
</select>
|
|
<button id="btn-add-section" class="bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold px-3 py-2 rounded-lg text-xs transition-colors">
|
|
+ Hinzufügen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2 pt-4 border-t border-slate-800">
|
|
<label class="text-xs font-semibold text-slate-400 uppercase tracking-wider block">Sektionen verwalten</label>
|
|
<div id="section-list" class="space-y-2">
|
|
{targetPage?.sections?.map((sec: any, index: number) => (
|
|
<div class="p-3 bg-slate-800/80 border border-slate-700/60 rounded-lg text-xs space-y-2 text-slate-200" data-section-id={sec.id}>
|
|
<div class="flex items-center justify-between font-semibold">
|
|
<span class="text-sky-400">#{index + 1} {sec.type}</span>
|
|
<div class="flex items-center gap-1">
|
|
<button class="btn-move-up hover:text-sky-400 px-1" title="Nach oben">⬆️</button>
|
|
<button class="btn-move-down hover:text-sky-400 px-1" title="Nach unten">⬇️</button>
|
|
<button class="btn-delete-section hover:text-red-400 px-1" title="Sektion löschen">🗑️</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-4 border-t border-slate-800 bg-slate-950/50 space-y-2">
|
|
<button id="btn-save-draft" class="w-full bg-slate-800 hover:bg-slate-700 text-slate-200 font-semibold py-2.5 rounded-lg text-xs transition-colors border border-slate-700">
|
|
💾 Entwurf speichern
|
|
</button>
|
|
<button id="btn-publish" class="w-full bg-sky-500 hover:bg-sky-400 text-slate-950 font-bold py-2.5 rounded-lg text-xs transition-colors shadow-lg shadow-sky-500/20">
|
|
🚀 Live Veröffentlichen
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="flex-1 bg-slate-950 flex flex-col relative">
|
|
<div class="h-10 bg-slate-900 border-b border-slate-800 flex items-center justify-between px-4 text-xs text-slate-400">
|
|
<span>Vorschau-Route: <code class="text-sky-400 font-mono">{previewUrl}</code></span>
|
|
<span id="editor-status-badge" class="text-amber-400 bg-amber-950/60 border border-amber-800/60 px-2 py-0.5 rounded text-[10px]">Entwurf aktiv</span>
|
|
</div>
|
|
<iframe
|
|
id="preview-iframe"
|
|
src={previewUrl}
|
|
class="w-full h-full border-none bg-slate-950"
|
|
title="Live Website Preview"
|
|
></iframe>
|
|
</main>
|
|
|
|
</div>
|
|
</Layout>
|
|
|
|
<script>
|
|
const iframe = document.getElementById('preview-iframe') as HTMLIFrameElement;
|
|
const statusBadge = document.getElementById('editor-status-badge');
|
|
|
|
// 1. PostMessage helper
|
|
function sendToIframe(type: string, payload: any) {
|
|
if (iframe?.contentWindow) {
|
|
iframe.contentWindow.postMessage({ type, payload }, '*');
|
|
}
|
|
}
|
|
|
|
// 2. Draft Speichern
|
|
document.getElementById('btn-save-draft')?.addEventListener('click', async () => {
|
|
const res = await fetch('/api/editor/save-draft', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ updated_at: new Date().toISOString() })
|
|
});
|
|
|
|
if (res.ok && statusBadge) {
|
|
statusBadge.textContent = 'Entwurf gespeichert';
|
|
statusBadge.className = 'text-sky-400 bg-sky-950/60 border border-sky-800/60 px-2 py-0.5 rounded text-[10px]';
|
|
}
|
|
});
|
|
|
|
// 3. Veröffentlichen
|
|
document.getElementById('btn-publish')?.addEventListener('click', async () => {
|
|
const res = await fetch('/api/editor/publish', { method: 'POST' });
|
|
if (res.ok && statusBadge) {
|
|
statusBadge.textContent = 'Live Veröffentlicht!';
|
|
statusBadge.className = 'text-emerald-400 bg-emerald-950/60 border border-emerald-800/60 px-2 py-0.5 rounded text-[10px]';
|
|
sendToIframe('RELOAD_CANVAS', {});
|
|
}
|
|
});
|
|
|
|
// 4. Preset Wechsel
|
|
document.getElementById('preset-selector')?.addEventListener('change', (e: any) => {
|
|
sendToIframe('THEME_UPDATE', { preset: e.target.value });
|
|
});
|
|
|
|
// 5. Event: Sektion hinzufügen
|
|
document.getElementById('btn-add-section')?.addEventListener('click', async () => {
|
|
const sectionType = (document.getElementById('select-new-section-type') as HTMLSelectElement).value;
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const pageId = urlParams.get('page_id');
|
|
|
|
const res = await fetch('/api/editor/add-section', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ page_id: pageId, section_type: sectionType })
|
|
});
|
|
|
|
if (res.ok) {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
|
|
// 6. Event: Sektion löschen
|
|
document.querySelectorAll('.btn-delete-section').forEach(btn => {
|
|
btn.addEventListener('click', async (e: any) => {
|
|
const card = e.target.closest('[data-section-id]');
|
|
const sectionId = card?.getAttribute('data-section-id');
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const pageId = urlParams.get('page_id');
|
|
|
|
const res = await fetch('/api/editor/delete-section', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ page_id: pageId, section_id: sectionId })
|
|
});
|
|
|
|
if (res.ok) {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
});
|
|
</script> |