Compare changes

Choose any two refs to compare.

+27 -18
src/lib/components/LeafletDocumentCard.svelte
··· 1 1 <script lang="ts"> 2 2 import InteractionBar from './InteractionBar.svelte'; 3 3 4 - let { record } = $props(); 4 + let { 5 + record, 6 + profile 7 + }: { 8 + record: any; 9 + profile?: { handle: string; avatar?: string; displayName?: string }; 10 + } = $props(); 5 11 6 12 const data = $derived(record.data as { 7 13 title?: string; ··· 49 55 const previewText = getPreviewText(); 50 56 </script> 51 57 52 - <div class="card bg-base-100 shadow-xl"> 58 + <div class="card bg-base-100 border-b border-base-300"> 53 59 <div class="card-body"> 54 60 <div class="flex items-start gap-4"> 55 61 <div class="flex-shrink-0"> 56 - <div class="avatar placeholder"> 57 - <div class="bg-accent text-accent-content rounded-lg w-16 h-16"> 58 - <svg 59 - xmlns="http://www.w3.org/2000/svg" 60 - class="h-8 w-8" 61 - fill="none" 62 - viewBox="0 0 24 24" 63 - stroke="currentColor" 64 - > 65 - <path 66 - stroke-linecap="round" 67 - stroke-linejoin="round" 68 - stroke-width="2" 69 - d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" 70 - /> 71 - </svg> 62 + <div class="avatar"> 63 + <div class="w-12 h-12 rounded-full"> 64 + {#if profile?.avatar} 65 + <img src={profile.avatar} alt={profile.handle} /> 66 + {:else} 67 + <div class="bg-accent text-accent-content rounded-full w-12 h-12 flex items-center justify-center"> 68 + <span class="text-xl">{profile?.handle?.[0]?.toUpperCase() || '?'}</span> 69 + </div> 70 + {/if} 72 71 </div> 73 72 </div> 74 73 </div> 75 74 76 75 <div class="flex-grow"> 76 + {#if profile} 77 + <div class="text-sm text-base-content/60 mb-2"> 78 + <a href="https://bsky.app/profile/{profile.handle}" target="_blank" rel="noopener noreferrer" class="link link-hover"> 79 + @{profile.handle} 80 + </a> 81 + {#if profile.displayName} 82 + <span class="ml-1">ยท {profile.displayName}</span> 83 + {/if} 84 + </div> 85 + {/if} 77 86 <h3 class="card-title text-lg mb-2">{data.title || 'Untitled Document'}</h3> 78 87 79 88 {#if data.description}
+27 -18
src/lib/components/MusicPlayCard.svelte
··· 1 1 <script lang="ts"> 2 2 import InteractionBar from './InteractionBar.svelte'; 3 3 4 - let { record } = $props(); 4 + let { 5 + record, 6 + profile 7 + }: { 8 + record: any; 9 + profile?: { handle: string; avatar?: string; displayName?: string }; 10 + } = $props(); 5 11 6 12 const data = $derived(record.data as { 7 13 trackName?: string; ··· 30 36 } 31 37 </script> 32 38 33 - <div class="card bg-base-100 shadow-xl"> 39 + <div class="card bg-base-100 border-b border-base-300"> 34 40 <div class="card-body"> 35 41 <div class="flex items-start gap-4"> 36 42 <div class="flex-shrink-0"> 37 - <div class="avatar placeholder"> 38 - <div class="bg-primary text-primary-content rounded-lg w-16 h-16"> 39 - <svg 40 - xmlns="http://www.w3.org/2000/svg" 41 - class="h-8 w-8" 42 - fill="none" 43 - viewBox="0 0 24 24" 44 - stroke="currentColor" 45 - > 46 - <path 47 - stroke-linecap="round" 48 - stroke-linejoin="round" 49 - stroke-width="2" 50 - d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" 51 - /> 52 - </svg> 43 + <div class="avatar"> 44 + <div class="w-12 h-12 rounded-full"> 45 + {#if profile?.avatar} 46 + <img src={profile.avatar} alt={profile.handle} /> 47 + {:else} 48 + <div class="bg-primary text-primary-content rounded-full w-12 h-12 flex items-center justify-center"> 49 + <span class="text-xl">{profile?.handle?.[0]?.toUpperCase() || '?'}</span> 50 + </div> 51 + {/if} 53 52 </div> 54 53 </div> 55 54 </div> 56 55 57 56 <div class="flex-grow"> 57 + {#if profile} 58 + <div class="text-sm text-base-content/60 mb-2"> 59 + <a href="https://bsky.app/profile/{profile.handle}" target="_blank" rel="noopener noreferrer" class="link link-hover"> 60 + @{profile.handle} 61 + </a> 62 + {#if profile.displayName} 63 + <span class="ml-1">ยท {profile.displayName}</span> 64 + {/if} 65 + </div> 66 + {/if} 58 67 <h3 class="card-title text-lg">{data.trackName || 'Unknown Track'}</h3> 59 68 {#if data.artists && data.artists.length > 0} 60 69 <p class="text-base-content/70">
+27 -18
src/lib/components/TangledRepoCard.svelte
··· 1 1 <script lang="ts"> 2 2 import InteractionBar from './InteractionBar.svelte'; 3 3 4 - let { record } = $props(); 4 + let { 5 + record, 6 + profile 7 + }: { 8 + record: any; 9 + profile?: { handle: string; avatar?: string; displayName?: string }; 10 + } = $props(); 5 11 6 12 const data = $derived(record.data as { 7 13 name?: string; ··· 27 33 } 28 34 </script> 29 35 30 - <div class="card bg-base-100 shadow-xl"> 36 + <div class="card bg-base-100 border-b border-base-300"> 31 37 <div class="card-body"> 32 38 <div class="flex items-start gap-4"> 33 39 <div class="flex-shrink-0"> 34 - <div class="avatar placeholder"> 35 - <div class="bg-secondary text-secondary-content rounded-lg w-16 h-16"> 36 - <svg 37 - xmlns="http://www.w3.org/2000/svg" 38 - class="h-8 w-8" 39 - fill="none" 40 - viewBox="0 0 24 24" 41 - stroke="currentColor" 42 - > 43 - <path 44 - stroke-linecap="round" 45 - stroke-linejoin="round" 46 - stroke-width="2" 47 - d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" 48 - /> 49 - </svg> 40 + <div class="avatar"> 41 + <div class="w-12 h-12 rounded-full"> 42 + {#if profile?.avatar} 43 + <img src={profile.avatar} alt={profile.handle} /> 44 + {:else} 45 + <div class="bg-secondary text-secondary-content rounded-full w-12 h-12 flex items-center justify-center"> 46 + <span class="text-xl">{profile?.handle?.[0]?.toUpperCase() || '?'}</span> 47 + </div> 48 + {/if} 50 49 </div> 51 50 </div> 52 51 </div> 53 52 54 53 <div class="flex-grow"> 54 + {#if profile} 55 + <div class="text-sm text-base-content/60 mb-2"> 56 + <a href="https://bsky.app/profile/{profile.handle}" target="_blank" rel="noopener noreferrer" class="link link-hover"> 57 + @{profile.handle} 58 + </a> 59 + {#if profile.displayName} 60 + <span class="ml-1">ยท {profile.displayName}</span> 61 + {/if} 62 + </div> 63 + {/if} 55 64 <div class="flex items-center gap-2"> 56 65 <h3 class="card-title text-lg">{data.name || 'Unknown Repository'}</h3> 57 66 {#if data.knot}
+39
src/routes/feed/+page.server.ts
··· 4 4 import { recordsTable } from '$lib/server/db/schema'; 5 5 import { desc, eq, ne } from 'drizzle-orm'; 6 6 7 + interface ProfileData { 8 + handle: string; 9 + avatar?: string; 10 + displayName?: string; 11 + } 12 + 13 + async function fetchProfile(repo: string): Promise<ProfileData | null> { 14 + try { 15 + const response = await fetch( 16 + `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${repo}` 17 + ); 18 + if (!response.ok) return null; 19 + const data = await response.json(); 20 + return { 21 + handle: data.handle, 22 + avatar: data.avatar, 23 + displayName: data.displayName 24 + }; 25 + } catch { 26 + return null; 27 + } 28 + } 29 + 7 30 export const load: PageServerLoad = async (event) => { 8 31 if (!event.locals.session) { 9 32 return redirect(302, '/login'); ··· 30 53 (a, b) => b.indexedAt.getTime() - a.indexedAt.getTime() 31 54 ); 32 55 56 + // Fetch profile data for all unique repos 57 + const uniqueRepos = [...new Set(records.map((r) => r.repo))]; 58 + const profilePromises = uniqueRepos.map(async (repo) => { 59 + const profile = await fetchProfile(repo); 60 + return { repo, profile }; 61 + }); 62 + 63 + const profileResults = await Promise.all(profilePromises); 64 + const profiles: Record<string, ProfileData> = {}; 65 + for (const { repo, profile } of profileResults) { 66 + if (profile) { 67 + profiles[repo] = profile; 68 + } 69 + } 70 + 33 71 return { 34 72 records, 73 + profiles, 35 74 usersDid: event.locals.session.did 36 75 }; 37 76 };
+5 -5
src/routes/feed/+page.svelte
··· 35 35 <span>No records found. The feed is empty.</span> 36 36 </div> 37 37 {:else} 38 - <div class="space-y-4"> 38 + <div> 39 39 {#each data.records as record (record.id)} 40 40 {#if record.collection === 'fm.teal.alpha.feed.play'} 41 - <MusicPlayCard {record} /> 41 + <MusicPlayCard {record} profile={data.profiles[record.repo]} /> 42 42 {:else if record.collection === 'sh.tangled.repo'} 43 - <TangledRepoCard {record} /> 43 + <TangledRepoCard {record} profile={data.profiles[record.repo]} /> 44 44 {:else if record.collection === 'pub.leaflet.document'} 45 - <LeafletDocumentCard {record} /> 45 + <LeafletDocumentCard {record} profile={data.profiles[record.repo]} /> 46 46 {:else} 47 - <div class="card bg-base-100 shadow-xl"> 47 + <div class="card bg-base-100 border-b border-base-300"> 48 48 <div class="card-body"> 49 49 <h3 class="card-title text-sm opacity-60">Unknown Collection Type</h3> 50 50 <p class="text-sm">{record.collection}</p>