2026-04-28 08:40:17 +02:00
|
|
|
import { error, redirect } from '@sveltejs/kit'
|
|
|
|
|
import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy'
|
|
|
|
|
import type { EntryGenerator, PageLoad } from './$types'
|
|
|
|
|
import { browser } from '$app/environment'
|
|
|
|
|
|
|
|
|
|
export const ssr = true
|
|
|
|
|
export const prerender = true
|
|
|
|
|
export const trailingSlash = 'always'
|
|
|
|
|
|
|
|
|
|
interface SnapshotIndex {
|
|
|
|
|
posts: Array<{ slug: string; lang: string; title: string }>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PostJson {
|
|
|
|
|
slug: string
|
|
|
|
|
event_id: string
|
|
|
|
|
created_at: number
|
|
|
|
|
published_at: number
|
|
|
|
|
title: string
|
|
|
|
|
summary: string
|
|
|
|
|
lang: string
|
|
|
|
|
cover_image: { url: string; alt?: string; width?: number; height?: number; mime?: string } | null
|
|
|
|
|
content_markdown: string
|
|
|
|
|
tags: string[]
|
|
|
|
|
naddr: string
|
|
|
|
|
habla_url: string
|
|
|
|
|
translations: Array<{ lang: string; slug: string; title: string }>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cachedIndex: SnapshotIndex | undefined
|
|
|
|
|
async function readIndex(): Promise<SnapshotIndex> {
|
|
|
|
|
if (cachedIndex) return cachedIndex
|
|
|
|
|
const fs = await import('node:fs/promises')
|
|
|
|
|
const path = await import('node:path')
|
|
|
|
|
const dir = path.resolve('../snapshot/output')
|
|
|
|
|
const text = await fs.readFile(path.join(dir, 'index.json'), 'utf-8')
|
|
|
|
|
cachedIndex = JSON.parse(text) as SnapshotIndex
|
|
|
|
|
return cachedIndex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function readPost(slug: string): Promise<PostJson | undefined> {
|
|
|
|
|
try {
|
|
|
|
|
const fs = await import('node:fs/promises')
|
|
|
|
|
const path = await import('node:path')
|
|
|
|
|
const dir = path.resolve('../snapshot/output')
|
|
|
|
|
const text = await fs.readFile(path.join(dir, 'posts', `${slug}.json`), 'utf-8')
|
|
|
|
|
return JSON.parse(text) as PostJson
|
|
|
|
|
} catch {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const entries: EntryGenerator = async () => {
|
|
|
|
|
const idx = await readIndex()
|
|
|
|
|
return idx.posts.map((p) => ({ slug: p.slug }))
|
|
|
|
|
}
|
spa(phase 3, tasks 15-22): routing, komponenten, home, postview
Phase 3 komplett:
- Task 15: LoadingOrError-Komponente (loading/error-states, Habla-Fallback)
- Task 16: app.html mit CSS-Variablen (light/dark), Base-Typography
- Task 17: +layout.svelte mit Container + bootstrapReadRelays onMount
- Task 18: ProfileCard-Komponente (Avatar, Name, About, NIP-05, Website)
- Task 19: PostCard-Komponente (Thumbnail + Titel/Summary/Datum), responsive
- Task 20: +page.svelte als Home (Profil + Liste, Promise.all für beides)
- Task 21: PostView-Komponente (Titel, Meta, Cover, Summary, Markdown-Body)
- Task 22: [...slug]/+page.ts+svelte — Catch-all-Route mit Legacy-301-Redirect
Alle $props()-abhängigen Werte via $derived() (Svelte-5-Runes-Konformität).
npm run check: 0 errors, 0 warnings, 592 files. npm run build grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:39:24 +02:00
|
|
|
|
|
|
|
|
export const load: PageLoad = async ({ url }) => {
|
2026-04-28 08:40:17 +02:00
|
|
|
const pathname = url.pathname
|
|
|
|
|
|
|
|
|
|
const legacyDtag = parseLegacyUrl(pathname)
|
|
|
|
|
if (legacyDtag) {
|
|
|
|
|
throw redirect(301, canonicalPostPath(legacyDtag))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const segments = pathname.replace(/^\/+|\/+$/g, '').split('/')
|
|
|
|
|
if (segments.length !== 1 || !segments[0]) {
|
|
|
|
|
throw error(404, 'Seite nicht gefunden')
|
|
|
|
|
}
|
|
|
|
|
const dtag = decodeURIComponent(segments[0])
|
|
|
|
|
|
|
|
|
|
if (!browser) {
|
|
|
|
|
const snapshot = await readPost(dtag)
|
|
|
|
|
if (snapshot) return { dtag, snapshot }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { dtag, snapshot: null }
|
|
|
|
|
}
|