-1
src/app.css
-1
src/app.css
+59
-9
src/components/BskyPost.svelte
+59
-9
src/components/BskyPost.svelte
···
1
1
<script lang="ts">
2
2
import type { AtpClient } from '$lib/at/client';
3
3
import { AppBskyFeedPost } from '@atcute/bluesky';
4
-
import type { ActorIdentifier, Did, RecordKey } from '@atcute/lexicons';
5
-
import { map, ok } from '$lib/result';
4
+
import {
5
+
parseCanonicalResourceUri,
6
+
type ActorIdentifier,
7
+
type Did,
8
+
type RecordKey,
9
+
type ResourceUri
10
+
} from '@atcute/lexicons';
11
+
import { expect, ok } from '$lib/result';
6
12
import { generateColorForDid } from '$lib/accounts';
7
13
import ProfilePicture from './ProfilePicture.svelte';
14
+
import { isBlob } from '@atcute/lexicons/interfaces';
15
+
import { blob, img } from '$lib/cdn';
16
+
import BskyPost from './BskyPost.svelte';
8
17
9
18
interface Props {
10
19
client: AtpClient;
···
20
29
const color = generateColorForDid(did);
21
30
22
31
let handle: ActorIdentifier = $state(did);
23
-
client
24
-
.resolveDidDoc(did)
25
-
.then((res) => map(res, (data) => data.handle))
26
-
.then((res) => {
27
-
if (res.ok) handle = res.value;
28
-
});
32
+
const didDoc = client.resolveDidDoc(did).then((res) => {
33
+
if (res.ok) handle = res.value.handle;
34
+
return res;
35
+
});
29
36
const post = record
30
37
? Promise.resolve(ok(record))
31
38
: client.getRecord(AppBskyFeedPost.mainSchema, did, rkey);
···
170
177
</div>
171
178
<p class="leading-relaxed text-wrap">
172
179
{record.text}
173
-
{@render embedBadge(record)}
174
180
</p>
181
+
{#if record.embed}
182
+
{@const embed = record.embed}
183
+
<div class="mt-2">
184
+
{#snippet embedPost(uri: ResourceUri)}
185
+
{@const parsedUri = expect(parseCanonicalResourceUri(uri))}
186
+
<!-- reject recursive quotes -->
187
+
{#if !(did === parsedUri.repo && rkey === parsedUri.rkey)}
188
+
<BskyPost {client} did={parsedUri.repo} rkey={parsedUri.rkey} />
189
+
{:else}
190
+
<span>you think you're funny with that recursive quote but i'm onto you</span>
191
+
{/if}
192
+
{/snippet}
193
+
{#if embed.$type === 'app.bsky.embed.images'}
194
+
<!-- todo: improve how images are displayed, and pop out on click -->
195
+
{#each embed.images as image (image.image)}
196
+
{#if isBlob(image.image)}
197
+
<img
198
+
class="rounded-sm"
199
+
src={img('feed_thumbnail', did, image.image.ref.$link)}
200
+
alt={image.alt}
201
+
/>
202
+
{/if}
203
+
{/each}
204
+
{:else if embed.$type === 'app.bsky.embed.video'}
205
+
{#if isBlob(embed.video)}
206
+
{#await didDoc then didDoc}
207
+
{#if didDoc.ok}
208
+
<!-- svelte-ignore a11y_media_has_caption -->
209
+
<video
210
+
class="rounded-sm"
211
+
src={blob(didDoc.value.pds, did, embed.video.ref.$link)}
212
+
controls
213
+
></video>
214
+
{/if}
215
+
{/await}
216
+
{/if}
217
+
{:else if embed.$type === 'app.bsky.embed.record'}
218
+
{@render embedPost(embed.record.uri)}
219
+
{:else if embed.$type === 'app.bsky.embed.recordWithMedia'}
220
+
{@render embedPost(embed.record.record.uri)}
221
+
{/if}
222
+
<!-- todo: implement external link embeds -->
223
+
</div>
224
+
{/if}
175
225
</div>
176
226
{:else}
177
227
<div class="rounded-xl border-2 p-4" style="background: #ef444422; border-color: #ef4444;">
+3
src/lib/cdn.ts
+3
src/lib/cdn.ts