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 overflow-wrap: break-word; 217 word-wrap: normal; 218 word-break: break-word; 219 hyphens: none; 220 } 221 #headerText { 222 margin-left: 10px; 223 font-size: 0.9em; 224 text-align: start; 225 overflow-wrap: break-word; 226 overflow: hidden; 227 } 228 #avatar { 229 height: 100%; 230 margin: 0px; 231 margin-left: 0px; 232 border-right: var(--border-color) 1px solid; 233 } 234 #carouselContainer { 235 position: relative; 236 width: 100%; 237 margin-top: 10px; 238 display: flex; 239 flex-direction: column; 240 align-items: center; 241 } 242 #carouselControls { 243 display: flex; 244 justify-content: space-between; 245 align-items: center; 246 width: 100%; 247 max-width: 500px; 248 margin-top: 5px; 249 } 250 #carouselIndicators { 251 display: flex; 252 gap: 5px; 253 } 254 .indicator { 255 width: 8px; 256 height: 8px; 257 background-color: var(--indicator-inactive-color); 258 } 259 .indicator.active { 260 background-color: var(--indicator-active-color); 261 } 262 #prevBtn, 263 #nextBtn { 264 background-color: rgba(31, 17, 69, 0.7); 265 color: var(--text-color); 266 border: 1px solid var(--border-color); 267 width: 30px; 268 height: 30px; 269 cursor: pointer; 270 display: flex; 271 align-items: center; 272 justify-content: center; 273 } 274 #prevBtn:disabled, 275 #nextBtn:disabled { 276 opacity: 0.5; 277 cursor: not-allowed; 278 } 279 #embedVideo { 280 width: 100%; 281 max-width: 500px; 282 margin-top: 10px; 283 align-self: center; 284 } 285 286 #embedImages { 287 min-width: min(100%, 500px); 288 max-width: min(100%, 500px); 289 max-height: 500px; 290 object-fit: contain; 291 292 margin: 0; 293 } 294</style>