290 lines
7.1 KiB
Svelte
290 lines
7.1 KiB
Svelte
<script lang="ts">
|
|
import { Post } from "./pdsfetch";
|
|
import { Config } from "../../config";
|
|
import { onMount } from "svelte";
|
|
import moment from "moment";
|
|
|
|
let { post }: { post: Post } = $props();
|
|
|
|
// State for image carousel
|
|
let currentImageIndex = $state(0);
|
|
|
|
// Functions to navigate carousel
|
|
function nextImage() {
|
|
if (post.imagesCid && currentImageIndex < post.imagesCid.length - 1) {
|
|
currentImageIndex++;
|
|
}
|
|
}
|
|
|
|
function prevImage() {
|
|
if (currentImageIndex > 0) {
|
|
currentImageIndex--;
|
|
}
|
|
}
|
|
|
|
// Function to preload an image
|
|
function preloadImage(index: number): void {
|
|
if (!post.imagesCid || index < 0 || index >= post.imagesCid.length) return;
|
|
|
|
const img = new Image();
|
|
img.src = `${Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did=${post.authorDid}&cid=${post.imagesCid[index]}`;
|
|
}
|
|
|
|
// Preload adjacent images when current index changes
|
|
$effect(() => {
|
|
if (post.imagesCid && post.imagesCid.length > 1) {
|
|
// Preload next image if available
|
|
if (currentImageIndex < post.imagesCid.length - 1) {
|
|
preloadImage(currentImageIndex + 1);
|
|
}
|
|
|
|
// Preload previous image if available
|
|
if (currentImageIndex > 0) {
|
|
preloadImage(currentImageIndex - 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initial preload of images
|
|
onMount(() => {
|
|
if (post.imagesCid && post.imagesCid.length > 1) {
|
|
// Preload the next image if it exists
|
|
if (post.imagesCid.length > 1) {
|
|
preloadImage(1);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div id="postContainer">
|
|
<div id="postHeader">
|
|
{#if post.authorAvatarCid}
|
|
<img
|
|
id="avatar"
|
|
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.authorAvatarCid}"
|
|
alt="avatar of {post.displayName}"
|
|
/>
|
|
{/if}
|
|
<div id="headerText">
|
|
<a id="displayName" href="{Config.FRONTEND_URL}/profile/{post.authorDid}"
|
|
>{post.displayName}</a
|
|
>
|
|
<p id="handle">
|
|
<a href="{Config.FRONTEND_URL}/profile/{post.authorHandle}"
|
|
>{post.authorHandle}</a
|
|
>
|
|
|
|
<a
|
|
id="postLink"
|
|
href="{Config.FRONTEND_URL}/profile/{post.authorDid}/post/{post.recordName}"
|
|
>{moment(post.timenotstamp).isBefore(moment().subtract(1, "month"))
|
|
? moment(post.timenotstamp).format("MMM D, YYYY")
|
|
: moment(post.timenotstamp).fromNow()}</a
|
|
>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div id="postContent">
|
|
{#if post.replyingUri}
|
|
<a
|
|
id="replyingText"
|
|
href="{Config.FRONTEND_URL}/profile/{post.replyingUri.repo}/post/{post
|
|
.replyingUri.rkey}">replying to {post.replyingUri.repo}</a
|
|
>
|
|
{/if}
|
|
{#if post.quotingUri}
|
|
<a
|
|
id="quotingText"
|
|
href="{Config.FRONTEND_URL}/profile/{post.quotingUri.repo}/post/{post
|
|
.quotingUri.rkey}">quoting {post.quotingUri.repo}</a
|
|
>
|
|
{/if}
|
|
<div id="postText">{post.text}</div>
|
|
{#if post.imagesCid && post.imagesCid.length > 0}
|
|
<div id="carouselContainer">
|
|
<img
|
|
id="embedImages"
|
|
alt="Post Image {currentImageIndex + 1} of {post.imagesCid.length}"
|
|
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post
|
|
.imagesCid[currentImageIndex]}"
|
|
/>
|
|
|
|
{#if post.imagesCid.length > 1}
|
|
<div id="carouselControls">
|
|
<button
|
|
id="prevBtn"
|
|
onclick={prevImage}
|
|
disabled={currentImageIndex === 0}>←</button
|
|
>
|
|
<div id="carouselIndicators">
|
|
{#each post.imagesCid as _, i}
|
|
<div
|
|
class="indicator {i === currentImageIndex ? 'active' : ''}"
|
|
></div>
|
|
{/each}
|
|
</div>
|
|
<button
|
|
id="nextBtn"
|
|
onclick={nextImage}
|
|
disabled={currentImageIndex === post.imagesCid.length - 1}
|
|
>→</button
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if post.videosLinkCid}
|
|
<!-- svelte-ignore a11y_media_has_caption -->
|
|
<video
|
|
id="embedVideo"
|
|
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.videosLinkCid}"
|
|
controls
|
|
></video>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
#postContainer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid var(--border-color);
|
|
background-color: var(--background-color);
|
|
margin-bottom: 15px;
|
|
overflow-wrap: break-word;
|
|
}
|
|
#postHeader {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: start;
|
|
background-color: var(--header-background-color);
|
|
padding: 0px 0px;
|
|
height: fit-content;
|
|
border-bottom: 1px solid var(--border-color);
|
|
font-weight: bold;
|
|
overflow-wrap: break-word;
|
|
height: 60px;
|
|
}
|
|
#displayName {
|
|
color: var(--text-color);
|
|
font-size: 1.2em;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
#handle {
|
|
color: var(--border-color);
|
|
font-size: 0.8em;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
#postLink {
|
|
color: var(--border-color);
|
|
font-size: 0.8em;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
#postContent {
|
|
display: flex;
|
|
text-align: start;
|
|
flex-direction: column;
|
|
padding: 10px;
|
|
background-color: var(--content-background-color);
|
|
color: var(--text-color);
|
|
overflow-wrap: break-word;
|
|
white-space: pre-line;
|
|
}
|
|
#replyingText {
|
|
font-size: 0.7em;
|
|
margin: 0;
|
|
padding: 0;
|
|
padding-bottom: 5px;
|
|
}
|
|
#quotingText {
|
|
font-size: 0.7em;
|
|
margin: 0;
|
|
padding: 0;
|
|
padding-bottom: 5px;
|
|
}
|
|
#postText {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
#headerText {
|
|
margin-left: 10px;
|
|
font-size: 0.9em;
|
|
text-align: start;
|
|
overflow-wrap: break-word;
|
|
overflow: hidden;
|
|
}
|
|
#avatar {
|
|
height: 100%;
|
|
margin: 0px;
|
|
margin-left: 0px;
|
|
border-right: var(--border-color) 1px solid;
|
|
}
|
|
#carouselContainer {
|
|
position: relative;
|
|
width: 100%;
|
|
margin-top: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
#carouselControls {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
margin-top: 5px;
|
|
}
|
|
#carouselIndicators {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
.indicator {
|
|
width: 8px;
|
|
height: 8px;
|
|
background-color: var(--indicator-inactive-color);
|
|
}
|
|
.indicator.active {
|
|
background-color: var(--indicator-active-color);
|
|
}
|
|
#prevBtn,
|
|
#nextBtn {
|
|
background-color: rgba(31, 17, 69, 0.7);
|
|
color: var(--text-color);
|
|
border: 1px solid var(--border-color);
|
|
width: 30px;
|
|
height: 30px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
#prevBtn:disabled,
|
|
#nextBtn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
#embedVideo {
|
|
width: 100%;
|
|
max-width: 500px;
|
|
margin-top: 10px;
|
|
align-self: center;
|
|
}
|
|
|
|
#embedImages {
|
|
min-width: min(100%, 500px);
|
|
max-width: min(100%, 500px);
|
|
max-height: 500px;
|
|
object-fit: contain;
|
|
|
|
margin: 0;
|
|
}
|
|
</style>
|