this repo has no description
1<script lang="ts"> 2 import { Post } from "./pdsfetch"; 3 import { Config } from "../../config"; 4 import { onMount } from "svelte"; 5 import moment from "moment"; 6 7 let { post }: { post: Post } = $props(); 8 9 // State for image carousel 10 let currentImageIndex = $state(0); 11 12 // Functions to navigate carousel 13 function nextImage() { 14 if (post.imagesCid && currentImageIndex < post.imagesCid.length - 1) { 15 currentImageIndex++; 16 } 17 } 18 19 function prevImage() { 20 if (currentImageIndex > 0) { 21 currentImageIndex--; 22 } 23 } 24 25 // Function to preload an image 26 function preloadImage(index: number): void { 27 if (!post.imagesCid || index < 0 || index >= post.imagesCid.length) return; 28 29 const img = new Image(); 30 img.src = `${Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did=${post.authorDid}&cid=${post.imagesCid[index]}`; 31 } 32 33 // Preload adjacent images when current index changes 34 $effect(() => { 35 if (post.imagesCid && post.imagesCid.length > 1) { 36 // Preload next image if available 37 if (currentImageIndex < post.imagesCid.length - 1) { 38 preloadImage(currentImageIndex + 1); 39 } 40 41 // Preload previous image if available 42 if (currentImageIndex > 0) { 43 preloadImage(currentImageIndex - 1); 44 } 45 } 46 }); 47 48 // Initial preload of images 49 onMount(() => { 50 if (post.imagesCid && post.imagesCid.length > 1) { 51 // Preload the next image if it exists 52 if (post.imagesCid.length > 1) { 53 preloadImage(1); 54 } 55 } 56 }); 57</script> 58 59<div id="postContainer"> 60 <div id="postHeader"> 61 {#if post.authorAvatarCid} 62 <img 63 id="avatar" 64 src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.authorAvatarCid}" 65 alt="avatar of {post.displayName}" 66 /> 67 {/if} 68 <div id="headerText"> 69 <a id="displayName" href="{Config.FRONTEND_URL}/profile/{post.authorDid}" 70 >{post.displayName}</a 71 > 72 <p id="handle"> 73 <a href="{Config.FRONTEND_URL}/profile/{post.authorHandle}" 74 >{post.authorHandle}</a 75 > 76 77 <a 78 id="postLink" 79 href="{Config.FRONTEND_URL}/profile/{post.authorDid}/post/{post.recordName}" 80 >{moment(post.timenotstamp).isBefore(moment().subtract(1, "month")) 81 ? moment(post.timenotstamp).format("MMM D, YYYY") 82 : moment(post.timenotstamp).fromNow()}</a 83 > 84 </p> 85 </div> 86 </div> 87 <div id="postContent"> 88 {#if post.replyingUri} 89 <a 90 id="replyingText" 91 href="{Config.FRONTEND_URL}/profile/{post.replyingUri.repo}/post/{post 92 .replyingUri.rkey}">replying to {post.replyingUri.repo}</a 93 > 94 {/if} 95 {#if post.quotingUri} 96 <a 97 id="quotingText" 98 href="{Config.FRONTEND_URL}/profile/{post.quotingUri.repo}/post/{post 99 .quotingUri.rkey}">quoting {post.quotingUri.repo}</a 100 > 101 {/if} 102 <div id="postText">{post.text}</div> 103 {#if post.imagesCid && post.imagesCid.length > 0} 104 <div id="carouselContainer"> 105 <img 106 id="embedImages" 107 alt="Post Image {currentImageIndex + 1} of {post.imagesCid.length}" 108 src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post 109 .imagesCid[currentImageIndex]}" 110 /> 111 112 {#if post.imagesCid.length > 1} 113 <div id="carouselControls"> 114 <button 115 id="prevBtn" 116 onclick={prevImage} 117 disabled={currentImageIndex === 0}></button 118 > 119 <div id="carouselIndicators"> 120 {#each post.imagesCid as _, i} 121 <div 122 class="indicator {i === currentImageIndex ? 'active' : ''}" 123 ></div> 124 {/each} 125 </div> 126 <button 127 id="nextBtn" 128 onclick={nextImage} 129 disabled={currentImageIndex === post.imagesCid.length - 1} 130 ></button 131 > 132 </div> 133 {/if} 134 </div> 135 {/if} 136 {#if post.videosLinkCid} 137 <!-- svelte-ignore a11y_media_has_caption --> 138 <video 139 id="embedVideo" 140 src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.videosLinkCid}" 141 controls 142 ></video> 143 {/if} 144 </div> 145</div> 146 147<style> 148 a:hover { 149 text-decoration: underline; 150 } 151 #postContainer { 152 display: flex; 153 flex-direction: column; 154 border: 1px solid var(--border-color); 155 background-color: var(--background-color); 156 margin-bottom: 15px; 157 overflow-wrap: break-word; 158 } 159 #postHeader { 160 display: flex; 161 flex-direction: row; 162 align-items: center; 163 justify-content: start; 164 background-color: var(--header-background-color); 165 padding: 0px 0px; 166 height: fit-content; 167 border-bottom: 1px solid var(--border-color); 168 font-weight: bold; 169 overflow-wrap: break-word; 170 height: 60px; 171 } 172 #displayName { 173 color: var(--text-color); 174 font-size: 1.2em; 175 padding: 0; 176 margin: 0; 177 } 178 #handle { 179 color: var(--border-color); 180 font-size: 0.8em; 181 padding: 0; 182 margin: 0; 183 } 184 185 #postLink { 186 color: var(--border-color); 187 font-size: 0.8em; 188 padding: 0; 189 margin: 0; 190 } 191 #postContent { 192 display: flex; 193 text-align: start; 194 flex-direction: column; 195 padding: 10px; 196 background-color: var(--content-background-color); 197 color: var(--text-color); 198 overflow-wrap: break-word; 199 white-space: pre-line; 200 } 201 #replyingText { 202 font-size: 0.7em; 203 margin: 0; 204 padding: 0; 205 padding-bottom: 5px; 206 } 207 #quotingText { 208 font-size: 0.7em; 209 margin: 0; 210 padding: 0; 211 padding-bottom: 5px; 212 } 213 #postText { 214 margin: 0; 215 padding: 0; 216 } 217 #headerText { 218 margin-left: 10px; 219 font-size: 0.9em; 220 text-align: start; 221 overflow-wrap: break-word; 222 overflow: hidden; 223 } 224 #avatar { 225 height: 100%; 226 margin: 0px; 227 margin-left: 0px; 228 border-right: var(--border-color) 1px solid; 229 } 230 #carouselContainer { 231 position: relative; 232 width: 100%; 233 margin-top: 10px; 234 display: flex; 235 flex-direction: column; 236 align-items: center; 237 } 238 #carouselControls { 239 display: flex; 240 justify-content: space-between; 241 align-items: center; 242 width: 100%; 243 max-width: 500px; 244 margin-top: 5px; 245 } 246 #carouselIndicators { 247 display: flex; 248 gap: 5px; 249 } 250 .indicator { 251 width: 8px; 252 height: 8px; 253 background-color: var(--indicator-inactive-color); 254 } 255 .indicator.active { 256 background-color: var(--indicator-active-color); 257 } 258 #prevBtn, 259 #nextBtn { 260 background-color: rgba(31, 17, 69, 0.7); 261 color: var(--text-color); 262 border: 1px solid var(--border-color); 263 width: 30px; 264 height: 30px; 265 cursor: pointer; 266 display: flex; 267 align-items: center; 268 justify-content: center; 269 } 270 #prevBtn:disabled, 271 #nextBtn:disabled { 272 opacity: 0.5; 273 cursor: not-allowed; 274 } 275 #embedVideo { 276 width: 100%; 277 max-width: 500px; 278 margin-top: 10px; 279 align-self: center; 280 } 281 282 #embedImages { 283 min-width: min(100%, 500px); 284 max-width: min(100%, 500px); 285 max-height: 500px; 286 object-fit: contain; 287 288 margin: 0; 289 } 290</style>