your personal website on atproto - mirror blento.app
at custom-domains 333 lines 11 kB view raw
1<script lang="ts"> 2 import Embed from './embeds/Embed.svelte'; 3 import { sanitize } from '$lib/sanitize'; 4 import { cn, Prose } from '@foxui/core'; 5 import type { WithChildren, WithElementRef } from 'bits-ui'; 6 import type { HTMLAttributes } from 'svelte/elements'; 7 import type { PostData } from '.'; 8 import PostAction from './PostAction.svelte'; 9 import type { Snippet } from 'svelte'; 10 import { numberToHumanReadable } from '..'; 11 import { RelativeTime } from '@foxui/time'; 12 import PostEmbed from './PostEmbed.svelte'; 13 14 let { 15 ref = $bindable(), 16 data, 17 class: className, 18 bookmarked = $bindable(false), 19 liked = $bindable(false), 20 21 showReply = $bindable(true), 22 showRepost = $bindable(true), 23 showLike = $bindable(true), 24 showBookmark = $bindable(true), 25 26 onReplyClick, 27 onRepostClick, 28 onLikeClick, 29 onBookmarkClick, 30 31 replyHref, 32 repostHref, 33 likeHref, 34 35 customActions, 36 37 children, 38 39 logo, 40 41 showAvatar = false, 42 compact = false 43 }: WithElementRef<WithChildren<HTMLAttributes<HTMLDivElement>>> & { 44 data: PostData; 45 class?: string; 46 47 bookmarked?: boolean; 48 liked?: boolean; 49 50 showReply?: boolean; 51 showRepost?: boolean; 52 showLike?: boolean; 53 showBookmark?: boolean; 54 55 onReplyClick?: () => void; 56 onRepostClick?: () => void; 57 onLikeClick?: () => void; 58 onBookmarkClick?: () => void; 59 60 replyHref?: string; 61 repostHref?: string; 62 likeHref?: string; 63 64 customActions?: Snippet; 65 66 logo?: Snippet; 67 68 showAvatar?: boolean; 69 compact?: boolean; 70 } = $props(); 71</script> 72 73<div 74 bind:this={ref} 75 class={cn('text-base-950 dark:text-base-50 transition-colors duration-200', className)} 76> 77 {#if data.reposted} 78 <div class="mb-3 inline-flex items-center gap-2 text-xs"> 79 <svg 80 xmlns="http://www.w3.org/2000/svg" 81 viewBox="0 0 24 24" 82 fill="currentColor" 83 class="size-3" 84 > 85 <path 86 fill-rule="evenodd" 87 d="M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z" 88 clip-rule="evenodd" 89 /> 90 </svg> 91 92 <div class="inline-flex gap-1"> 93 reposted by 94 <a 95 href={data.reposted.href} 96 class="hover:text-accent-600 dark:hover:text-accent-400 font-bold" 97 > 98 @{data.reposted.handle} 99 </a> 100 </div> 101 </div> 102 {/if} 103 {#if data.replyTo} 104 <div class="mb-3 inline-flex items-center gap-2 text-xs"> 105 <svg 106 xmlns="http://www.w3.org/2000/svg" 107 viewBox="0 0 24 24" 108 fill="currentColor" 109 class="size-3" 110 > 111 <path 112 fill-rule="evenodd" 113 d="M14.47 2.47a.75.75 0 0 1 1.06 0l6 6a.75.75 0 0 1 0 1.06l-6 6a.75.75 0 1 1-1.06-1.06l4.72-4.72H9a5.25 5.25 0 1 0 0 10.5h3a.75.75 0 0 1 0 1.5H9a6.75 6.75 0 0 1 0-13.5h10.19l-4.72-4.72a.75.75 0 0 1 0-1.06Z" 114 clip-rule="evenodd" 115 /> 116 </svg> 117 118 <div class="inline-flex gap-1"> 119 replying to 120 <a 121 href={data.replyTo.href} 122 class="hover:text-accent-600 dark:hover:text-accent-400 font-bold" 123 > 124 @{data.replyTo.handle} 125 </a> 126 </div> 127 </div> 128 {/if} 129 <div class="flex gap-4"> 130 {#if showAvatar && data.author.avatar} 131 <a href={data.author.href} class="flex-shrink-0"> 132 <img 133 src={data.author.avatar} 134 alt="" 135 class={compact ? 'size-7 rounded-full object-cover' : 'size-10 rounded-full object-cover'} 136 /> 137 </a> 138 {/if} 139 <div class="w-full"> 140 <div class="mb-1 flex items-start justify-between gap-2"> 141 <div class="flex items-start gap-4"> 142 {#if data.author.href} 143 <a 144 class="hover:bg-accent-900/5 accent:hover:bg-accent-100/10 group/post-author -mx-2 -my-0.5 flex flex-col items-baseline gap-x-2 gap-y-0.5 rounded-xl px-2 py-0.5 sm:flex-row" 145 href={data.author.href} 146 > 147 {#if data.author.displayName} 148 <div 149 class="text-base-900 group-hover/post-author:text-accent-600 dark:text-base-50 dark:group-hover/post-author:text-accent-300 accent:group-hover/post-author:text-accent-950 line-clamp-1 text-sm leading-tight font-semibold" 150 > 151 {data.author.displayName} 152 </div> 153 {/if} 154 <div 155 class={cn( 156 'group-hover/post-author:text-accent-600 dark:group-hover/post-author:text-accent-400 accent:text-accent-950 accent:group-hover/post-author:text-accent-900 text-sm', 157 !data.author.displayName 158 ? 'text-base-900 dark:text-base-50 font-semibold' 159 : 'text-base-600 dark:text-base-400' 160 )} 161 > 162 @{data.author.handle} 163 </div> 164 </a> 165 {:else} 166 <div 167 class="-mx-2 -my-0.5 flex flex-col items-baseline gap-x-2 gap-y-0.5 rounded-xl px-2 py-0.5 sm:flex-row" 168 > 169 <div class="text-base-900 dark:text-base-50 text-sm leading-tight font-semibold"> 170 {data.author.displayName} 171 </div> 172 <div class="text-base-600 dark:text-base-400 accent:text-accent-950 text-sm"> 173 @{data.author.handle} 174 </div> 175 </div> 176 {/if} 177 178 <div 179 class={cn( 180 'text-base-600 dark:text-base-400 accent:text-accent-950 block no-underline', 181 compact ? 'text-xs' : 'text-sm' 182 )} 183 > 184 <RelativeTime date={new Date(data.createdAt)} locale="en" /> 185 </div> 186 </div> 187 188 {#if logo} 189 {@render logo?.()} 190 {/if} 191 </div> 192 193 <Prose 194 size={compact ? 'default' : 'md'} 195 class="accent:prose-a:text-accent-950 accent:text-base-900 accent:prose-p:text-base-900 accent:prose-a:underline" 196 > 197 {#if data.htmlContent} 198 {@html sanitize(data.htmlContent, { ADD_ATTR: ['target'] })} 199 {:else} 200 {@render children?.()} 201 {/if} 202 </Prose> 203 204 <PostEmbed {data} /> 205 206 {#if !compact && (showReply || showRepost || showLike || showBookmark || customActions)} 207 <div 208 class="text-base-500 dark:text-base-400 accent:text-base-900 mt-4 flex justify-between gap-2" 209 > 210 {#if showReply} 211 <PostAction onclick={onReplyClick} href={replyHref}> 212 <svg 213 xmlns="http://www.w3.org/2000/svg" 214 fill="none" 215 viewBox="0 0 24 24" 216 stroke-width="1.5" 217 stroke="currentColor" 218 class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 219 > 220 <path 221 stroke-linecap="round" 222 stroke-linejoin="round" 223 d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z" 224 /> 225 </svg> 226 {#if data.replyCount} 227 {numberToHumanReadable(data.replyCount)} 228 {/if} 229 </PostAction> 230 {/if} 231 232 {#if showRepost} 233 <PostAction onclick={onRepostClick} href={repostHref}> 234 <svg 235 xmlns="http://www.w3.org/2000/svg" 236 fill="none" 237 viewBox="0 0 24 24" 238 stroke-width="1.5" 239 stroke="currentColor" 240 class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 241 > 242 <path 243 stroke-linecap="round" 244 stroke-linejoin="round" 245 d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" 246 /> 247 </svg> 248 {#if data.repostCount} 249 {numberToHumanReadable(data.repostCount)} 250 {/if} 251 </PostAction> 252 {/if} 253 {#if showLike} 254 <PostAction 255 class={liked ? 'text-accent-700 dark:text-accent-500 font-semibold' : ''} 256 onclick={onLikeClick} 257 href={likeHref} 258 > 259 {#if liked} 260 <svg 261 xmlns="http://www.w3.org/2000/svg" 262 viewBox="0 0 24 24" 263 fill="currentColor" 264 class="group-hover/post-action:bg-accent-500/10 text-accent-700 dark:text-accent-500 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 265 > 266 <path 267 d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" 268 /> 269 </svg> 270 {:else} 271 <svg 272 xmlns="http://www.w3.org/2000/svg" 273 fill="none" 274 viewBox="0 0 24 24" 275 stroke-width="1.5" 276 stroke="currentColor" 277 class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 278 > 279 <path 280 stroke-linecap="round" 281 stroke-linejoin="round" 282 d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" 283 /> 284 </svg> 285 {/if} 286 {#if data.likeCount} 287 {numberToHumanReadable(data.likeCount)} 288 {/if} 289 </PostAction> 290 {/if} 291 292 {#if showBookmark} 293 <PostAction onclick={onBookmarkClick}> 294 <span class="sr-only">Bookmark</span> 295 296 {#if bookmarked} 297 <svg 298 xmlns="http://www.w3.org/2000/svg" 299 viewBox="0 0 24 24" 300 fill="currentColor" 301 class="group-hover/post-action:bg-accent-500/10 text-accent-700 dark:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 302 > 303 <path 304 fill-rule="evenodd" 305 d="M6.32 2.577a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 0 1-1.085.67L12 18.089l-7.165 3.583A.75.75 0 0 1 3.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93Z" 306 clip-rule="evenodd" 307 /> 308 </svg> 309 {:else} 310 <svg 311 xmlns="http://www.w3.org/2000/svg" 312 fill="none" 313 viewBox="0 0 24 24" 314 stroke-width="1.5" 315 stroke="currentColor" 316 class="group-hover/post-action:bg-accent-500/10 group-hover/post-action:text-accent-700 dark:group-hover/post-action:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 317 > 318 <path 319 stroke-linecap="round" 320 stroke-linejoin="round" 321 d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z" 322 /> 323 </svg> 324 {/if} 325 </PostAction> 326 {/if} 327 328 {@render customActions?.()} 329 </div> 330 {/if} 331 </div> 332 </div> 333</div>