Komentáře
Modelový případ: web má blog, ale u článků chybí komentáře. Komentáře se nezapékají do statiky —
naprogramuješ je jako klientský JS, který za běhu volá API. Server (build) jen vykreslí prázdnou
kostru + injektuje konfiguraci do data- atributů.
- Modul:
comments(PRO) · runtime - Endpointy:
GET /comments?postSlug=<slug>→{ count, comments:[{ id, authorName, body, isStaff, createdAt, replies:[…] }] }(jen schválené, vlákna 1 úroveň).POST /comments→{ id, ok:true }. Tělo:{ postSlug, authorName, authorEmail, body, parentId?, website }.
Komponenta — src/components/Comments.astro
Sekce “Komponenta — src/components/Comments.astro”---const postSlug = Astro.props.postSlug;const base = import.meta.env.PUBLIC_API_BASE_URL; // https://api.contenta.cz/api/v1const key = import.meta.env.PUBLIC_SITE_KEY; // rk_…const slug = import.meta.env.PUBLIC_SITE_SLUG; // muj-webconst ok = base && key && slug;---{ok && ( <section data-comments data-base={base} data-key={key} data-slug={slug} data-post={postSlug}> <h2>Komentáře</h2> <div data-list><p>Načítám…</p></div> <form data-form> <input name="authorName" required placeholder="Jméno" aria-label="Jméno" autocomplete="name" /> <input name="authorEmail" type="email" required placeholder="E-mail (nezveřejníme)" aria-label="E-mail" autocomplete="email" /> <textarea name="body" required placeholder="Váš komentář…" aria-label="Komentář"></textarea> <!-- honeypot: skrýt CSS-kou, ne hidden --> <input name="website" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px" /> <button type="submit">Odeslat komentář</button> <span data-status role="status" aria-live="polite"></span> </form> </section>)}<script> const root = document.querySelector("[data-comments]"); if (root) { const { base, key, slug, post } = root.dataset; const list = root.querySelector("[data-list]"); const form = root.querySelector("[data-form]"); const status = root.querySelector("[data-status]"); const esc = (s) => { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; };
async function load() { const r = await fetch(`${base}/public/sites/${slug}/comments?postSlug=${post}`, { headers: { "X-Site-Key": key } }); const { data } = await r.json(); list.innerHTML = data.comments.length ? data.comments.map((c) => ` <div class="comment"> <strong>${esc(c.authorName)}</strong>${c.isStaff ? " (provozovatel)" : ""} <p>${esc(c.body)}</p> ${c.replies.map((rp) => `<div class="reply"><strong>${esc(rp.authorName)}</strong><p>${esc(rp.body)}</p></div>` ).join("")} </div>`).join("") : "<p>Zatím tu nejsou žádné komentáře. Buďte první.</p>"; } form.addEventListener("submit", async (e) => { e.preventDefault(); const f = new FormData(form); const res = await fetch(`${base}/public/sites/${slug}/comments`, { method: "POST", headers: { "Content-Type": "application/json", "X-Site-Key": key }, body: JSON.stringify({ postSlug: post, authorName: f.get("authorName"), authorEmail: f.get("authorEmail"), body: f.get("body"), website: f.get("website"), }), }); status.textContent = res.ok ? "Děkujeme! Komentář čeká na schválení." : "Nepodařilo se odeslat."; if (res.ok) form.reset(); }); load(); }</script>Na detailu blogu pak jen:
<Comments postSlug={post.slug} />Napojení na admin
Sekce “Napojení na admin”Komentář dorazí pod web jako PENDING → v modulu Komentáře ho schválíš / zamítneš / označíš spam /
odpovíš jako provozovatel. Po schválení ho GET vrátí a objeví se na webu. E-mail se nezveřejňuje;
body renderuj jako text (escapuj).