Thread viewer for Bluesky
1<script lang="ts">
2 import { accountAPI } from '../../api.js';
3 import { getPostContext } from './PostComponent.svelte';
4 import { linkToPostThread, linkToQuotesPage } from '../../router.js';
5 import { account } from '../../models/account.svelte.js';
6 import { showLoginDialog } from '../Dialogs.svelte';
7 import { showError, pluralize } from '../../utils.js';
8
9 let { post, placement } = getPostContext();
10 let { quoteCount }: { quoteCount: number | undefined } = $props();
11
12 let isLiked = $state(post.liked);
13 let likeCount = $state(post.likeCount);
14 let isUnavailableForLiking = $state(false);
15
16 async function onHeartClick() {
17 try {
18 if (post.hasViewerInfo) {
19 await likePost();
20 } else if (account.loggedIn) {
21 await checkIfCanBeLiked();
22 } else {
23 showLoginDialog({ showClose: true });
24 }
25 } catch (error) {
26 showError(error);
27 }
28 }
29
30 async function checkIfCanBeLiked() {
31 let data = await accountAPI.loadPostViewerInfo(post);
32
33 if (data) {
34 if (post.liked) {
35 isLiked = true;
36 } else {
37 await likePost();
38 }
39 } else {
40 isUnavailableForLiking = true;
41 }
42 }
43
44 async function likePost() {
45 if (!isLiked) {
46 let like = await accountAPI.likePost(post);
47 post.viewerLike = like.uri;
48
49 isLiked = true;
50 likeCount += 1;
51 } else {
52 await accountAPI.removeLike(post.viewerLike);
53 post.viewerLike = undefined;
54
55 isLiked = false;
56 likeCount -= 1;
57 }
58 }
59</script>
60
61<p class="stats">
62 <span>
63 <i class="fa-solid fa-heart {isLiked ? 'liked' : ''}" onclick={onHeartClick}></i> <output>{likeCount}</output>
64 </span>
65
66 {#if post.repostCount > 0}
67 <span><i class="fa-solid fa-retweet"></i> {post.repostCount}</span>
68 {/if}
69
70 {#if post.replyCount > 0 && (placement == 'quotes' || placement == 'feed')}
71 <span>
72 <i class="fa-regular fa-message"></i>
73 <a href="{linkToPostThread(post)}">{pluralize(post.replyCount, 'reply', 'replies')}</a>
74 </span>
75 {/if}
76
77 {#if quoteCount && placement != 'quote'}
78 {#if placement == 'quotes' || placement == 'feed' || post.isPageRoot}
79 <span>
80 <i class="fa-regular fa-comments"></i>
81 <a href={linkToQuotesPage(post.linkToPost)}>{pluralize(quoteCount, 'quote')}</a>
82 </span>
83 {:else}
84 <a href={linkToQuotesPage(post.linkToPost)}>
85 <i class="fa-regular fa-comments"></i> {quoteCount}
86 </a>
87 {/if}
88 {/if}
89
90 {#if post.isRestrictingReplies}
91 {#if placement == 'thread'}
92 <span><i class="fa-solid fa-ban"></i> Limited replies</span>
93 {:else if placement == 'quotes'}
94 <span><i class="fa-solid fa-ban" title="Limited replies"></i></span>
95 {/if}
96 {/if}
97
98 {#if isUnavailableForLiking}
99 <span class="blocked-info">🚫 Post unavailable</span>
100 {/if}
101</p>
102
103<style>
104 .stats {
105 font-size: 10pt;
106 color: #666;
107 }
108
109 a {
110 color: #666;
111 text-decoration: none;
112 }
113
114 a:hover {
115 text-decoration: underline;
116 }
117
118 i {
119 font-size: 9pt;
120 color: #888;
121 }
122
123 i.fa-heart {
124 color: #aaa;
125 }
126
127 i.fa-heart.liked {
128 color: #e03030;
129 }
130
131 i.fa-heart:hover {
132 color: #888;
133 cursor: pointer;
134 }
135
136 i.fa-heart.liked:hover {
137 color: #c02020;
138 }
139
140 span {
141 margin-right: 7px;
142 }
143
144 .blocked-info {
145 color: #a02020;
146 font-weight: bold;
147 margin-left: 5px;
148 }
149
150 @media (prefers-color-scheme: dark) {
151 .stats { color: #aaa; }
152 i { color: #888; }
153 i.fa-heart { color: #aaa; }
154 i.fa-heart.liked { color: #f04040; }
155 i.fa-heart:hover { color: #eee; }
156 i.fa-heart.liked:hover { color: #ff7070; }
157 }
158</style>