Přeskočit na obsah

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/v1
const key = import.meta.env.PUBLIC_SITE_KEY; // rk_…
const slug = import.meta.env.PUBLIC_SITE_SLUG; // muj-web
const 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} />

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).