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>