your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context';
4 import { CardDefinitionsByType } from '../..';
5 import type { ContentComponentProps } from '../../types';
6 import { Button } from '@foxui/core';
7 import { BlueskyPost } from '$lib/components/bluesky-post';
8 import type { PostView } from '@atcute/bluesky/types/app/feed/defs';
9
10 let { item }: ContentComponentProps = $props();
11
12 const data = getAdditionalUserData();
13 const did = getDidContext();
14 const handle = getHandleContext();
15
16 type Reply = {
17 $type: string;
18 post: PostView;
19 };
20
21 let isLoaded = $state(false);
22
23 let cardUri = $derived(item.cardData.uri as string);
24
25 // svelte-ignore state_referenced_locally
26 let replies = $state<Reply[]>(
27 ((data['guestbook'] as Record<string, Reply[]>)?.[item.cardData.uri as string] ?? []) as Reply[]
28 );
29
30 onMount(async () => {
31 if (!cardUri) {
32 isLoaded = true;
33 return;
34 }
35
36 try {
37 const loaded = await CardDefinitionsByType[item.cardType]?.loadData?.([item], {
38 did,
39 handle
40 });
41 const result = loaded as Record<string, Reply[]> | undefined;
42 const freshReplies = result?.[cardUri] ?? [];
43
44 if (freshReplies.length > 0) {
45 replies = freshReplies;
46 }
47
48 if (!data['guestbook']) {
49 data['guestbook'] = {};
50 }
51 (data['guestbook'] as Record<string, Reply[]>)[cardUri] = replies;
52 } catch (e) {
53 console.error('Failed to load guestbook replies', e);
54 }
55
56 isLoaded = true;
57 });
58</script>
59
60<div class="flex h-full flex-col overflow-hidden p-4">
61 {#if item.cardData.href}
62 <div class="mb-2 flex justify-end">
63 <a href={item.cardData.href} target="_blank" rel="noopener noreferrer">
64 <Button size="sm">Add a comment on Bluesky</Button>
65 </a>
66 </div>
67 {/if}
68
69 <div class="flex-1 overflow-y-auto">
70 {#if replies.length > 0}
71 <div class="replies">
72 {#each replies as reply (reply.post.uri)}
73 <div class="reply">
74 <BlueskyPost feedViewPost={reply.post} showAvatar compact showLogo={false} />
75 </div>
76 {/each}
77 </div>
78 {:else if isLoaded}
79 <div
80 class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm"
81 >
82 No comments yet — share your Bluesky post to get started!
83 </div>
84 {:else}
85 <div
86 class="text-base-500 dark:text-base-400 accent:text-white/60 flex h-full items-center justify-center text-center text-sm"
87 >
88 Loading comments...
89 </div>
90 {/if}
91 </div>
92</div>
93
94<style>
95 .reply {
96 padding-bottom: 1rem;
97 margin-bottom: 1rem;
98 border-bottom: 1px solid oklch(0.5 0 0 / 0.1);
99 }
100
101 .reply:last-child {
102 border-bottom: none;
103 margin-bottom: 0;
104 padding-bottom: 0;
105 }
106
107 .reply :global(img:not([class*='rounded-full'])) {
108 max-height: 10rem;
109 }
110
111 .reply :global(article) {
112 max-height: 10rem;
113 }
114
115 @container card (width >= 30rem) {
116 .replies {
117 columns: 2;
118 column-gap: 1.5rem;
119 column-rule: 1px solid oklch(0.5 0 0 / 0.15);
120 }
121
122 .reply {
123 break-inside: avoid;
124 }
125 }
126</style>