replies timeline only, appview-less bluesky client

post composer highlighting

ptr.pet 9569baf1 3a4c1f8f

verified
Changed files
+48 -18
src
components
+48 -18
src/components/PostComposer.svelte
··· 7 import BskyPost from './BskyPost.svelte'; 8 import { parseCanonicalResourceUri } from '@atcute/lexicons'; 9 import type { ComAtprotoRepoStrongRef } from '@atcute/atproto'; 10 11 export type State = 12 | { type: 'null' } ··· 32 cid: p.cid!, 33 uri: p.uri 34 }); 35 const record: AppBskyFeedPost.Main = { 36 $type: 'app.bsky.feed.post', 37 - text, 38 reply: 39 _state.type === 'focused' && _state.replying 40 ? { ··· 117 /> 118 {/snippet} 119 120 {#snippet composer(replying?: PostWithUri, quoting?: PostWithUri)} 121 <div class="flex items-center gap-2"> 122 <div class="grow"></div> ··· 144 {@render renderPost(replying)} 145 {/if} 146 <div class="composer space-y-2"> 147 - <textarea 148 - bind:this={textareaEl} 149 - bind:value={postText} 150 - onfocus={() => (_state.type = 'focused')} 151 - onblur={unfocus} 152 - onkeydown={(event) => { 153 - if (event.key === 'Escape') unfocus(); 154 - if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) doPost(); 155 - }} 156 - placeholder="what's on your mind?" 157 - rows="4" 158 - class="field-sizing-content resize-none" 159 - ></textarea> 160 {#if quoting} 161 {@render renderPost(quoting)} 162 {/if} ··· 209 </div> 210 </div> 211 212 - <!-- TODO: this fucking blows --> 213 <style> 214 @reference "../app.css"; 215 ··· 224 } 225 226 textarea { 227 - @apply w-full bg-transparent p-0; 228 } 229 230 input { ··· 235 @apply focus:scale-100; 236 } 237 238 - input::placeholder, 239 - textarea::placeholder { 240 color: color-mix(in srgb, var(--acc-color) 45%, var(--nucleus-bg)); 241 } 242
··· 7 import BskyPost from './BskyPost.svelte'; 8 import { parseCanonicalResourceUri } from '@atcute/lexicons'; 9 import type { ComAtprotoRepoStrongRef } from '@atcute/atproto'; 10 + import { parseToRichText } from '$lib/richtext'; 11 + import { tokenize } from '$lib/richtext/parser'; 12 13 export type State = 14 | { type: 'null' } ··· 34 cid: p.cid!, 35 uri: p.uri 36 }); 37 + 38 + // Parse rich text (mentions, links, tags) 39 + const rt = await parseToRichText(client, text); 40 + 41 const record: AppBskyFeedPost.Main = { 42 $type: 'app.bsky.feed.post', 43 + text: rt.text, 44 + facets: rt.facets, 45 reply: 46 _state.type === 'focused' && _state.replying 47 ? { ··· 124 /> 125 {/snippet} 126 127 + {#snippet highlighter(text: string)} 128 + {#each tokenize(text) as token, idx (idx)} 129 + {@const highlighted = 130 + token.type === 'mention' || 131 + token.type === 'topic' || 132 + token.type === 'link' || 133 + token.type === 'autolink'} 134 + <span class={highlighted ? 'text-(--nucleus-accent2)' : ''}>{token.raw}</span> 135 + {/each} 136 + {#if text.endsWith('\n')} 137 + <br /> 138 + {/if} 139 + {/snippet} 140 + 141 {#snippet composer(replying?: PostWithUri, quoting?: PostWithUri)} 142 <div class="flex items-center gap-2"> 143 <div class="grow"></div> ··· 165 {@render renderPost(replying)} 166 {/if} 167 <div class="composer space-y-2"> 168 + <div class="relative grid"> 169 + <!-- todo: replace this with a proper rich text editor --> 170 + <div 171 + class="pointer-events-none col-start-1 row-start-1 min-h-[5lh] w-full bg-transparent text-wrap break-all whitespace-pre-wrap text-(--nucleus-fg)" 172 + aria-hidden="true" 173 + > 174 + {@render highlighter(postText)} 175 + </div> 176 + 177 + <textarea 178 + bind:this={textareaEl} 179 + bind:value={postText} 180 + onfocus={() => (_state.type = 'focused')} 181 + onblur={unfocus} 182 + onkeydown={(event) => { 183 + if (event.key === 'Escape') unfocus(); 184 + if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) doPost(); 185 + }} 186 + placeholder="what's on your mind?" 187 + rows="4" 188 + class="col-start-1 row-start-1 field-sizing-content min-h-[5lh] w-full resize-none overflow-hidden bg-transparent text-wrap break-all whitespace-pre-wrap text-transparent caret-(--nucleus-fg) placeholder:text-(--nucleus-fg)/45" 189 + ></textarea> 190 + </div> 191 + 192 {#if quoting} 193 {@render renderPost(quoting)} 194 {/if} ··· 241 </div> 242 </div> 243 244 <style> 245 @reference "../app.css"; 246 ··· 255 } 256 257 textarea { 258 + @apply w-full p-0; 259 } 260 261 input { ··· 266 @apply focus:scale-100; 267 } 268 269 + input::placeholder { 270 color: color-mix(in srgb, var(--acc-color) 45%, var(--nucleus-bg)); 271 } 272