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