Render the forum shell, resources and contacts on the server

This commit is contained in:
dtonon 2026-08-17 13:25:44 +01:00
parent a7b5d8f223
commit 40e0be35d9
16 changed files with 259 additions and 30 deletions

View file

@ -1,3 +1,8 @@
import { SSR_ENABLED } from "$lib/config";
import { snapshot, type ForumShell } from "$lib/forum/snapshot";
export const ssr = SSR_ENABLED;
export async function load({ fetch }) {
return { shell: await snapshot<ForumShell>(fetch, "/api/shell") };
}

View file

@ -0,0 +1,9 @@
import { json } from "@sveltejs/kit";
import { fetchProfiles } from "$lib/forum/profiles";
import { loadShell } from "$lib/ssr/shell";
import { profileQuery } from "$lib/ssr/relay";
export async function GET() {
const shell = await loadShell();
return json({ profiles: await fetchProfiles(profileQuery, shell.admins) });
}

View file

@ -0,0 +1,6 @@
import { json } from "@sveltejs/kit";
import { loadShell } from "$lib/ssr/shell";
export async function GET() {
return json(await loadShell());
}

View file

@ -15,6 +15,7 @@
} from "$lib/profiles.svelte";
import { partialsStore } from "$lib/partials.svelte";
import PostContent from "$lib/components/PostContent.svelte";
import { page } from "$app/state";
const partial = $derived(partialsStore.get("contacts"));
@ -48,11 +49,30 @@
const contacts = $derived<Contact[]>(
adminPubkeys.list.map((pk) => {
const entry = profileStore.profiles.get(pk);
const entry = profileStore.profiles.get(pk) ?? snapshotEntry(pk);
return { pubkey: pk, npub: entry?.npub ?? nip19.npubEncode(pk), entry };
}),
);
// Server-rendered profile, used until the live one is fetched.
function snapshotEntry(pk: string): ProfileEntry | undefined {
const u = page.data.profiles?.[pk];
if (!u) return undefined;
const md = u.metadata ?? {};
return {
pubkey: pk,
npub: u.npub,
name: md.name,
displayName: md.display_name,
nip05: md.nip05,
picture: md.picture ?? u.image,
about: md.about,
website: md.website,
lud16: md.lud16,
fetchedAt: u.lastUpdated || 0,
};
}
function displayName(c: Contact): string {
return c.entry?.displayName || c.entry?.name || `${c.npub.slice(0, 12)}…`;
}
@ -81,14 +101,18 @@
{/if}
{#if loading}
<p class="py-6 text-center text-neutral-400 dark:text-neutral-500">Loading…</p>
<p class="py-6 text-center text-neutral-400 dark:text-neutral-500">
Loading…
</p>
{:else if contacts.length === 0}
<p class="py-6 text-center text-neutral-400 dark:text-neutral-500">No admins listed.</p>
<p class="py-6 text-center text-neutral-400 dark:text-neutral-500">
No admins listed.
</p>
{:else}
<ul class="mt-2 space-y-3">
{#each contacts as c (c.pubkey)}
<li
class="rounded-lg border border-neutral-100 dark:border-neutral-800 p-4 shadow-sm"
class="rounded-lg border border-neutral-100 p-4 shadow-sm dark:border-neutral-800"
>
<div class="flex gap-4">
{#if c.entry?.picture}
@ -99,7 +123,7 @@
/>
{:else}
<span
class="flex h-14 w-14 shrink-0 items-center justify-center rounded-full bg-neutral-200 dark:bg-neutral-700 text-lg font-semibold text-neutral-500 dark:text-neutral-400"
class="flex h-14 w-14 shrink-0 items-center justify-center rounded-full bg-neutral-200 text-lg font-semibold text-neutral-500 dark:bg-neutral-700 dark:text-neutral-400"
>
{displayName(c)[0].toUpperCase()}
</span>
@ -107,7 +131,9 @@
<div class="flex min-w-0 flex-1 items-start justify-between gap-3">
<div class="min-w-0">
<p class="truncate text-2xl font-medium text-neutral-900 dark:text-neutral-100">
<p
class="truncate text-2xl font-medium text-neutral-900 dark:text-neutral-100"
>
{displayName(c)}
</p>
{#if c.entry?.nip05}
@ -129,7 +155,9 @@
<div class="mt-3 md:pl-18">
{#if c.entry?.about}
<p class="whitespace-pre-line text-neutral-600 dark:text-neutral-400">
<p
class="whitespace-pre-line text-neutral-600 dark:text-neutral-400"
>
{c.entry.about}
</p>
{/if}
@ -147,7 +175,10 @@
</a>
{/if}
{#if c.entry?.lud16}
<span class="text-neutral-500 dark:text-neutral-400" title="Lightning address">
<span
class="text-neutral-500 dark:text-neutral-400"
title="Lightning address"
>
⚡ {c.entry.lud16}
</span>
{/if}
@ -158,7 +189,9 @@
{@const rooms = roomsOf(c.pubkey)}
{#if rooms.length > 0}
<p class="mt-2 text-neutral-500 dark:text-neutral-400">
<span class="text-neutral-400 dark:text-neutral-500">Manages:</span>
<span class="text-neutral-400 dark:text-neutral-500"
>Manages:</span
>
{#each rooms as r, i}<a
href="/room/{r.id}"
class="text-accent hover:underline">{r.name}</a

View file

@ -0,0 +1,6 @@
import { snapshot, type Profiles } from "$lib/forum/snapshot";
export async function load({ fetch }) {
const data = await snapshot<{ profiles: Profiles }>(fetch, "/api/contacts");
return { profiles: data?.profiles ?? null };
}