replies timeline only, appview-less bluesky client
1<script lang="ts"> 2 import type { AtpClient } from '$lib/at/client'; 3 import { ok, err, type Result } from '$lib/result'; 4 import type { AppBskyFeedPost } from '@atcute/bluesky'; 5 import type { ResourceUri } from '@atcute/lexicons'; 6 import { theme } from '$lib/theme.svelte'; 7 8 interface Props { 9 client: AtpClient; 10 onPostSent: (uri: ResourceUri, post: AppBskyFeedPost.Main) => void; 11 } 12 13 const { client, onPostSent }: Props = $props(); 14 15 const post = async ( 16 text: string 17 ): Promise<Result<{ uri: ResourceUri; record: AppBskyFeedPost.Main }, string>> => { 18 const record: AppBskyFeedPost.Main = { 19 $type: 'app.bsky.feed.post', 20 text, 21 createdAt: new Date().toISOString() 22 }; 23 24 const res = await client.atcute?.post('com.atproto.repo.createRecord', { 25 input: { 26 collection: 'app.bsky.feed.post', 27 repo: client.didDoc!.did, 28 record 29 } 30 }); 31 32 if (!res) { 33 return err('failed to post: not logged in'); 34 } 35 36 if (!res.ok) { 37 return err(`failed to post: ${res.data.error}: ${res.data.message ?? 'no details'}`); 38 } 39 40 return ok({ 41 uri: res.data.uri, 42 record 43 }); 44 }; 45 46 let postText = $state(''); 47 let info = $state(''); 48 49 const doPost = () => { 50 post(postText).then((res) => { 51 if (res.ok) { 52 onPostSent(res.value.uri, res.value.record); 53 postText = ''; 54 info = 'posted!'; 55 setTimeout(() => (info = ''), 1000 * 3); 56 } else { 57 info = res.error; 58 } 59 }); 60 }; 61</script> 62 63<div 64 class="flex min-h-16 max-w-full items-center rounded-sm border-2 px-1 shadow-lg backdrop-blur-sm" 65 style="background: {theme.accent}18; border-color: {theme.accent}66;" 66> 67 <div class="w-full p-1"> 68 {#if info.length > 0} 69 <div 70 class="rounded-sm px-3 py-1.5 text-center font-medium text-nowrap overflow-ellipsis" 71 style="background: {theme.accent}22; color: {theme.accent};" 72 > 73 {info} 74 </div> 75 {:else} 76 <div class="flex gap-2"> 77 <input 78 bind:value={postText} 79 onkeydown={(event) => { 80 if (event.key === 'Enter') doPost(); 81 }} 82 type="text" 83 placeholder="what's on your mind?" 84 class="placeholder-opacity-50 flex-1 rounded-sm border-2 px-3 py-2 text-sm font-medium transition-all focus:scale-[1.01] focus:shadow-lg focus:outline-none" 85 style="background: {theme.bg}66; border-color: {theme.accent}44; color: {theme.fg};" 86 /> 87 <button 88 onclick={doPost} 89 class="rounded-sm border-none px-5 py-2 text-sm font-bold transition-all hover:scale-105 hover:shadow-xl" 90 style="background: linear-gradient(120deg, {theme.accent}c0, {theme.accent2}c0); color: {theme.fg}f0;" 91 > 92 post 93 </button> 94 </div> 95 {/if} 96 </div> 97</div>