your personal website on atproto - mirror blento.app
at fix-cached-posts 167 lines 4.5 kB view raw
1<script lang="ts"> 2 import { Alert, Button, Input, Subheading } from '@foxui/core'; 3 import Modal from '$lib/components/modal/Modal.svelte'; 4 import type { CreationModalComponentProps } from '../../types'; 5 import { createPost } from '$lib/atproto/methods'; 6 import { user } from '$lib/atproto/auth.svelte'; 7 import { parseBlueskyPostUrl } from '../BlueskyPostCard/utils'; 8 9 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 10 11 let mode = $state<'create' | 'existing'>('create'); 12 13 const profileUrl = `https://blento.app/${user.profile?.handle ?? ''}`; 14 let postText = $state(`Comment on this post to appear on my Blento! ${profileUrl}`); 15 let postUrl = $state(''); 16 let isPosting = $state(false); 17 let errorMessage = $state(''); 18 19 function buildFacets(text: string, url: string) { 20 const encoder = new TextEncoder(); 21 const encoded = encoder.encode(text); 22 const urlBytes = encoder.encode(url); 23 24 let byteStart = -1; 25 for (let i = 0; i <= encoded.length - urlBytes.length; i++) { 26 let match = true; 27 for (let j = 0; j < urlBytes.length; j++) { 28 if (encoded[i + j] !== urlBytes[j]) { 29 match = false; 30 break; 31 } 32 } 33 if (match) { 34 byteStart = i; 35 break; 36 } 37 } 38 39 if (byteStart === -1) return undefined; 40 41 return [ 42 { 43 index: { byteStart, byteEnd: byteStart + urlBytes.length }, 44 features: [{ $type: 'app.bsky.richtext.facet#link', uri: url }] 45 } 46 ]; 47 } 48 49 async function handleCreateNew() { 50 if (!postText.trim()) { 51 errorMessage = 'Post text cannot be empty.'; 52 return; 53 } 54 55 isPosting = true; 56 errorMessage = ''; 57 58 try { 59 const facets = buildFacets(postText, profileUrl); 60 const response = await createPost({ text: postText, facets }); 61 62 if (!response.ok) { 63 throw new Error('Failed to create post'); 64 } 65 66 item.cardData.uri = response.data.uri; 67 68 const rkey = response.data.uri.split('/').pop(); 69 item.cardData.href = `https://bsky.app/profile/${user.profile?.handle}/post/${rkey}`; 70 71 oncreate(); 72 } catch (err) { 73 errorMessage = 74 err instanceof Error ? err.message : 'Failed to create post. Please try again.'; 75 } finally { 76 isPosting = false; 77 } 78 } 79 80 function handleExisting() { 81 errorMessage = ''; 82 const parsed = parseBlueskyPostUrl(postUrl.trim()); 83 84 if (!parsed) { 85 errorMessage = 86 'Invalid URL. Please enter a valid Bluesky post URL (e.g., https://bsky.app/profile/handle/post/...)'; 87 return; 88 } 89 90 item.cardData.uri = `at://${parsed.handle}/app.bsky.feed.post/${parsed.rkey}`; 91 item.cardData.href = postUrl.trim(); 92 93 oncreate(); 94 } 95 96 async function handleSubmit() { 97 if (mode === 'create') { 98 await handleCreateNew(); 99 } else { 100 handleExisting(); 101 } 102 } 103</script> 104 105<Modal open={true} closeButton={false}> 106 <form 107 onsubmit={(e) => { 108 e.preventDefault(); 109 handleSubmit(); 110 }} 111 class="flex flex-col gap-2" 112 > 113 <Subheading>Guestbook</Subheading> 114 115 <div class="flex gap-2"> 116 <Button 117 size="sm" 118 variant="ghost" 119 class={mode === 'create' ? 'bg-base-200 dark:bg-base-700' : ''} 120 onclick={() => (mode = 'create')} 121 > 122 Create new post 123 </Button> 124 <Button 125 size="sm" 126 variant="ghost" 127 class={mode === 'existing' ? 'bg-base-200 dark:bg-base-700' : ''} 128 onclick={() => (mode = 'existing')} 129 > 130 Use existing post 131 </Button> 132 </div> 133 134 {#if mode === 'create'} 135 <p class="text-base-500 dark:text-base-400 text-sm"> 136 This will create a post on your Bluesky account. Replies to that post will appear on your 137 guestbook card. 138 </p> 139 <textarea 140 bind:value={postText} 141 rows="4" 142 class="bg-base-100 dark:bg-base-800 border-base-300 dark:border-base-600 mt-2 w-full rounded-lg border p-3 text-sm focus:outline-none" 143 ></textarea> 144 {:else} 145 <p class="text-base-500 dark:text-base-400 text-sm"> 146 Paste a Bluesky post URL to use as your guestbook. Replies to that post will appear on your 147 card. 148 </p> 149 <Input bind:value={postUrl} placeholder="https://bsky.app/profile/handle/post/..." /> 150 {/if} 151 152 {#if errorMessage} 153 <Alert type="error" title="Error"><span>{errorMessage}</span></Alert> 154 {/if} 155 156 <div class="mt-4 flex justify-end gap-2"> 157 <Button onclick={oncancel} variant="ghost">Cancel</Button> 158 {#if mode === 'create'} 159 <Button type="submit" disabled={isPosting || !postText.trim()}> 160 {isPosting ? 'Posting...' : 'Post to Bluesky & Create'} 161 </Button> 162 {:else} 163 <Button type="submit" disabled={!postUrl.trim()}>Create</Button> 164 {/if} 165 </div> 166 </form> 167</Modal>