bluesky client without react native baggage written in sveltekit
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 144 lines 4.3 kB view raw
1<script lang="ts"> 2 import RichText from './RichText.svelte'; 3 import Avatar from './Avatar.svelte'; 4 import { getClient } from '$lib/atproto'; 5 import { getUserContext } from '$lib/context'; 6 import type { PostView } from '@atcute/bluesky/types/app/feed/defs'; 7 8 let { post }: { post: PostView } = $props(); 9 const user = getUserContext(); 10 let liked = $state(!!post.viewer?.like); 11 let reposted = $state(!!post.viewer?.repost); 12 let likeCount = $derived(post.viewer?.like ? (post.likeCount ?? 0) - 1 : post.likeCount); 13 let repostCount = $derived(post.viewer?.repost ? (post.repostCount ?? 0) - 1 : post.repostCount); 14 const isAuthenticated = !!user.profile?.did; 15 16 async function likePost() { 17 liked = true; 18 const client = await getClient(); 19 20 if (!user.profile?.did) { 21 liked = false; 22 throw new Error('you must be authenticated to do this action'); 23 } 24 25 const { ok } = await client.post('com.atproto.repo.createRecord', { 26 input: { 27 collection: 'app.bsky.feed.like', 28 record: { 29 $type: 'app.bsky.feed.like', 30 createdAt: new Date().toISOString(), 31 subject: { 32 cid: post.cid, 33 uri: post.uri 34 } 35 }, 36 repo: user.profile.did 37 } 38 }); 39 40 if (!ok) { 41 liked = false; 42 throw new Error('failed to like the post'); 43 } 44 } 45 46 async function unlikePost() { 47 liked = false; 48 const client = await getClient(); 49 50 if (!user.profile?.did) { 51 liked = true; 52 throw new Error('you must be authenticated to do this action (how did you even like this)'); 53 } 54 const rkey = post.uri.split('/').at(-1); 55 if (!rkey) { 56 liked = true; 57 throw new Error("couldn't properly extract rkey"); 58 } 59 const { ok } = await client.post('com.atproto.repo.deleteRecord', { 60 input: { 61 collection: 'app.bsky.feed.like', 62 rkey, 63 repo: user.profile.did 64 } 65 }); 66 67 if (!ok) { 68 liked = true; 69 throw new Error('failed to unlike the post'); 70 } 71 console.log('liked post!'); 72 } 73</script> 74 75<article 76 class="flex border-x border-b border-post-border bg-body-background pt-2 pr-4 pb-2 pl-2.5 hover:bg-item-hover" 77> 78 <div class="mr-2.5 ml-2 shrink-0"> 79 <Avatar user={post.author} /> 80 </div> 81 <div> 82 <div class="mb-1"> 83 <a href="#"> 84 <b>{post.author.displayName || post.author.handle}</b> 85 <span class="text-secondary-text">@{post.author.handle}</span> 86 </a> 87 </div> 88 <RichText text={post.record.text} facets={post.record.facets} /> 89 {#if post.embed} 90 {#if post.embed.$type === 'app.bsky.embed.images#view'} 91 {#each post.embed.images as image (image.thumb)} 92 <img class="aspect-[1.23151 / 1] my-2 rounded-xl" src={image.thumb} alt={image.alt} /> 93 {/each} 94 {/if} 95 {/if} 96 <div class="mt-0.5 flex w-full"> 97 <div class="flex w-[320px] max-w-[320px] justify-between"> 98 <div class="grow"> 99 <button class="flex items-center gap-1 py-1.25 pr-1.25" 100 ><span class="text-4.5 icon-[boxicons--message-reply] h-4.5 w-4.5"></span> 101 {post.replyCount}</button 102 > 103 </div> 104 {#if reposted} 105 <div class="flex grow items-center gap-1"> 106 <span class="text-4.5 icon-[mdi--repost] h-4.5 w-4.5 text-green-500"></span> 107 {(repostCount ?? 0) + 1} 108 </div> 109 {:else} 110 <div class="flex grow items-center gap-1"> 111 <span class="text-4.5 icon-[mdi--repost] h-4.5 w-4.5"></span> 112 {repostCount} 113 </div> 114 {/if} 115 {#if !isAuthenticated} 116 <div class="flex grow items-center gap-1"> 117 <span class="text-4.5 icon-[icon-park-solid--like] h-4.5 w-4.5 text-gray-400"></span> 118 <span aria-hidden={true}>{likeCount}</span> 119 </div> 120 {:else if liked} 121 <button 122 aria-label={`Unlike this post, {likeCount+1} likes`} 123 aria-pressed={true} 124 class="flex grow items-center gap-1 hover:cursor-pointer" 125 onclick={unlikePost} 126 > 127 <span class="text-4.5 icon-[icon-park-solid--like] h-4.5 w-4.5 text-red-500"></span> 128 <span aria-hidden={true}>{(likeCount ?? 0) + 1}</span> 129 </button> 130 {:else} 131 <button 132 aria-label={`Like this post, {likeCount} likes`} 133 aria-pressed={false} 134 class="flex grow items-center gap-1 hover:cursor-pointer" 135 onclick={likePost} 136 > 137 <span class="text-4.5 icon-[icon-park-outline--like] h-4.5 w-4.5"></span> 138 <span aria-hidden={true}>{likeCount}</span> 139 </button> 140 {/if} 141 </div> 142 </div> 143 </div> 144</article>