appview-less bluesky client
27
fork

Configure Feed

Select the types of activity you want to include in your feed.

only show scroll to top if post composer isnt focused

dawn ae5f88ad 58e54caf

+40 -45
+3 -3
src/components/BskyPost.svelte
··· 81 81 const p = await client.getProfile(did); 82 82 if (!p.ok) return; 83 83 profile = p.value; 84 - console.log(profile.description); 84 + // console.log(profile.description); 85 85 }); 86 86 // const replies = replyBacklinks 87 87 // ? Promise.resolve(ok(replyBacklinks)) ··· 97 97 98 98 const scrollToAndPulse = (targetUri: ResourceUri) => { 99 99 const targetId = `timeline-post-${targetUri}-0`; 100 - console.log(`Scrolling to ${targetId}`); 100 + // console.log(`Scrolling to ${targetId}`); 101 101 const element = document.getElementById(targetId); 102 102 if (!element) return; 103 103 ··· 171 171 // reply: backlinks[2], 172 172 // quote: backlinks[3] 173 173 }; 174 - console.log('findAllBacklinks', did, aturi, actions); 174 + // console.log('findAllBacklinks', did, aturi, actions); 175 175 postActions.set(`${did}:${aturi}`, actions); 176 176 }; 177 177 onMount(() => {
+30 -34
src/components/PostComposer.svelte
··· 8 8 import { parseCanonicalResourceUri } from '@atcute/lexicons'; 9 9 import type { ComAtprotoRepoStrongRef } from '@atcute/atproto'; 10 10 11 + export type State = 12 + | { type: 'null' } 13 + | { type: 'focused'; quoting?: PostWithUri; replying?: PostWithUri }; 14 + 11 15 interface Props { 12 16 client: AtpClient; 13 17 onPostSent: (post: PostWithUri) => void; 14 - quoting?: PostWithUri; 15 - replying?: PostWithUri; 18 + _state: State; 16 19 } 17 20 18 - let { 19 - client, 20 - onPostSent, 21 - quoting = $bindable(undefined), 22 - replying = $bindable(undefined) 23 - }: Props = $props(); 21 + let { client, onPostSent, _state = $bindable({ type: 'null' }) }: Props = $props(); 24 22 25 - let color = $derived( 23 + const isFocused = $derived(_state.type === 'focused'); 24 + 25 + const color = $derived( 26 26 client.user?.did ? generateColorForDid(client.user?.did) : 'var(--nucleus-accent2)' 27 27 ); 28 28 ··· 35 35 const record: AppBskyFeedPost.Main = { 36 36 $type: 'app.bsky.feed.post', 37 37 text, 38 - reply: replying 39 - ? { 40 - root: replying.record.reply?.root ?? strongRef(replying), 41 - parent: strongRef(replying) 42 - } 43 - : undefined, 44 - embed: quoting 45 - ? { 46 - $type: 'app.bsky.embed.record', 47 - record: strongRef(quoting) 48 - } 49 - : undefined, 38 + reply: 39 + _state.type === 'focused' && _state.replying 40 + ? { 41 + root: _state.replying.record.reply?.root ?? strongRef(_state.replying), 42 + parent: strongRef(_state.replying) 43 + } 44 + : undefined, 45 + embed: 46 + _state.type === 'focused' && _state.quoting 47 + ? { 48 + $type: 'app.bsky.embed.record', 49 + record: strongRef(_state.quoting) 50 + } 51 + : undefined, 50 52 createdAt: new Date().toISOString() 51 53 }; 52 54 ··· 75 77 76 78 let postText = $state(''); 77 79 let info = $state(''); 78 - let isFocused = $state(false); 79 80 let textareaEl: HTMLTextAreaElement | undefined = $state(); 80 81 81 82 const unfocus = () => { 82 - isFocused = false; 83 - quoting = undefined; 84 - replying = undefined; 83 + _state.type = 'null'; 85 84 }; 86 85 87 86 const doPost = () => { ··· 104 103 $effect(() => { 105 104 document.documentElement.style.setProperty('--acc-color', color); 106 105 if (isFocused && textareaEl) textareaEl.focus(); 107 - if (quoting || replying) isFocused = true; 108 106 }); 109 107 </script> 110 108 ··· 119 117 /> 120 118 {/snippet} 121 119 122 - {#snippet composer()} 120 + {#snippet composer(replying?: PostWithUri, quoting?: PostWithUri)} 123 121 <div class="flex items-center gap-2"> 124 122 <div class="grow"></div> 125 123 <span ··· 149 147 <textarea 150 148 bind:this={textareaEl} 151 149 bind:value={postText} 152 - onfocus={() => (isFocused = true)} 150 + onfocus={() => (_state.type = 'focused')} 153 151 onblur={unfocus} 154 152 onkeydown={(event) => { 155 153 if (event.key === 'Escape') unfocus(); ··· 174 172 <!-- svelte-ignore a11y_no_static_element_interactions --> 175 173 <div 176 174 onmousedown={(e) => { 177 - if (isFocused) { 178 - e.preventDefault(); 179 - } 175 + if (isFocused) e.preventDefault(); 180 176 }} 181 177 class="flex max-w-full rounded-sm border-2 shadow-lg transition-all duration-300 182 178 {!isFocused ? 'min-h-13 items-center' : ''} ··· 196 192 </div> 197 193 {:else} 198 194 <div class="flex flex-col gap-2"> 199 - {#if isFocused} 200 - {@render composer()} 195 + {#if _state.type === 'focused'} 196 + {@render composer(_state.replying, _state.quoting)} 201 197 {:else} 202 198 <input 203 199 bind:value={postText} 204 - onfocus={() => (isFocused = true)} 200 + onfocus={() => (_state = { type: 'focused' })} 205 201 type="text" 206 202 placeholder="what's on your mind?" 207 203 class="flex-1"
+7 -8
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import BskyPost from '$components/BskyPost.svelte'; 3 - import PostComposer from '$components/PostComposer.svelte'; 3 + import PostComposer, { type State as PostComposerState } from '$components/PostComposer.svelte'; 4 4 import AccountSelector from '$components/AccountSelector.svelte'; 5 5 import SettingsPopup from '$components/SettingsPopup.svelte'; 6 6 import { AtpClient, type NotificationsStreamEvent } from '$lib/at/client'; ··· 23 23 24 24 const { data: loadData }: PageProps = $props(); 25 25 26 + // svelte-ignore state_referenced_locally 26 27 let errors = $state(loadData.client.ok ? [] : [loadData.client.error]); 27 28 let errorsOpen = $state(false); 28 29 ··· 72 73 73 74 const threads = $derived(filterThreads(buildThreads(posts), $accounts, { viewOwnPosts })); 74 75 75 - let quoting = $state<PostWithUri | undefined>(undefined); 76 - let replying = $state<PostWithUri | undefined>(undefined); 76 + let postComposerState = $state<PostComposerState>({ type: 'null' }); 77 77 78 78 const expandedThreads = new SvelteSet<ResourceUri>(); 79 79 ··· 330 330 <PostComposer 331 331 client={selectedClient} 332 332 onPostSent={(post) => posts.get(selectedDid!)?.set(post.uri, post)} 333 - bind:quoting 334 - bind:replying 333 + bind:_state={postComposerState} 335 334 /> 336 335 </div> 337 336 {:else} ··· 342 341 </div> 343 342 {/if} 344 343 345 - {#if showScrollToTop} 344 + {#if postComposerState.type === 'null' && showScrollToTop} 346 345 {@render appButton(scrollToTop, 'heroicons:arrow-up-16-solid', 'scroll to top')} 347 346 {/if} 348 347 </div> ··· 415 414 <div class="mb-1.5"> 416 415 <BskyPost 417 416 client={selectedClient ?? viewClient} 418 - onQuote={(post) => (quoting = post)} 419 - onReply={(post) => (replying = post)} 417 + onQuote={(post) => (postComposerState = { type: 'focused', quoting: post })} 418 + onReply={(post) => (postComposerState = { type: 'focused', replying: post })} 420 419 {...post} 421 420 /> 422 421 </div>