your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import type { Item } from '$lib/types';
3 import { onMount } from 'svelte';
4 import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context';
5 import { CardDefinitionsByType } from '../..';
6 import type { SembleCollectionData } from './index';
7
8 let { item }: { item: Item } = $props();
9
10 const additionalData = getAdditionalUserData();
11 let did = getDidContext();
12 let handle = getHandleContext();
13
14 let key = $derived(`${item.cardData.handle}/${item.cardData.collectionRkey}`);
15
16 // svelte-ignore state_referenced_locally
17 let collectionData = $state(
18 additionalData[item.cardType] != null
19 ? (additionalData[item.cardType] as Record<string, SembleCollectionData>)[key]
20 : undefined
21 );
22
23 onMount(async () => {
24 if (!collectionData) {
25 const result = (await CardDefinitionsByType[item.cardType]?.loadData?.([item], {
26 did,
27 handle
28 })) as Record<string, SembleCollectionData>;
29
30 if (result) {
31 additionalData[item.cardType] = {
32 ...((additionalData[item.cardType] as Record<string, SembleCollectionData>) ?? {}),
33 ...result
34 };
35 collectionData = result[key];
36 }
37 }
38 });
39
40 function getDisplayUrl(url: string) {
41 try {
42 const u = new URL(url);
43 return u.hostname + (u.pathname !== '/' ? u.pathname : '');
44 } catch {
45 return url;
46 }
47 }
48
49 function truncate(text: string, max: number) {
50 if (text.length <= max) return text;
51 return text.slice(0, max) + '…';
52 }
53</script>
54
55<div class={['flex h-full flex-col overflow-y-auto px-5 py-4', item.cardData.label ? 'pt-12' : '']}>
56 {#if collectionData}
57 <div class="mb-3 flex flex-col gap-1">
58 <h3 class="text-base-900 dark:text-base-100 accent:text-black text-sm font-semibold">
59 {collectionData.name}
60 </h3>
61 {#if collectionData.description}
62 <p class="text-base-500 dark:text-base-400 accent:text-black/60 text-xs">
63 {collectionData.description}
64 </p>
65 {/if}
66 </div>
67
68 {#if collectionData.cards.length > 0}
69 <div class="flex flex-col gap-3">
70 {#each collectionData.cards as card (card.uri)}
71 {#if card.type === 'URL' && card.url}
72 <a
73 href={card.url}
74 target="_blank"
75 rel="noopener noreferrer"
76 class="bg-base-100 dark:bg-base-800 accent:bg-black/10 hover:bg-base-200 dark:hover:bg-base-700 accent:hover:bg-black/15 flex flex-col gap-1.5 rounded-xl px-5 py-3 transition-colors"
77 >
78 {#if card.title}
79 <span
80 class="text-base-900 dark:text-base-100 accent:text-black text-sm leading-snug font-medium"
81 >
82 {truncate(card.title, 80)}
83 </span>
84 {/if}
85 {#if card.description}
86 <span
87 class="text-base-600 dark:text-base-400 accent:text-black/70 text-xs leading-snug"
88 >
89 {truncate(card.description, 120)}
90 </span>
91 {/if}
92 <span class="text-base-400 dark:text-base-500 accent:text-black/60 truncate text-xs">
93 {getDisplayUrl(card.url)}
94 </span>
95 </a>
96 {:else if card.type === 'NOTE' && card.text}
97 <div
98 class="bg-base-100 dark:bg-base-800 accent:bg-black/10 flex flex-col gap-1.5 rounded-xl px-5 py-3"
99 >
100 <span
101 class="text-base-700 dark:text-base-300 accent:text-black/80 border-accent-500 accent:border-black/60 border-l-2 pl-3 text-sm leading-snug italic"
102 >
103 {truncate(card.text, 200)}
104 </span>
105 </div>
106 {/if}
107 {/each}
108 </div>
109 {:else}
110 <div
111 class="text-base-500 dark:text-base-400 accent:text-black/60 flex flex-1 items-center justify-center text-center text-sm"
112 >
113 No cards in this collection yet.
114 </div>
115 {/if}
116 {:else}
117 <div
118 class="text-base-500 dark:text-base-400 accent:text-black/60 flex h-full w-full items-center justify-center text-center text-sm"
119 >
120 Loading...
121 </div>
122 {/if}
123</div>