refactor: fold comment count into PostActions toolbar

CommentCountBadge was a separate component rendered next to PostActions
— two adjacent strips of buttons that should have been one toolbar.
Merges the count fetch + click-to-scroll behaviour into PostActions
behind `commentPageId` / `commentTargetId` props. The standalone
CommentCountBadge.svelte is now redundant; deleted.

Layout under the post hero is now a single row:
[💬 3]  [↗ Teilen]  [📋 Link]  [🖨️]  [⏱ 4 min]

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-04-28 14:52:30 +02:00
parent 3318fe9e17
commit 6b2b7245f4
4 changed files with 67 additions and 74 deletions
@@ -1,65 +0,0 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import { env } from "$env/dynamic/public";
import { useTranslate, T } from "$lib/translations";
const t = useTranslate();
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={t(T.comments_jump_to)}
class="no-underline! inline-flex items-center gap-1 text-xs font-medium transition-colors {cls}"
>
<Icon icon="mdi:comment-outline" class="size-4" />
<span class="tabular-nums">{count ?? "…"}</span>
</a>
+64 -1
View File
@@ -1,12 +1,16 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import { env } from "$env/dynamic/public";
import { useTranslate, T } from "$lib/translations";
let {
url,
title,
readingTimeMinutes = null,
commentPageId = null,
commentEnvironment,
commentTargetId = "comments-section",
class: cls = "",
}: {
/** Absolute URL of the page being shared. */
@@ -15,11 +19,58 @@
title: string;
/** Estimated reading time. `null` hides the badge. */
readingTimeMinutes?: number | null;
/** Page id for the comment plugin. `null` hides the count badge. */
commentPageId?: string | null;
/** Optional space override for the comment count fetch. */
commentEnvironment?: string;
/** DOM id of the comment accordion (used by the count-badge click). */
commentTargetId?: string;
class?: string;
} = $props();
const t = useTranslate();
// ── Comment count fetch (only when commentPageId is set) ────────────────
let commentCount = $state<number | null>(null);
const cmsBase = (
env.PUBLIC_CMS_URL ||
import.meta.env.PUBLIC_CMS_URL ||
"http://localhost:3000"
).replace(/\/$/, "");
$effect(() => {
if (!commentPageId) {
commentCount = null;
return;
}
const fetchUrl = `${cmsBase}/api/comments/${encodeURIComponent(commentPageId)}/count${
commentEnvironment
? `?_environment=${encodeURIComponent(commentEnvironment)}`
: ""
}`;
let cancelled = false;
fetch(fetchUrl)
.then((r) => r.json())
.then((d) => {
if (cancelled) return;
commentCount = typeof d?.count === "number" ? d.count : 0;
})
.catch(() => {
if (!cancelled) commentCount = 0;
});
return () => {
cancelled = true;
};
});
function onCommentClick(e: MouseEvent) {
const el = document.getElementById(commentTargetId) as HTMLDetailsElement | null;
if (!el) return;
e.preventDefault();
el.open = true;
el.scrollIntoView({ behavior: "smooth", block: "start" });
}
let copied = $state(false);
let shared = $state(false);
let copyTimer: ReturnType<typeof setTimeout> | null = null;
@@ -77,7 +128,19 @@
}
</script>
<div class="inline-flex flex-wrap items-center gap-x-2 gap-y-1 {cls}">
<div class="inline-flex flex-wrap items-center gap-x-3 gap-y-1 {cls}">
{#if commentPageId}
<a
href="#{commentTargetId}"
onclick={onCommentClick}
aria-label={t(T.comments_jump_to)}
class="no-underline! inline-flex items-center gap-1 text-xs font-medium text-zinc-600 hover:text-zinc-900 transition-colors"
>
<Icon icon="mdi:comment-outline" class="size-4" />
<span class="tabular-nums">{commentCount ?? "…"}</span>
</a>
{/if}
<button
type="button"
onclick={share}
+1 -1
View File
@@ -133,7 +133,7 @@ const TRANSLATION_KEYS = [
"post_map_activate",
"post_map_open_osm",
"post_comments",
// Comment plugin (Comments.svelte / PostCard count pill / CommentCountBadge).
// Comment plugin (Comments.svelte / PostCard count pill / PostActions toolbar).
"comments_loading",
"comments_load_error_title",
"comments_retry",
+2 -7
View File
@@ -5,7 +5,6 @@
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 PostActions from "$lib/components/PostActions.svelte";
import RustyImage from "$lib/components/RustyImage.svelte";
@@ -78,16 +77,12 @@
</div>
</div>
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-zinc-600">
{#if data.commentPageId}
<CommentCountBadge
pageId={data.commentPageId}
targetId="comments-section"
/>
{/if}
<PostActions
url={data.canonicalUrl}
title={data.seoTitle}
readingTimeMinutes={data.readingTimeMinutes}
commentPageId={data.commentPageId}
commentTargetId="comments-section"
/>
</div>