From 2ad27adf1f921bd54f9b5a61ae45df4fc98ef282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Tue, 28 Apr 2026 08:46:12 +0200 Subject: [PATCH] feat(spa): snapshot-pfad mit reactions/replies/langs/tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snapshot-pfad bekommt feature-paritaet mit dem runtime-fallback: - Sprach-switcher (inline, gleiche optik wie LanguageAvailability, ohne neue i18n-keys — verwendet snapshot.translations direkt) - Tag-liste mit links auf /tag// - Reactions, ExternalClientLinks, ReplyComposer, ReplyList (alle dtag-basiert, brauchen keine NostrEvent-konstruktion) Co-Authored-By: Claude Opus 4.7 (1M context) --- app/src/routes/[...slug]/+page.svelte | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/app/src/routes/[...slug]/+page.svelte b/app/src/routes/[...slug]/+page.svelte index d38b296..fdd6bec 100644 --- a/app/src/routes/[...slug]/+page.svelte +++ b/app/src/routes/[...slug]/+page.svelte @@ -5,10 +5,15 @@ import { buildHablaLink } from '$lib/nostr/naddr' import PostView from '$lib/components/PostView.svelte' import LoadingOrError from '$lib/components/LoadingOrError.svelte' + import Reactions from '$lib/components/Reactions.svelte' + import ReplyList from '$lib/components/ReplyList.svelte' + import ReplyComposer from '$lib/components/ReplyComposer.svelte' + import ExternalClientLinks from '$lib/components/ExternalClientLinks.svelte' import { renderMarkdown } from '$lib/render/markdown' import { t } from '$lib/i18n' import { get } from 'svelte/store' import { onMount } from 'svelte' + import type { SignedEvent } from '$lib/nostr/signer' let { data } = $props() const dtag = $derived(data.dtag) @@ -57,6 +62,11 @@ }) }) + let optimisticReplies: NostrEvent[] = $state([]) + function handlePublished(signed: SignedEvent) { + optimisticReplies = [...optimisticReplies, signed as unknown as NostrEvent] + } + const jsonLd = $derived( snapshot ? JSON.stringify({ @@ -111,6 +121,16 @@ {#if snapshot}

{snapshot.title}

+ {#if snapshot.translations.length > 0} +

+ + {snapshot.lang.toUpperCase()} + {#each [...snapshot.translations].sort((a, b) => a.lang.localeCompare(b.lang)) as alt} + + {alt.lang.toUpperCase()} + {/each} +

+ {/if} {#if snapshot.cover_image}

{snapshot.cover_image.alt @@ -120,6 +140,17 @@

{snapshot.summary}

{/if}
{@html bodyHtmlPrerendered}
+ {#if snapshot.tags.length > 0} +
+ {#each snapshot.tags as tag} + {tag} + {/each} +
+ {/if} + + + +
{:else} @@ -206,4 +237,52 @@ margin: 1rem 0; color: var(--muted); } + .lang-switch { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.88rem; + color: var(--muted); + margin: 0.25rem 0 1rem; + } + .icon { + font-size: 1rem; + line-height: 1; + } + .btn { + background: transparent; + border: 1px solid var(--border); + color: var(--muted); + border-radius: 3px; + padding: 1px 7px; + font-size: 0.8rem; + font-family: inherit; + text-decoration: none; + } + .btn:hover:not(.active) { + color: var(--fg); + } + .btn.active { + color: var(--accent); + border-color: var(--accent); + } + .sep { + opacity: 0.4; + } + .tags { + margin-top: 1.5rem; + } + .tag { + display: inline-block; + background: var(--code-bg); + border-radius: 3px; + padding: 1px 7px; + margin: 0 4px 4px 0; + font-size: 0.85em; + color: var(--fg); + text-decoration: none; + } + .tag:hover { + background: var(--border); + }