From 1cc745459ab66ed28d4826b0b49754ffbbd6123d Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:35:28 +0100 Subject: [PATCH] blog stuff pt1 --- .../[[actor=actor]]/blog/+page.server.ts | 102 ++++++++ src/routes/[[actor=actor]]/blog/+page.svelte | 105 ++++++++ .../blog/[rkey]/+page.server.ts | 86 +++++++ .../[[actor=actor]]/blog/[rkey]/+page.svelte | 238 ++++++++++++++++++ 4 files changed, 531 insertions(+) create mode 100644 src/routes/[[actor=actor]]/blog/+page.server.ts create mode 100644 src/routes/[[actor=actor]]/blog/+page.svelte create mode 100644 src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts create mode 100644 src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte diff --git a/src/routes/[[actor=actor]]/blog/+page.server.ts b/src/routes/[[actor=actor]]/blog/+page.server.ts new file mode 100644 index 0000000..3f45427 --- /dev/null +++ b/src/routes/[[actor=actor]]/blog/+page.server.ts @@ -0,0 +1,102 @@ +import { error } from '@sveltejs/kit'; +import { getBlentoOrBskyProfile, getRecord, listRecords, parseUri } from '$lib/atproto/methods.js'; +import { createCache, type CachedProfile } from '$lib/cache'; +import type { Did } from '@atcute/lexicons'; +import { getActor } from '$lib/actor.js'; + +export async function load({ params, platform, request }) { + const cache = createCache(platform); + + const did = await getActor({ request, paramActor: params.actor, platform }); + + if (!did) { + throw error(404, 'Blog not found'); + } + + try { + const [records, hostProfile] = await Promise.all([ + listRecords({ + did: did as Did, + collection: 'site.standard.document', + limit: 100 + }), + cache + ? cache.getProfile(did as Did).catch(() => null) + : getBlentoOrBskyProfile({ did: did as Did }) + .then( + (p): CachedProfile => ({ + did: p.did as string, + handle: p.handle as string, + displayName: p.displayName as string | undefined, + avatar: p.avatar as string | undefined, + hasBlento: p.hasBlento, + url: p.url + }) + ) + .catch(() => null) + ]); + + // Resolve publication URLs for site fields + const publications: Record = {}; + + for (const record of records) { + const site = record.value.site as string; + if (!site) continue; + + if (site.startsWith('at://')) { + if (!publications[site]) { + const siteParts = parseUri(site); + if (!siteParts) continue; + + try { + const publicationRecord = await getRecord({ + did: siteParts.repo as Did, + collection: siteParts.collection!, + rkey: siteParts.rkey + }); + + if (publicationRecord.value?.url) { + publications[site] = publicationRecord.value.url as string; + } + } catch { + continue; + } + } + + if (publications[site]) { + record.value.href = publications[site] + record.value.path; + } + } else { + record.value.href = site + record.value.path; + } + } + + const posts = records + .filter((r) => r.value?.href) + .map((r) => { + const value = r.value as Record; + return { + title: value.title as string, + description: value.description as string | undefined, + publishedAt: value.publishedAt as string | undefined, + href: value.href as string, + coverImage: value.coverImage as { $type: 'blob'; ref: { $link: string } } | undefined, + rkey: r.uri.split('/').pop() as string + }; + }) + .sort((a, b) => { + const dateA = a.publishedAt || ''; + const dateB = b.publishedAt || ''; + return dateB.localeCompare(dateA); + }); + + return { + posts, + did, + hostProfile: hostProfile ?? null + }; + } catch (e) { + if (e && typeof e === 'object' && 'status' in e) throw e; + throw error(404, 'Blog not found'); + } +} diff --git a/src/routes/[[actor=actor]]/blog/+page.svelte b/src/routes/[[actor=actor]]/blog/+page.svelte new file mode 100644 index 0000000..02d116a --- /dev/null +++ b/src/routes/[[actor=actor]]/blog/+page.svelte @@ -0,0 +1,105 @@ + + + + {hostName} - Blog + + + + + + + + +
+
+ +
+

Blog

+
+ Written by + + + {hostName} + +
+
+ + {#if posts.length === 0} +

No blog posts found.

+ {:else} + + {/if} +
+
diff --git a/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts b/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts new file mode 100644 index 0000000..60686b7 --- /dev/null +++ b/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts @@ -0,0 +1,86 @@ +import { error } from '@sveltejs/kit'; +import { getBlentoOrBskyProfile, getRecord, parseUri } from '$lib/atproto/methods.js'; +import { createCache, type CachedProfile } from '$lib/cache'; +import type { Did } from '@atcute/lexicons'; +import { getActor } from '$lib/actor'; + +export async function load({ params, platform, request }) { + const { rkey } = params; + + const cache = createCache(platform); + + const did = await getActor({ request, paramActor: params.actor, platform }); + + if (!did || !rkey) { + throw error(404, 'Post not found'); + } + + try { + const [postRecord, hostProfile] = await Promise.all([ + getRecord({ + did: did as Did, + collection: 'site.standard.document', + rkey + }), + cache + ? cache.getProfile(did as Did).catch(() => null) + : getBlentoOrBskyProfile({ did: did as Did }) + .then( + (p): CachedProfile => ({ + did: p.did as string, + handle: p.handle as string, + displayName: p.displayName as string | undefined, + avatar: p.avatar as string | undefined, + hasBlento: p.hasBlento, + url: p.url + }) + ) + .catch(() => null) + ]); + + if (!postRecord?.value) { + throw error(404, 'Post not found'); + } + + const post = postRecord.value as Record; + + // Resolve external URL + let externalUrl: string | null = null; + const site = post.site as string | undefined; + const path = post.path as string | undefined; + + if (site && path) { + if (site.startsWith('at://')) { + const siteParts = parseUri(site); + if (siteParts) { + try { + const publicationRecord = await getRecord({ + did: siteParts.repo as Did, + collection: siteParts.collection!, + rkey: siteParts.rkey + }); + + if (publicationRecord.value?.url) { + externalUrl = (publicationRecord.value.url as string) + path; + } + } catch { + // Could not resolve publication URL + } + } + } else { + externalUrl = site + path; + } + } + + return { + post, + did, + rkey, + hostProfile: hostProfile ?? null, + externalUrl + }; + } catch (e) { + if (e && typeof e === 'object' && 'status' in e) throw e; + throw error(404, 'Post not found'); + } +} diff --git a/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte b/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte new file mode 100644 index 0000000..bfc6ef7 --- /dev/null +++ b/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte @@ -0,0 +1,238 @@ + + + + {title} + + + + {#if coverUrl} + + {/if} + + + + {#if coverUrl} + + {/if} + + +
+
+ + {#if coverUrl} + {title} + {/if} + + +
+

+ {title} +

+ +
+ {#if publishedAt} + + {formatDate(publishedAt)} + + {/if} + + + {hostName} + +
+ + {#if tags.length > 0} +
+ {#each tags as tag (tag)} + + {tag} + + {/each} +
+ {/if} +
+ + + {#if isMarkdown && content} +
+ {@html sanitize(marked.parse(content.value, { renderer }) as string, { + ADD_ATTR: ['target'] + })} +
+ {:else} +
+ {#if description} +

+ {description} +

+ {/if} + + {#if externalUrl} + + Read on original page + + + + + {/if} +
+ {/if} + + + +
+
-- 2.51.2