Thread viewer for Bluesky
1<script lang="ts">
2 import { api } from '../../api.js';
3 import { getPostContext } from '../posts/PostComponent.svelte';
4 import { BasePost, Post, MissingPost } from '../../models/posts.js';
5 import { InlineRecordEmbed, InlineRecordWithMediaEmbed } from '../../models/embeds.js';
6 import { ATProtoRecord, FeedGeneratorRecord, StarterPackRecord, UserListRecord } from '../../models/records.js';
7 import { atURI } from '../../utils.js';
8
9 import FeedGeneratorView from '../embeds/FeedGeneratorView.svelte';
10 import PostWrapper from '../posts/PostWrapper.svelte';
11 import StarterPackView from '../embeds/StarterPackView.svelte';
12 import UserListView from '../embeds/UserListView.svelte';
13
14 let { record }: { record: ATProtoRecord } = $props();
15 let { post } = getPostContext();
16
17 async function loadQuotedRecord(): Promise<ATProtoRecord> {
18 let { collection } = atURI(record.uri);
19
20 if (collection == 'app.bsky.feed.post') {
21 let reloaded = await api.loadPostIfExists(record.uri);
22
23 if (reloaded) {
24 return new Post(reloaded);
25 } else {
26 return new MissingPost(post.data);
27 }
28 } else {
29 let reloadedPost = await api.loadPostIfExists(post.uri).then(x => x && new Post(x));
30 let newEmbed = reloadedPost?.embed;
31
32 if (newEmbed instanceof InlineRecordEmbed || newEmbed instanceof InlineRecordWithMediaEmbed) {
33 return newEmbed.record;
34 } else {
35 return new MissingPost(record);
36 }
37 }
38 }
39</script>
40
41{#if record.constructor === ATProtoRecord && !record.type}
42 {#await loadQuotedRecord()}
43 <div class="quote-embed">
44 <p class="post placeholder">Loading quoted post...</p>
45 </div>
46 {:then record}
47 {@render quoteContent(record)}
48 {:catch}
49 <div class="quote-embed">
50 <p class="post placeholder">Error loading quoted post</p>
51 </div>
52 {/await}
53{:else}
54 {@render quoteContent(record)}
55{/if}
56
57{#snippet quoteContent(record: ATProtoRecord)}
58 {#if record instanceof BasePost}
59 <div class="quote-embed">
60 <PostWrapper post={record} placement="quote" />
61 </div>
62
63 {:else if record instanceof FeedGeneratorRecord}
64 <FeedGeneratorView feed={record} />
65
66 {:else if record instanceof StarterPackRecord}
67 <StarterPackView starterPack={record} />
68
69 {:else if record instanceof UserListRecord}
70 <UserListView list={record} />
71
72 {:else}
73 <div class="quote-embed">
74 <p>[{record.type}]</p>
75 </div>
76 {/if}
77{/snippet}
78
79<style>
80 .quote-embed {
81 border: 1px solid #ddd;
82 border-radius: 8px;
83 background-color: #fbfcfd;
84 margin-top: 25px;
85 margin-bottom: 15px;
86 margin-left: 0px;
87 max-width: 800px;
88 }
89
90 .quote-embed :global(.post) {
91 margin-top: 16px;
92 padding-left: 16px;
93 padding-right: 16px;
94 padding-bottom: 5px;
95 }
96
97 .placeholder {
98 font-style: italic;
99 font-size: 11pt;
100 color: #888;
101 }
102
103 @media (prefers-color-scheme: dark) {
104 .quote-embed {
105 background-color: #303030;
106 border-color: #606060;
107 }
108 }
109</style>