spa(phase 4, tasks 23-25): tag-navigation
- loadPostsByTag(tagName): client-seitige Filterung der Post-Liste (case-insensitive). #t-Filter wird nicht von allen Relays zuverlässig unterstützt — wir laden alles und filtern lokal. - /tag/[name]/+page.ts+svelte: neue Tag-Route, Breadcrumb zurück zur Übersicht, #tagName als H1, dieselbe PostCard-Darstellung wie Home. - Tag-Chips in PostView sind bereits klickbar (aus Task 21). npm run check: 0 errors. Deploy live auf svelte.joerg-lohrer.de. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
feb336fc5b
commit
c089d9e429
|
|
@ -145,6 +145,19 @@ export async function loadReplies(
|
||||||
return events.sort((a, b) => a.created_at - b.created_at);
|
return events.sort((a, b) => a.created_at - b.created_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtert Post-Liste clientseitig nach Tag-Name.
|
||||||
|
* (Relay-seitige #t-Filter werden nicht von allen Relays unterstützt — safer
|
||||||
|
* ist es, die ganze Liste zu laden und lokal zu filtern.)
|
||||||
|
*/
|
||||||
|
export async function loadPostsByTag(tagName: string): Promise<NostrEvent[]> {
|
||||||
|
const all = await loadPostList();
|
||||||
|
const norm = tagName.toLowerCase();
|
||||||
|
return all.filter((ev) =>
|
||||||
|
ev.tags.some((t) => t[0] === 't' && t[1]?.toLowerCase() === norm)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReactionSummary {
|
export interface ReactionSummary {
|
||||||
/** Emoji oder "+"/"-" */
|
/** Emoji oder "+"/"-" */
|
||||||
content: string;
|
content: string;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import type { NostrEvent } from '$lib/nostr/loaders';
|
||||||
|
import { loadPostsByTag } from '$lib/nostr/loaders';
|
||||||
|
import PostCard from '$lib/components/PostCard.svelte';
|
||||||
|
import LoadingOrError from '$lib/components/LoadingOrError.svelte';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
const tagName = $derived(data.tagName);
|
||||||
|
|
||||||
|
let posts: NostrEvent[] = $state([]);
|
||||||
|
let loading = $state(true);
|
||||||
|
let error: string | null = $state(null);
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
posts = await loadPostsByTag(tagName);
|
||||||
|
loading = false;
|
||||||
|
if (posts.length === 0) {
|
||||||
|
error = `Keine Posts mit Tag "${tagName}" gefunden.`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
loading = false;
|
||||||
|
error = e instanceof Error ? e.message : 'Unbekannter Fehler';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
document.title = `#${tagName} – Jörg Lohrer`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="breadcrumb"><a href="/">← Zurück zur Übersicht</a></nav>
|
||||||
|
|
||||||
|
<h1 class="tag-title">#{tagName}</h1>
|
||||||
|
|
||||||
|
<LoadingOrError {loading} {error} />
|
||||||
|
|
||||||
|
{#each posts as post (post.id)}
|
||||||
|
<PostCard event={post} />
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.breadcrumb {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.breadcrumb a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.breadcrumb a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.tag-title {
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params }) => {
|
||||||
|
return { tagName: decodeURIComponent(params.name) };
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue