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>