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