feat: replace Cusdis with own comment plugin
Drops the Cusdis embed (postCommentSlot in pageConfig) in favour of the new RustyCMS comment plugin. New Comments.svelte renders the thread, form, replies and inline edit/delete via /api/comments. Count-badge near the post date links to the accordion and uses /api/comments/:id/count. Accordion gets a `lazy` mode so the comment iframe / fetch happens only on first open (avoids the previous "empty on first render" issue with hidden details). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,27 +3,43 @@
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
open?: boolean;
|
||||
lazy?: boolean;
|
||||
id?: string;
|
||||
class?: string;
|
||||
onToggleEvent?: (e: Event) => void;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
label,
|
||||
open = false,
|
||||
lazy = false,
|
||||
id = undefined,
|
||||
class: cls = "",
|
||||
onToggleEvent,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
open?: boolean;
|
||||
id?: string;
|
||||
class?: string;
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
}: Props = $props();
|
||||
|
||||
let everOpened = $state(false);
|
||||
const mounted = $derived(!lazy || open || everOpened);
|
||||
|
||||
function onToggle(e: Event) {
|
||||
if ((e.target as HTMLDetailsElement).open) everOpened = true;
|
||||
onToggleEvent?.(e);
|
||||
}
|
||||
</script>
|
||||
|
||||
<details {id} {open} class="pt-4 border-t border-zinc-200 group {cls}">
|
||||
<details {id} {open} ontoggle={onToggle} class="pt-4 border-t border-zinc-200 group {cls}">
|
||||
<summary class="flex cursor-pointer list-none items-center gap-2 text-sm font-medium select-none">
|
||||
<Icon icon="mdi:chevron-right" class="size-4 shrink-0 transition-transform group-open:rotate-90" />
|
||||
{label}
|
||||
</summary>
|
||||
<div class="mt-4">
|
||||
{@render children()}
|
||||
{#if mounted}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
let {
|
||||
pageId,
|
||||
environment,
|
||||
targetId = "comments-section",
|
||||
class: cls = "",
|
||||
}: {
|
||||
pageId: string;
|
||||
environment?: string;
|
||||
targetId?: string;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let count = $state<number | null>(null);
|
||||
|
||||
const cmsBase = (
|
||||
env.PUBLIC_CMS_URL ||
|
||||
import.meta.env.PUBLIC_CMS_URL ||
|
||||
"http://localhost:3000"
|
||||
).replace(/\/$/, "");
|
||||
|
||||
$effect(() => {
|
||||
const url = `${cmsBase}/api/comments/${encodeURIComponent(pageId)}/count${
|
||||
environment ? `?_environment=${encodeURIComponent(environment)}` : ""
|
||||
}`;
|
||||
let cancelled = false;
|
||||
fetch(url)
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
if (cancelled) return;
|
||||
count = typeof d?.count === "number" ? d.count : 0;
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) count = 0;
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
});
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
const el = document.getElementById(targetId) as HTMLDetailsElement | null;
|
||||
if (!el) return;
|
||||
e.preventDefault();
|
||||
el.open = true;
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<a
|
||||
href="#{targetId}"
|
||||
onclick={onClick}
|
||||
aria-label="Zu Kommentaren springen"
|
||||
class="inline-flex items-center gap-1 text-sm text-zinc-600 hover:text-zinc-900 transition-colors {cls}"
|
||||
>
|
||||
<Icon icon="mdi:comment-outline" class="size-4" />
|
||||
<span class="tabular-nums">{count ?? "…"}</span>
|
||||
</a>
|
||||
@@ -0,0 +1,394 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
let {
|
||||
pageId,
|
||||
environment,
|
||||
class: cls = "",
|
||||
}: {
|
||||
pageId: string;
|
||||
environment?: string;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
type Status = "approved" | "pending" | "spam";
|
||||
|
||||
type PublicComment = {
|
||||
id: string;
|
||||
parent_id?: string;
|
||||
author: string;
|
||||
body: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
status: Status;
|
||||
can_edit: boolean;
|
||||
};
|
||||
|
||||
type ListResponse = {
|
||||
comments: PublicComment[];
|
||||
session_id: string;
|
||||
require_approval: boolean;
|
||||
};
|
||||
|
||||
const cmsBase = (
|
||||
env.PUBLIC_CMS_URL ||
|
||||
import.meta.env.PUBLIC_CMS_URL ||
|
||||
"http://localhost:3000"
|
||||
).replace(/\/$/, "");
|
||||
|
||||
const apiUrl = (suffix = "") =>
|
||||
`${cmsBase}/api/comments/${encodeURIComponent(pageId)}${suffix}${
|
||||
environment ? `?_environment=${encodeURIComponent(environment)}` : ""
|
||||
}`;
|
||||
|
||||
let comments = $state<PublicComment[]>([]);
|
||||
let requireApproval = $state(false);
|
||||
let loading = $state(true);
|
||||
let loadError = $state<string | null>(null);
|
||||
|
||||
let author = $state("");
|
||||
let body = $state("");
|
||||
let website = $state(""); // honeypot
|
||||
let submitting = $state(false);
|
||||
let submitError = $state<string | null>(null);
|
||||
|
||||
let replyTo = $state<string | null>(null);
|
||||
let replyAuthor = $state("");
|
||||
let replyBody = $state("");
|
||||
let replyError = $state<string | null>(null);
|
||||
|
||||
let editingId = $state<string | null>(null);
|
||||
let editBody = $state("");
|
||||
let editError = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
loadComments();
|
||||
});
|
||||
|
||||
async function loadComments() {
|
||||
loading = true;
|
||||
loadError = null;
|
||||
try {
|
||||
const r = await fetch(apiUrl(), { credentials: "include" });
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const data = (await r.json()) as ListResponse;
|
||||
comments = data.comments;
|
||||
requireApproval = data.require_approval;
|
||||
} catch (e) {
|
||||
loadError = e instanceof Error ? e.message : "Load failed";
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function postComment(authorName: string, text: string, parentId: string | null) {
|
||||
const r = await fetch(apiUrl(), {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
author: authorName,
|
||||
body: text,
|
||||
parent_id: parentId,
|
||||
website,
|
||||
}),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const msg = await r.text().catch(() => "");
|
||||
throw new Error(msg || `HTTP ${r.status}`);
|
||||
}
|
||||
return (await r.json()) as PublicComment;
|
||||
}
|
||||
|
||||
async function onSubmit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
if (!author.trim() || !body.trim() || submitting) return;
|
||||
submitting = true;
|
||||
submitError = null;
|
||||
try {
|
||||
await postComment(author, body, null);
|
||||
body = "";
|
||||
await loadComments();
|
||||
} catch (e) {
|
||||
submitError = e instanceof Error ? e.message : "Send failed";
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onReply(e: SubmitEvent, parentId: string) {
|
||||
e.preventDefault();
|
||||
if (!replyAuthor.trim() || !replyBody.trim() || submitting) return;
|
||||
submitting = true;
|
||||
replyError = null;
|
||||
try {
|
||||
await postComment(replyAuthor, replyBody, parentId);
|
||||
replyBody = "";
|
||||
replyTo = null;
|
||||
await loadComments();
|
||||
} catch (e) {
|
||||
replyError = e instanceof Error ? e.message : "Send failed";
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(c: PublicComment) {
|
||||
editingId = c.id;
|
||||
editBody = c.body;
|
||||
editError = null;
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editingId = null;
|
||||
editBody = "";
|
||||
editError = null;
|
||||
}
|
||||
|
||||
async function saveEdit(id: string) {
|
||||
if (!editBody.trim()) return;
|
||||
try {
|
||||
const r = await fetch(`${apiUrl()}`.replace(`/${pageId}`, `/${pageId}/${id}`), {
|
||||
method: "PATCH",
|
||||
credentials: "include",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ body: editBody }),
|
||||
});
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
await loadComments();
|
||||
cancelEdit();
|
||||
} catch (e) {
|
||||
editError = e instanceof Error ? e.message : "Edit failed";
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteComment(id: string) {
|
||||
if (!confirm("Kommentar löschen?")) return;
|
||||
try {
|
||||
const r = await fetch(`${apiUrl()}`.replace(`/${pageId}`, `/${pageId}/${id}`), {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
});
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
await loadComments();
|
||||
} catch (e) {
|
||||
loadError = e instanceof Error ? e.message : "Delete failed";
|
||||
}
|
||||
}
|
||||
|
||||
function fmtDate(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleString("de-DE", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
const topLevel = $derived(comments.filter((c) => !c.parent_id));
|
||||
function repliesOf(parentId: string) {
|
||||
return comments.filter((c) => c.parent_id === parentId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="comments {cls}">
|
||||
{#if loading && comments.length === 0}
|
||||
<p class="text-sm text-zinc-500">Lade Kommentare …</p>
|
||||
{:else if loadError}
|
||||
<p class="text-sm text-red-700">Fehler: {loadError}</p>
|
||||
{:else}
|
||||
{#if topLevel.length === 0}
|
||||
<p class="text-sm text-zinc-500 mb-4">Noch keine Kommentare. Sei der erste.</p>
|
||||
{/if}
|
||||
|
||||
<ul class="space-y-4 mb-8 list-none p-0">
|
||||
{#each topLevel as c (c.id)}
|
||||
<li class="border-l-2 border-zinc-200 pl-4">
|
||||
<div class="flex items-baseline gap-2 text-sm">
|
||||
<strong class="font-medium">{c.author}</strong>
|
||||
<time class="text-zinc-500">{fmtDate(c.created_at)}</time>
|
||||
{#if c.updated_at}
|
||||
<span class="text-zinc-400 text-xs">(bearbeitet)</span>
|
||||
{/if}
|
||||
{#if c.status === "pending"}
|
||||
<span class="text-amber-700 text-xs">(wartet auf Freigabe)</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if editingId === c.id}
|
||||
<textarea
|
||||
bind:value={editBody}
|
||||
class="w-full mt-2 p-2 border border-zinc-300 rounded text-sm"
|
||||
rows="3"
|
||||
></textarea>
|
||||
{#if editError}<p class="text-red-700 text-xs mt-1">{editError}</p>{/if}
|
||||
<div class="mt-1 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => saveEdit(c.id)}
|
||||
class="text-sm text-blue-700 hover:underline"
|
||||
>Speichern</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={cancelEdit}
|
||||
class="text-sm text-zinc-500 hover:underline"
|
||||
>Abbrechen</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="mt-1 whitespace-pre-wrap text-sm">{c.body}</p>
|
||||
{#if c.can_edit}
|
||||
<div class="mt-1 flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => startEdit(c)}
|
||||
class="text-xs text-zinc-600 hover:text-zinc-900"
|
||||
>Bearbeiten</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => deleteComment(c.id)}
|
||||
class="text-xs text-zinc-600 hover:text-red-700"
|
||||
>Löschen</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (replyTo = replyTo === c.id ? null : c.id)}
|
||||
class="text-xs text-zinc-600 hover:text-zinc-900 inline-flex items-center gap-1"
|
||||
>
|
||||
<Icon icon="mdi:reply" class="size-3" />
|
||||
Antworten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if replyTo === c.id}
|
||||
<form
|
||||
onsubmit={(e) => onReply(e, c.id)}
|
||||
class="mt-2 ml-4 space-y-2"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={replyAuthor}
|
||||
placeholder="Name"
|
||||
required
|
||||
maxlength="80"
|
||||
class="w-full p-2 border border-zinc-300 rounded text-sm"
|
||||
/>
|
||||
<textarea
|
||||
bind:value={replyBody}
|
||||
placeholder="Antwort"
|
||||
required
|
||||
rows="2"
|
||||
class="w-full p-2 border border-zinc-300 rounded text-sm"
|
||||
></textarea>
|
||||
{#if replyError}<p class="text-red-700 text-xs">{replyError}</p>{/if}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
class="px-3 py-1 bg-zinc-800 text-white rounded text-sm hover:bg-zinc-700 disabled:opacity-50"
|
||||
>Senden</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
{@const replies = repliesOf(c.id)}
|
||||
{#if replies.length > 0}
|
||||
<ul class="mt-3 ml-4 space-y-3 list-none p-0">
|
||||
{#each replies as r (r.id)}
|
||||
<li class="border-l-2 border-zinc-100 pl-3">
|
||||
<div class="flex items-baseline gap-2 text-sm">
|
||||
<strong class="font-medium">{r.author}</strong>
|
||||
<time class="text-zinc-500">{fmtDate(r.created_at)}</time>
|
||||
{#if r.updated_at}
|
||||
<span class="text-zinc-400 text-xs">(bearbeitet)</span>
|
||||
{/if}
|
||||
{#if r.status === "pending"}
|
||||
<span class="text-amber-700 text-xs">(wartet auf Freigabe)</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if editingId === r.id}
|
||||
<textarea
|
||||
bind:value={editBody}
|
||||
class="w-full mt-2 p-2 border border-zinc-300 rounded text-sm"
|
||||
rows="2"
|
||||
></textarea>
|
||||
{#if editError}<p class="text-red-700 text-xs mt-1">{editError}</p>{/if}
|
||||
<div class="mt-1 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => saveEdit(r.id)}
|
||||
class="text-sm text-blue-700 hover:underline"
|
||||
>Speichern</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={cancelEdit}
|
||||
class="text-sm text-zinc-500 hover:underline"
|
||||
>Abbrechen</button>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="mt-1 whitespace-pre-wrap text-sm">{r.body}</p>
|
||||
{#if r.can_edit}
|
||||
<div class="mt-1 flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => startEdit(r)}
|
||||
class="text-xs text-zinc-600 hover:text-zinc-900"
|
||||
>Bearbeiten</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => deleteComment(r.id)}
|
||||
class="text-xs text-zinc-600 hover:text-red-700"
|
||||
>Löschen</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<form onsubmit={onSubmit} class="space-y-2 border-t border-zinc-200 pt-4">
|
||||
<h3 class="font-medium text-sm">Kommentar schreiben</h3>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={author}
|
||||
placeholder="Name"
|
||||
required
|
||||
maxlength="80"
|
||||
class="w-full p-2 border border-zinc-300 rounded text-sm"
|
||||
/>
|
||||
<textarea
|
||||
bind:value={body}
|
||||
placeholder="Dein Kommentar"
|
||||
required
|
||||
rows="4"
|
||||
class="w-full p-2 border border-zinc-300 rounded text-sm"
|
||||
></textarea>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={website}
|
||||
tabindex="-1"
|
||||
autocomplete="off"
|
||||
aria-hidden="true"
|
||||
class="absolute left-[-9999px]"
|
||||
/>
|
||||
{#if submitError}<p class="text-red-700 text-xs">{submitError}</p>{/if}
|
||||
{#if requireApproval}
|
||||
<p class="text-xs text-zinc-500">Kommentare werden vor Veröffentlichung geprüft.</p>
|
||||
{/if}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
class="px-4 py-2 bg-zinc-800 text-white rounded text-sm hover:bg-zinc-700 disabled:opacity-50"
|
||||
>Senden</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getPostBySlug, getPageConfigBySlug, getPageConfigs } from '$lib/cms';
|
||||
import { getPostBySlug } from '$lib/cms';
|
||||
import {
|
||||
resolveContentImages,
|
||||
ensureTransformedImage,
|
||||
@@ -135,16 +135,6 @@ export const load: PageServerLoad = async ({ params, locals, url, setHeaders })
|
||||
? `https://www.openstreetmap.org/search?query=${encodeURIComponent(eventLocation.text)}`
|
||||
: null;
|
||||
|
||||
const pageConfig =
|
||||
(await getPageConfigBySlug('page-config-default', { locale: 'de' })) ??
|
||||
(await getPageConfigs({ locale: 'de', per_page: 1 }))[0] ??
|
||||
null;
|
||||
const commentSlotRaw =
|
||||
(pageConfig as { postCommentSlot?: string } | null)?.postCommentSlot ?? '';
|
||||
const commentSlot = commentSlotRaw
|
||||
.replace(/\{\{PAGE_ID\}\}/g, slug)
|
||||
.replace(/\{\{PAGE_URL\}\}/g, url.href)
|
||||
.replace(/\{\{PAGE_TITLE\}\}/g, post.headline ?? post.linkName ?? slug);
|
||||
const showCommentSection = (post as { showCommentSection?: boolean }).showCommentSection !== false;
|
||||
|
||||
const jsonLd: Record<string, unknown>[] = [];
|
||||
@@ -263,7 +253,7 @@ export const load: PageServerLoad = async ({ params, locals, url, setHeaders })
|
||||
eventLocationText: eventLocation?.text ?? null,
|
||||
mapEmbedUrl,
|
||||
mapLinkUrl,
|
||||
commentSlot: showCommentSection ? commentSlot : '',
|
||||
commentPageId: showCommentSection ? slug : null,
|
||||
translations,
|
||||
seoTitle: post.seoTitle ?? postHeadline,
|
||||
seoDescription: postDescription,
|
||||
|
||||
@@ -5,21 +5,13 @@
|
||||
import EventBadges from '$lib/components/EventBadges.svelte';
|
||||
import EventMap from '$lib/components/EventMap.svelte';
|
||||
import Accordion from '$lib/components/Accordion.svelte';
|
||||
import CommentCountBadge from '$lib/components/CommentCountBadge.svelte';
|
||||
import Comments from '$lib/components/Comments.svelte';
|
||||
import RustyImage from '$lib/components/RustyImage.svelte';
|
||||
import { t, T } from '$lib/translations';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
function executeScripts(node: HTMLElement) {
|
||||
const scripts = node.querySelectorAll("script");
|
||||
scripts.forEach((old) => {
|
||||
const s = document.createElement("script");
|
||||
for (const { name, value } of old.attributes) s.setAttribute(name, value);
|
||||
s.text = old.textContent ?? "";
|
||||
old.replaceWith(s);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -34,7 +26,7 @@
|
||||
|
||||
<div class="md:flex gap-2 items-end">
|
||||
{#if data.rawPostImageUrl}
|
||||
<div class="overflow-hidden inline-block my-4 border border-white self-start ring-1 ring-black/50 shadow-lg">
|
||||
<div class="overflow-hidden inline-block my-4 border border-white self-start ring-1 ring-black/50 shadow-lg aspect-square">
|
||||
<RustyImage
|
||||
src={data.rawPostImageUrl}
|
||||
focal={data.postImageFocal}
|
||||
@@ -49,7 +41,7 @@
|
||||
/>
|
||||
</div>
|
||||
{:else if data.postImageUrl}
|
||||
<div class="overflow-hidden inline-block my-4 border border-white self-start ring-1 ring-black/50 shadow-lg">
|
||||
<div class="overflow-hidden inline-block my-4 border border-white self-start ring-1 ring-black/50 shadow-lg aspect-square">
|
||||
<img
|
||||
src={data.postImageUrl}
|
||||
alt=""
|
||||
@@ -62,9 +54,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-2 mb-4! md:mb-1 min-w-0 gap-2">
|
||||
{#if data.postDate}
|
||||
{#if data.postDate || data.commentPageId}
|
||||
<div class="flex flex-nowrap gap-2 items-center shrink-0 mb-2">
|
||||
<Tag label={data.postDate} variant="date" />
|
||||
{#if data.postDate}
|
||||
<Tag label={data.postDate} variant="date" />
|
||||
{/if}
|
||||
{#if data.commentPageId}
|
||||
<CommentCountBadge pageId={data.commentPageId} targetId="comments-section" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-nowrap gap-2 items-center shrink-0 p-1 pb-4 -ml-1 overflow-x-auto overflow-y-hidden">
|
||||
@@ -148,10 +145,10 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if data.commentSlot}
|
||||
<Accordion label={t(data.translations, T.post_comments)} class="mt-6">
|
||||
<div class="p-8 border border-zinc-200 rounded-lg" use:executeScripts>
|
||||
{@html data.commentSlot}
|
||||
{#if data.commentPageId}
|
||||
<Accordion id="comments-section" label={t(data.translations, T.post_comments)} lazy class="mt-6">
|
||||
<div class="p-8 border border-zinc-200 rounded-lg">
|
||||
<Comments pageId={data.commentPageId} />
|
||||
</div>
|
||||
</Accordion>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user