replies timeline only, appview-less bluesky client

feat: add readme, add divider between post chains and fix post composer unfocusing when post button is clicked

Changed files
+34 -44
resources
src
components
routes
+11 -34
README.md
··· 1 - # sv 1 + ## nucleus 2 2 3 - Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). 3 + a WIP replies timeline only (eg. it only shows replies to your posts and your own posts) appview-less (it does not use the bluesky appview, but rather uses [microcosm](https://www.microcosm.blue/) services) bluesky client. it is implemented in SvelteKit and uses [atcute](https://tangled.org/@mary.my.id/atcute). 4 4 5 - ## Creating a project 5 + ![screenshot](./resources/screenshot.png) 6 6 7 - If you're seeing this, you've probably already done this step. Congrats! 7 + ### todos 8 8 9 - ```sh 10 - # create a new project in the current directory 11 - npx sv create 12 - 13 - # create a new project in my-app 14 - npx sv create my-app 15 - ``` 16 - 17 - ## Developing 18 - 19 - Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 - 21 - ```sh 22 - npm run dev 23 - 24 - # or start the server and open the app in a new browser tab 25 - npm run dev -- --open 26 - ``` 27 - 28 - ## Building 29 - 30 - To create a production version of your app: 31 - 32 - ```sh 33 - npm run build 34 - ``` 35 - 36 - You can preview the production build with `npm run preview`. 37 - 38 - > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. 9 + - [ ] properly implement auth (current state is just for deving) (we want oauth only) 10 + - [ ] implement popouts for showing full chains instead of expanding in the timeline 11 + - [ ] implement moderation (mutes, muted words etc., use blocks from `app.bsky.graph.block`) 12 + - [ ] profile view popout 13 + - [ ] consider showing posts that mention / quote the user.. 14 + - [ ] notifications when replied to (and mentioned and quoted?) 15 + - [ ] basic filtering settings for the timeline (dont show self posts and if we implement mentioned / quoted add toggles for those as well)
resources/screenshot.png

This is a binary file and will not be displayed.

+19 -9
src/components/PostComposer.svelte
··· 81 81 let isFocused = $state(false); 82 82 let textareaEl: HTMLTextAreaElement | undefined = $state(); 83 83 84 + const unfocus = () => { 85 + isFocused = false; 86 + quoting = undefined; 87 + replying = undefined; 88 + }; 89 + 84 90 const doPost = () => { 85 91 if (postText.length === 0 || postText.length > 300) return; 86 92 ··· 89 95 onPostSent(res.value); 90 96 postText = ''; 91 97 info = 'posted!'; 92 - isFocused = false; 93 - quoting = undefined; 94 - replying = undefined; 98 + unfocus(); 95 99 setTimeout(() => (info = ''), 1000 * 0.8); 96 100 } else { 97 101 // todo: add a way to clear error ··· 112 116 <div class="min-h-16"></div> 113 117 {/if} 114 118 119 + <!-- svelte-ignore a11y_no_static_element_interactions --> 115 120 <div 121 + onmousedown={(e) => { 122 + if (isFocused) { 123 + e.preventDefault(); 124 + } 125 + }} 116 126 class="flex max-w-full rounded-sm border-2 shadow-lg transition-all duration-300" 117 127 class:min-h-16={!isFocused} 118 128 class:items-center={!isFocused} ··· 153 163 bind:this={textareaEl} 154 164 bind:value={postText} 155 165 onfocus={() => (isFocused = true)} 156 - onblur={() => { 157 - isFocused = false; 158 - quoting = undefined; 159 - replying = undefined; 160 - }} 166 + onblur={unfocus} 161 167 onkeydown={(event) => { 168 + if (event.key === 'Escape') unfocus(); 162 169 if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) doPost(); 163 170 }} 164 171 placeholder="what's on your mind?" ··· 188 195 {postText.length} / 300 189 196 </span> 190 197 <button 191 - onclick={doPost} 198 + onmousedown={(e) => { 199 + e.preventDefault(); 200 + doPost(); 201 + }} 192 202 disabled={postText.length === 0 || postText.length > 300} 193 203 class="action-button border-none px-5 text-(--nucleus-fg)/94 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:scale-100" 194 204 style="background: color-mix(in srgb, {color} 87%, transparent);"
+4 -1
src/routes/+page.svelte
··· 493 493 494 494 {#snippet threadsView()} 495 495 {#each threads as thread (thread.rootUri)} 496 - <div class="flex {reverseChronological ? 'flex-col' : 'flex-col-reverse'} mb-6.5"> 496 + <div class="flex w-full shrink-0 {reverseChronological ? 'flex-col' : 'flex-col-reverse'}"> 497 497 {#if thread.branchParentPost} 498 498 {@render replyPost(thread.branchParentPost)} 499 499 {/if} ··· 536 536 {/if} 537 537 {/each} 538 538 </div> 539 + <div 540 + class="mx-8 mt-3 mb-4 h-px bg-gradient-to-r from-(--nucleus-accent)/30 to-(--nucleus-accent2)/30" 541 + ></div> 539 542 {/each} 540 543 {/snippet}