your personal website on atproto - mirror blento.app

Merge pull request #195 from flo-bit/some-small-fixes

refactor routes, etc

authored by

Florian and committed by
GitHub
0fdc4eea bb99ae30

+185 -443
+2 -2
AGENTS.md
··· 2 2 3 3 ## Project Structure & Module Organization 4 4 5 - - `src/routes` contains SvelteKit routes, including dynamic handle pages in `src/routes/[handle]/[[page]]`, edit flows in `src/routes/[handle]/[[page]]/edit` and `src/routes/edit`, and API endpoints under `src/routes/api`. 6 - - `src/lib` holds reusable modules: card implementations in `src/lib/cards`, shared UI in `src/lib/components`, OAuth helpers in `src/lib/oauth`, and site data/loading in `src/lib/website`. 5 + - `src/routes` contains SvelteKit routes. User-facing pages are under `src/routes/[[actor=actor]]/(pages)/` with an optional actor param (double brackets). When omitted, the actor is resolved from custom domain KV or `PUBLIC_HANDLE`. API endpoints live under `src/routes/api` and `src/routes/[[actor=actor]]/api`. 6 + - `src/lib` holds reusable modules: card implementations in `src/lib/cards`, shared UI in `src/lib/components`, ATProto/OAuth helpers in `src/lib/atproto`, and site data/loading in `src/lib/website`. 7 7 - Root app setup lives in `src/app.html` and `src/app.css`. 8 8 - `static` is for public assets served as-is. 9 9 - `docs` includes contributor-facing docs like custom cards and self-hosting.
+13 -7
CLAUDE.md
··· 61 61 - See e.g. `src/lib/cards/EmbedCard/` and `src/lib/cards/LivestreamCard/` for examples of implementation. 62 62 - Cards should be styled to work in light and dark mode (with `dark:` class modifier) as well as when cards are colorful (= bg-color-500 for the card background) (with `accent:` modifier). 63 63 64 - **ATProto Integration (`src/lib/oauth/`):** 64 + **ATProto Integration (`src/lib/atproto/`):** 65 65 66 66 - `auth.svelte.ts` - OAuth client state and login/logout flow using `@atcute/oauth-browser-client` 67 67 - `atproto.ts` - ATProto API helpers: `resolveHandle`, `listRecords`, `getRecord`, `putRecord`, `deleteRecord`, `uploadImage` 68 68 - Data is stored in user's PDS under collection `app.blento.card` 69 69 - **Important**: ATProto does not allow floating point numbers in records. All numeric values must be integers. 70 + - Login redirect: before OAuth redirect, the current path is saved to `localStorage` (`login-redirect`) and restored after callback 70 71 71 72 **Caching (`src/lib/cache.ts`):** 72 73 ··· 88 89 89 90 ### Routes 90 91 91 - - `/` - Landing page 92 - - `/[handle]/[[page]]` - View a user's bento site (loads from their PDS) 93 - - `/[handle]/[[page]]/edit` - Edit mode for a user's site page 94 - - `/edit` - Self-hosted edit mode 95 - - `/api/links` - Link preview API 92 + All user-facing pages live under `src/routes/[[actor=actor]]/(pages)/` using an optional `[[actor=actor]]` param. When the actor param is omitted, the layout resolves the actor from a custom domain (via KV lookup) or the `PUBLIC_HANDLE` env var. 93 + 94 + - `/` - Landing page (or a user's site when on a custom domain) 95 + - `/[actor]` - View a user's bento site (loads from their PDS) 96 + - `/[actor]/edit` - Edit mode for a user's main page 97 + - `/[actor]/p/[page]` - View a named sub-page 98 + - `/[actor]/p/[page]/edit` - Edit mode for a sub-page 99 + - `/[actor]/p/[page]/copy` - Copy a page to your own site 100 + - `/[actor]/og.png` - Dynamic OG image generation 101 + - `/[actor]/api/refresh` - Cache refresh endpoint 102 + - `/[actor]/.well-known/site.standard.publication` - Site publication metadata 96 103 - `/api/geocoding` - Geocoding API for map cards 97 - - `/api/reloadRecent`, `/api/update` - Additional data endpoints 98 104 99 105 ### Item Type 100 106
+2
src/lib/atproto/auth.svelte.ts
··· 126 126 scope: metadata.scope 127 127 }); 128 128 129 + localStorage.setItem('login-redirect', location.pathname + location.search); 130 + 129 131 // let browser persist local storage 130 132 await new Promise((resolve) => setTimeout(resolve, 200)); 131 133
+1 -1
src/lib/website/EditBar.svelte
··· 63 63 function getShareUrl() { 64 64 const base = typeof window !== 'undefined' ? window.location.origin : ''; 65 65 const pagePath = 66 - data.page && data.page !== 'blento.self' ? `/${data.page.replace('blento.', '')}` : ''; 66 + data.page && data.page !== 'blento.self' ? `/p/${data.page.replace('blento.', '')}` : ''; 67 67 return `${base}/${data.handle}${pagePath}`; 68 68 } 69 69
+1 -1
src/lib/website/SaveModal.svelte
··· 15 15 16 16 function getShareUrl() { 17 17 const base = typeof window !== 'undefined' ? window.location.origin : ''; 18 - const pagePath = page && page !== 'blento.self' ? `/${page.replace('blento.', '')}` : ''; 18 + const pagePath = page && page !== 'blento.self' ? `/p/${page.replace('blento.', '')}` : ''; 19 19 return `${base}/${handle}${pagePath}`; 20 20 } 21 21
+13 -4
src/routes/(auth)/oauth/callback/+page.svelte
··· 7 7 8 8 let startedErrorTimer = $state(); 9 9 10 + let hasRedirected = $state(false); 11 + 10 12 $effect(() => { 11 13 if (user.profile) { 12 - goto('/' + getHandleOrDid(user.profile) + '/edit', {}); 14 + if (hasRedirected) return; 15 + 16 + const redirect = localStorage.getItem('login-redirect'); 17 + localStorage.removeItem('login-redirect'); 18 + console.log('redirect', redirect); 19 + goto(redirect || '/' + getHandleOrDid(user.profile) + '/edit', {}); 20 + 21 + hasRedirected = true; 13 22 } 14 23 15 24 if (!user.isInitializing && !startedErrorTimer) { ··· 17 26 18 27 setTimeout(() => { 19 28 showError = true; 20 - }, 3000); 29 + }, 5000); 21 30 } 22 31 }); 23 32 </script> 24 33 25 - {#if user.isInitializing || !showError} 34 + {#if !showError} 26 35 <div class="flex min-h-screen w-full items-center justify-center text-3xl">Loading...</div> 27 - {:else if showError} 36 + {:else} 28 37 <div class="flex min-h-screen w-full items-center justify-center text-3xl"> 29 38 <span class="max-w-xl text-center font-medium" 30 39 >There was an error signing you in, please go back to the
+1 -7
src/routes/+layout.svelte
··· 18 18 19 19 onMount(() => { 20 20 initClient({ customDomain: data.customDomain }); 21 - 22 - const error = page.url.searchParams.get('error'); 23 - if (error) { 24 - const msg = errorMessages[error]?.(page.url.searchParams) ?? error; 25 - toast.error(msg); 26 - goto(page.url.pathname, { replaceState: true }); 27 - } 28 21 }); 29 22 </script> 30 23 ··· 33 26 </Tooltip.Provider> 34 27 35 28 <ThemeToggle class="fixed top-2 left-2 z-10" /> 29 + 36 30 <Toaster /> 37 31 38 32 {#if videoPlayer.id}
-26
src/routes/+page.server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { env } from '$env/dynamic/public'; 3 - import { env as privateEnv } from '$env/dynamic/private'; 4 - import { createCache } from '$lib/cache'; 5 - import type { ActorIdentifier } from '@atcute/lexicons'; 6 - 7 - export async function load({ platform, request }) { 8 - const handle = env.PUBLIC_HANDLE; 9 - 10 - const kv = platform?.env?.CUSTOM_DOMAINS; 11 - 12 - const cache = createCache(platform); 13 - const customDomain = request.headers.get('X-Custom-Domain')?.toLocaleLowerCase(); 14 - 15 - if (kv && customDomain) { 16 - try { 17 - const did = await kv.get(customDomain); 18 - 19 - if (did) return await loadData(did as ActorIdentifier, cache, false, 'self', privateEnv); 20 - } catch (error) { 21 - console.error('failed to get custom domain kv', error); 22 - } 23 - } 24 - 25 - return await loadData(handle as ActorIdentifier, cache, false, 'self', privateEnv); 26 - }
-13
src/routes/+page.svelte
··· 1 - <script lang="ts"> 2 - import { refreshData } from '$lib/helper.js'; 3 - import Website from '$lib/website/Website.svelte'; 4 - import { onMount } from 'svelte'; 5 - 6 - let { data } = $props(); 7 - 8 - onMount(() => { 9 - refreshData(data); 10 - }); 11 - </script> 12 - 13 - <Website {data} />
+40
src/routes/[[actor=actor]]/(pages)/+layout.server.ts
··· 1 + import { loadData } from '$lib/website/load'; 2 + import { env } from '$env/dynamic/private'; 3 + import { error } from '@sveltejs/kit'; 4 + import { createCache } from '$lib/cache'; 5 + import type { ActorIdentifier } from '@atcute/lexicons'; 6 + import { env as publicEnv } from '$env/dynamic/public'; 7 + 8 + export async function load({ params, platform, request }) { 9 + if (env.PUBLIC_IS_SELFHOSTED) error(404); 10 + 11 + const cache = createCache(platform); 12 + 13 + const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); 14 + 15 + let actor: ActorIdentifier | undefined = params.actor; 16 + 17 + if (!actor) { 18 + const kv = platform?.env?.CUSTOM_DOMAINS; 19 + 20 + if (kv && customDomain) { 21 + try { 22 + const did = await kv.get(customDomain); 23 + 24 + if (did) actor = did as ActorIdentifier; 25 + } catch (error) { 26 + console.error('failed to get custom domain kv', error); 27 + } 28 + } else { 29 + actor = publicEnv.PUBLIC_HANDLE as ActorIdentifier; 30 + } 31 + } else if (customDomain && params.actor) { 32 + actor = undefined; 33 + } 34 + 35 + if (!actor) { 36 + throw error(404, 'Page not found'); 37 + } 38 + 39 + return await loadData(actor, cache, false, params.page, env); 40 + }
+44
src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts
··· 1 + import { loadData } from '$lib/website/load'; 2 + import { createCache } from '$lib/cache'; 3 + import { env } from '$env/dynamic/private'; 4 + import { env as publicEnv } from '$env/dynamic/public'; 5 + import type { ActorIdentifier } from '@atcute/lexicons'; 6 + 7 + import { error } from '@sveltejs/kit'; 8 + import { text } from '@sveltejs/kit'; 9 + 10 + export async function GET({ params, platform, request }) { 11 + const cache = createCache(platform); 12 + 13 + const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); 14 + 15 + let actor: ActorIdentifier | undefined = params.actor; 16 + 17 + if (!actor) { 18 + const kv = platform?.env?.CUSTOM_DOMAINS; 19 + 20 + if (kv && customDomain) { 21 + try { 22 + const did = await kv.get(customDomain); 23 + 24 + if (did) actor = did as ActorIdentifier; 25 + } catch (error) { 26 + console.error('failed to get custom domain kv', error); 27 + } 28 + } else { 29 + actor = publicEnv.PUBLIC_HANDLE as ActorIdentifier; 30 + } 31 + } else if (customDomain && params.actor) { 32 + actor = undefined; 33 + } 34 + 35 + if (!actor) { 36 + throw error(404, 'Page not found'); 37 + } 38 + 39 + const data = await loadData(actor, cache, false, params.page, env); 40 + 41 + if (!data.publication) throw error(300); 42 + 43 + return text(data.did + '/site.standard.publication/blento.self'); 44 + }
+37
src/routes/[[actor=actor]]/api/refresh/+server.ts
··· 1 + import { createCache } from '$lib/cache'; 2 + import { loadData } from '$lib/website/load.js'; 3 + import { env } from '$env/dynamic/private'; 4 + import { env as publicEnv } from '$env/dynamic/public'; 5 + import type { ActorIdentifier } from '@atcute/lexicons'; 6 + import { error, json } from '@sveltejs/kit'; 7 + 8 + export async function GET({ params, platform, request }) { 9 + const cache = createCache(platform); 10 + if (!cache) return json('no cache'); 11 + 12 + const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); 13 + 14 + let actor: ActorIdentifier | undefined = params.actor; 15 + 16 + if (!actor) { 17 + const kv = platform?.env?.CUSTOM_DOMAINS; 18 + 19 + if (kv && customDomain) { 20 + try { 21 + const did = await kv.get(customDomain); 22 + 23 + if (did) actor = did as ActorIdentifier; 24 + } catch (error) { 25 + console.error('failed to get custom domain kv', error); 26 + } 27 + } else { 28 + actor = publicEnv.PUBLIC_HANDLE as ActorIdentifier; 29 + } 30 + } 31 + 32 + if (!actor) { 33 + throw error(404, 'Page not found'); 34 + } 35 + 36 + return json(await loadData(actor, cache, true, 'self', env)); 37 + }
-18
src/routes/[actor=actor]/(pages)/+layout.server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { env } from '$env/dynamic/private'; 3 - import { error } from '@sveltejs/kit'; 4 - import { createCache } from '$lib/cache'; 5 - 6 - export async function load({ params, platform, request }) { 7 - if (env.PUBLIC_IS_SELFHOSTED) error(404); 8 - 9 - const cache = createCache(platform); 10 - 11 - const customDomain = request.headers.get('X-Custom-Domain'); 12 - 13 - if (customDomain) { 14 - throw error(404, 'Page not found!'); 15 - } 16 - 17 - return await loadData(params.actor, cache, false, params.page, env); 18 - }
src/routes/[actor=actor]/(pages)/+page.svelte src/routes/[[actor=actor]]/(pages)/+page.svelte
src/routes/[actor=actor]/(pages)/edit/+page.svelte src/routes/[[actor=actor]]/(pages)/edit/+page.svelte
src/routes/[actor=actor]/(pages)/p/[[page]]/+page.svelte src/routes/[[actor=actor]]/(pages)/p/[[page]]/+page.svelte
-252
src/routes/[actor=actor]/(pages)/p/[[page]]/copy/+page.svelte
··· 1 - <script lang="ts"> 2 - import { 3 - putRecord, 4 - deleteRecord, 5 - listRecords, 6 - uploadBlob, 7 - getCDNImageBlobUrl 8 - } from '$lib/atproto/methods'; 9 - import { user } from '$lib/atproto/auth.svelte'; 10 - import { goto } from '$app/navigation'; 11 - import * as TID from '@atcute/tid'; 12 - import { Button } from '@foxui/core'; 13 - import { loginModalState } from '$lib/atproto/UI/LoginModal.svelte'; 14 - 15 - let { data } = $props(); 16 - 17 - let destinationPage = $state(''); 18 - let copying = $state(false); 19 - let error = $state(''); 20 - let success = $state(false); 21 - 22 - const sourceHandle = $derived(data.handle); 23 - 24 - const sourcePage = $derived( 25 - data.page === 'blento.self' ? 'main' : data.page.replace('blento.', '') 26 - ); 27 - const sourceCards = $derived(data.cards); 28 - 29 - // Re-upload blobs from source repo to current user's repo 30 - async function reuploadBlobs(obj: any, sourceDid: string): Promise<void> { 31 - if (!obj || typeof obj !== 'object') return; 32 - 33 - for (const key of Object.keys(obj)) { 34 - const value = obj[key]; 35 - 36 - if (value && typeof value === 'object') { 37 - // Check if this is a blob reference 38 - if (value.$type === 'blob' && value.ref?.$link) { 39 - try { 40 - // Get the blob URL from source repo 41 - const blobUrl = getCDNImageBlobUrl({ did: sourceDid, blob: value }); 42 - if (!blobUrl) continue; 43 - 44 - // Fetch the blob via proxy to avoid CORS 45 - const response = await fetch(`/api/image-proxy?url=${encodeURIComponent(blobUrl)}`); 46 - if (!response.ok) { 47 - console.error('Failed to fetch blob:', blobUrl); 48 - continue; 49 - } 50 - 51 - // Upload to current user's repo 52 - const blob = await response.blob(); 53 - const newBlobRef = await uploadBlob({ blob }); 54 - 55 - if (newBlobRef) { 56 - // Replace with new blob reference 57 - obj[key] = newBlobRef; 58 - } 59 - } catch (err) { 60 - console.error('Failed to re-upload blob:', err); 61 - } 62 - } else { 63 - // Recursively check nested objects 64 - await reuploadBlobs(value, sourceDid); 65 - } 66 - } 67 - } 68 - } 69 - 70 - async function copyPage() { 71 - if (!user.isLoggedIn || !user.did) { 72 - error = 'You must be logged in to copy pages'; 73 - return; 74 - } 75 - 76 - copying = true; 77 - error = ''; 78 - 79 - try { 80 - const targetPage = 81 - destinationPage.trim() === '' ? 'blento.self' : `blento.${destinationPage.trim()}`; 82 - 83 - // Fetch existing cards from destination page and delete them 84 - const existingCards = await listRecords({ 85 - did: user.did, 86 - collection: 'app.blento.card' 87 - }); 88 - 89 - const cardsToDelete = existingCards.filter( 90 - (card: { value: { page?: string } }) => card.value.page === targetPage 91 - ); 92 - 93 - // Delete existing cards from destination page 94 - const deletePromises = cardsToDelete.map((card: { uri: string }) => { 95 - const rkey = card.uri.split('/').pop()!; 96 - return deleteRecord({ 97 - collection: 'app.blento.card', 98 - rkey 99 - }); 100 - }); 101 - 102 - await Promise.all(deletePromises); 103 - 104 - // Copy each card with a new ID to the destination page 105 - // Re-upload blobs from source repo to current user's repo 106 - for (const card of sourceCards) { 107 - const newCard = { 108 - ...structuredClone(card), 109 - id: TID.now(), 110 - page: targetPage, 111 - updatedAt: new Date().toISOString(), 112 - version: 2 113 - }; 114 - 115 - // Re-upload any blobs in cardData 116 - await reuploadBlobs(newCard.cardData, data.did); 117 - 118 - await putRecord({ 119 - collection: 'app.blento.card', 120 - rkey: newCard.id, 121 - record: newCard 122 - }); 123 - } 124 - 125 - const userHandle = user.profile?.handle ?? data.handle; 126 - 127 - // Copy publication data if it exists 128 - if (data.publication) { 129 - const publicationCopy = structuredClone(data.publication) as Record<string, unknown>; 130 - 131 - // Re-upload any blobs in publication (e.g., icon) 132 - await reuploadBlobs(publicationCopy, data.did); 133 - 134 - // Update the URL to point to the user's page 135 - publicationCopy.url = `https://blento.app/${userHandle}`; 136 - if (targetPage !== 'blento.self') { 137 - publicationCopy.url += '/' + targetPage.replace('blento.', ''); 138 - } 139 - 140 - // Save to appropriate collection based on destination page type 141 - if (targetPage === 'blento.self') { 142 - await putRecord({ 143 - collection: 'site.standard.publication', 144 - rkey: targetPage, 145 - record: publicationCopy 146 - }); 147 - } else { 148 - await putRecord({ 149 - collection: 'app.blento.page', 150 - rkey: targetPage, 151 - record: publicationCopy 152 - }); 153 - } 154 - } 155 - 156 - // Refresh the logged-in user's cache 157 - await fetch(`/${userHandle}/api/refresh`); 158 - 159 - success = true; 160 - 161 - // Redirect to the logged-in user's destination page edit 162 - const destPath = destinationPage.trim() === '' ? '' : `/${destinationPage.trim()}`; 163 - setTimeout(() => { 164 - goto(`/${userHandle}${destPath}/edit`); 165 - }, 1000); 166 - } catch (e) { 167 - error = e instanceof Error ? e.message : 'Failed to copy page'; 168 - } finally { 169 - copying = false; 170 - } 171 - } 172 - </script> 173 - 174 - <div class="bg-base-50 dark:bg-base-900 flex min-h-screen items-center justify-center p-4"> 175 - <div class="bg-base-100 dark:bg-base-800 w-full max-w-md rounded-2xl p-6 shadow-lg"> 176 - {#if user.isLoggedIn} 177 - <h1 class="text-base-900 dark:text-base-50 mb-6 text-2xl font-bold">Copy Page</h1> 178 - 179 - <div class="mb-4"> 180 - <div class="text-base-700 dark:text-base-300 mb-1 block text-sm font-medium"> 181 - Source Page 182 - </div> 183 - <div 184 - class="bg-base-200 dark:bg-base-700 text-base-900 dark:text-base-100 rounded-lg px-3 py-2" 185 - > 186 - {sourceHandle}/{sourcePage || 'main'} 187 - </div> 188 - <p class="text-base-500 mt-1 text-sm">{sourceCards.length} cards</p> 189 - </div> 190 - 191 - <div class="mb-6"> 192 - <label 193 - for="destination" 194 - class="text-base-700 dark:text-base-300 mb-1 block text-sm font-medium" 195 - > 196 - Destination Page (on your profile: {user.profile?.handle}) 197 - </label> 198 - <input 199 - id="destination" 200 - type="text" 201 - bind:value={destinationPage} 202 - placeholder="Leave empty for main page" 203 - class="bg-base-50 dark:bg-base-700 border-base-300 dark:border-base-600 text-base-900 dark:text-base-100 focus:ring-accent-500 w-full rounded-lg border px-3 py-2 focus:ring-2 focus:outline-none" 204 - /> 205 - </div> 206 - 207 - {#if error} 208 - <div 209 - class="mb-4 rounded-lg bg-red-100 p-3 text-red-700 dark:bg-red-900/30 dark:text-red-400" 210 - > 211 - {error} 212 - </div> 213 - {/if} 214 - 215 - {#if success} 216 - <div 217 - class="mb-4 rounded-lg bg-green-100 p-3 text-green-700 dark:bg-green-900/30 dark:text-green-400" 218 - > 219 - Page copied successfully! Redirecting... 220 - </div> 221 - {/if} 222 - 223 - <div class="flex gap-3"> 224 - <a 225 - href="/{data.handle}/{sourcePage === 'main' ? '' : sourcePage}" 226 - class="bg-base-200 hover:bg-base-300 dark:bg-base-700 dark:hover:bg-base-600 text-base-700 dark:text-base-300 flex-1 rounded-lg px-4 py-2 text-center font-medium transition-colors" 227 - > 228 - Cancel 229 - </a> 230 - <button 231 - onclick={copyPage} 232 - disabled={copying || success} 233 - class="bg-accent-500 hover:bg-accent-600 flex-1 rounded-lg px-4 py-2 font-medium text-white transition-colors disabled:cursor-not-allowed disabled:opacity-50" 234 - > 235 - {#if copying} 236 - Copying... 237 - {:else} 238 - Copy {sourceCards.length} cards 239 - {/if} 240 - </button> 241 - </div> 242 - {:else} 243 - <h1 class="text-base-900 dark:text-base-50 mb-6 text-2xl font-bold"> 244 - You must be signed in to copy a page! 245 - </h1> 246 - 247 - <div class="flex w-full justify-center"> 248 - <Button size="lg" onclick={() => loginModalState.show()}>Login</Button> 249 - </div> 250 - {/if} 251 - </div> 252 - </div>
src/routes/[actor=actor]/(pages)/p/[[page]]/edit/+page.svelte src/routes/[[actor=actor]]/(pages)/p/[[page]]/edit/+page.svelte
-16
src/routes/[actor=actor]/.well-known/site.standard.publication/+server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { createCache } from '$lib/cache'; 3 - import { env } from '$env/dynamic/private'; 4 - 5 - import { error } from '@sveltejs/kit'; 6 - import { text } from '@sveltejs/kit'; 7 - 8 - export async function GET({ params, platform }) { 9 - const cache = createCache(platform); 10 - 11 - const data = await loadData(params.actor, cache, false, params.page, env); 12 - 13 - if (!data.publication) throw error(300); 14 - 15 - return text(data.did + '/site.standard.publication/blento.self'); 16 - }
-14
src/routes/[actor=actor]/api/refresh/+server.ts
··· 1 - import { createCache } from '$lib/cache'; 2 - import { loadData } from '$lib/website/load.js'; 3 - import { env } from '$env/dynamic/private'; 4 - import type { Handle } from '@atcute/lexicons'; 5 - import { json } from '@sveltejs/kit'; 6 - 7 - export async function GET({ params, platform }) { 8 - const cache = createCache(platform); 9 - if (!cache) return json('no cache'); 10 - 11 - await loadData(params.actor, cache, true, 'self', env); 12 - 13 - return json('ok'); 14 - }
+30 -3
src/routes/[actor=actor]/og.png/+server.ts src/routes/[[actor=actor]]/og.png/+server.ts
··· 2 2 import { createCache } from '$lib/cache'; 3 3 import { loadData } from '$lib/website/load'; 4 4 import { env } from '$env/dynamic/private'; 5 - import type { Handle } from '@atcute/lexicons'; 5 + import { env as publicEnv } from '$env/dynamic/public'; 6 + 7 + import type { ActorIdentifier } from '@atcute/lexicons'; 6 8 import { ImageResponse } from '@ethercorps/sveltekit-og'; 9 + import { error } from '@sveltejs/kit'; 7 10 8 11 function escapeHtml(str: string): string { 9 12 return str ··· 14 17 .replace(/'/g, '&#39;'); 15 18 } 16 19 17 - export async function GET({ params, platform }) { 20 + export async function GET({ params, platform, request }) { 18 21 const cache = createCache(platform); 19 22 20 - const data = await loadData(params.actor, cache, false, 'self', env); 23 + const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); 24 + 25 + let actor: ActorIdentifier | undefined = params.actor; 26 + 27 + if (!actor) { 28 + const kv = platform?.env?.CUSTOM_DOMAINS; 29 + 30 + if (kv && customDomain) { 31 + try { 32 + const did = await kv.get(customDomain); 33 + 34 + if (did) actor = did as ActorIdentifier; 35 + } catch (error) { 36 + console.error('failed to get custom domain kv', error); 37 + } 38 + } else { 39 + actor = publicEnv.PUBLIC_HANDLE as ActorIdentifier; 40 + } 41 + } 42 + 43 + if (!actor) { 44 + throw error(404, 'Page not found'); 45 + } 46 + 47 + const data = await loadData(actor, cache, false, 'self', env); 21 48 22 49 let image: string | undefined = data.profile.avatar; 23 50
-26
src/routes/edit/+page.server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { env } from '$env/dynamic/public'; 3 - import { env as privateEnv } from '$env/dynamic/private'; 4 - import { createCache } from '$lib/cache'; 5 - import type { ActorIdentifier } from '@atcute/lexicons'; 6 - 7 - export async function load({ platform, request }) { 8 - const handle = env.PUBLIC_HANDLE; 9 - 10 - const kv = platform?.env?.CUSTOM_DOMAINS; 11 - 12 - const cache = createCache(platform); 13 - const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); 14 - 15 - if (kv && customDomain) { 16 - try { 17 - const did = await kv.get(customDomain); 18 - 19 - if (did) return await loadData(did as ActorIdentifier, cache, false, 'self', privateEnv); 20 - } catch (error) { 21 - console.error('failed to get custom domain kv', error); 22 - } 23 - } 24 - 25 - return await loadData(handle as ActorIdentifier, cache, false, 'self', privateEnv); 26 - }
-7
src/routes/edit/+page.svelte
··· 1 - <script lang="ts"> 2 - import EditableWebsite from '$lib/website/EditableWebsite.svelte'; 3 - 4 - let { data } = $props(); 5 - </script> 6 - 7 - <EditableWebsite {data} />
-26
src/routes/p/[[page]]/+layout.server.ts
··· 1 - import { loadData } from '$lib/website/load'; 2 - import { env } from '$env/dynamic/public'; 3 - import { env as privateEnv } from '$env/dynamic/private'; 4 - import { createCache } from '$lib/cache'; 5 - import type { Did, Handle } from '@atcute/lexicons'; 6 - 7 - export async function load({ params, platform, request }) { 8 - const cache = createCache(platform); 9 - 10 - const handle = env.PUBLIC_HANDLE; 11 - 12 - const kv = platform?.env?.CUSTOM_DOMAINS; 13 - 14 - const customDomain = request.headers.get('X-Custom-Domain')?.toLocaleLowerCase(); 15 - 16 - if (kv && customDomain) { 17 - try { 18 - const did = await kv.get(customDomain); 19 - return await loadData(did as Did, cache, false, params.page, privateEnv); 20 - } catch { 21 - console.error('failed'); 22 - } 23 - } 24 - 25 - return await loadData(handle as Handle, cache, false, params.page, privateEnv); 26 - }
-13
src/routes/p/[[page]]/+page.svelte
··· 1 - <script lang="ts"> 2 - import { refreshData } from '$lib/helper.js'; 3 - import Website from '$lib/website/Website.svelte'; 4 - import { onMount } from 'svelte'; 5 - 6 - let { data } = $props(); 7 - 8 - onMount(() => { 9 - refreshData(data); 10 - }); 11 - </script> 12 - 13 - <Website {data} />
+1 -1
src/routes/p/[[page]]/copy/+page.svelte src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte
··· 159 159 success = true; 160 160 161 161 // Redirect to the logged-in user's destination page edit 162 - const destPath = destinationPage.trim() === '' ? '' : `/${destinationPage.trim()}`; 162 + const destPath = destinationPage.trim() === '' ? '' : `/p/${destinationPage.trim()}`; 163 163 setTimeout(() => { 164 164 goto(`/${userHandle}${destPath}/edit`); 165 165 }, 1000);
-6
src/routes/p/[[page]]/edit/+page.svelte
··· 1 - <script lang="ts"> 2 - import EditableWebsite from '$lib/website/EditableWebsite.svelte'; 3 - let { data } = $props(); 4 - </script> 5 - 6 - <EditableWebsite {data} />