You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.4 KiB
115 lines
3.4 KiB
<script lang='ts'> |
|
import { indexKind } from '$lib/consts'; |
|
import { ndkInstance } from '$lib/ndk'; |
|
import { filterValidIndexEvents } from '$lib/utils'; |
|
import { NDKRelaySet, type NDKEvent } from '@nostr-dev-kit/ndk'; |
|
import { Button, P, Skeleton, Spinner } from 'flowbite-svelte'; |
|
import ArticleHeader from './PublicationHeader.svelte'; |
|
import { onMount } from 'svelte'; |
|
|
|
let { relays } = $props<{ relays: string[] }>(); |
|
|
|
let eventsInView: NDKEvent[] = $state([]); |
|
let loadingMore: boolean = $state(false); |
|
let endOfFeed: boolean = $state(false); |
|
|
|
let cutoffTimestamp: number = $derived( |
|
eventsInView?.at(eventsInView.length - 1)?.created_at ?? new Date().getTime() |
|
); |
|
|
|
async function getEvents( |
|
before: number | undefined = undefined, |
|
): Promise<void> { |
|
let eventSet = await $ndkInstance.fetchEvents( |
|
{ |
|
kinds: [indexKind], |
|
limit: 16, |
|
until: before, |
|
}, |
|
{ |
|
groupable: false, |
|
skipVerification: false, |
|
skipValidation: false, |
|
}, |
|
NDKRelaySet.fromRelayUrls(relays, $ndkInstance) |
|
); |
|
eventSet = filterValidIndexEvents(eventSet); |
|
|
|
let eventArray = Array.from(eventSet); |
|
eventArray?.sort((a, b) => b.created_at! - a.created_at!); |
|
|
|
if (!eventArray) { |
|
return; |
|
} |
|
|
|
endOfFeed = eventArray?.at(eventArray.length - 1)?.id === eventsInView?.at(eventsInView.length - 1)?.id; |
|
|
|
if (endOfFeed) { |
|
return; |
|
} |
|
|
|
const eventMap = new Map([...eventsInView, ...eventArray].map(event => [event.id, event])); |
|
const allEvents = Array.from(eventMap.values()); |
|
const uniqueIds = new Set(allEvents.map(event => event.id)); |
|
eventsInView = Array.from(uniqueIds) |
|
.map(id => eventMap.get(id)) |
|
.filter(event => event != null) as NDKEvent[]; |
|
} |
|
|
|
const getSkeletonIds = (): string[] => { |
|
const skeletonHeight = 124; // The height of the skeleton component in pixels. |
|
|
|
// Determine the number of skeletons to display based on the height of the screen. |
|
const skeletonCount = Math.floor(window.innerHeight / skeletonHeight) - 2; |
|
|
|
const skeletonIds = []; |
|
for (let i = 0; i < skeletonCount; i++) { |
|
skeletonIds.push(`skeleton-${i}`); |
|
} |
|
return skeletonIds; |
|
} |
|
|
|
async function loadMorePublications() { |
|
loadingMore = true; |
|
await getEvents(cutoffTimestamp); |
|
loadingMore = false; |
|
} |
|
|
|
onMount(async () => { |
|
await getEvents(); |
|
}); |
|
</script> |
|
|
|
<div class='leather flex flex-col space-y-4'> |
|
{#if eventsInView.length === 0} |
|
{#each getSkeletonIds() as id} |
|
<Skeleton divClass='skeleton-leather w-full' size='lg' /> |
|
{/each} |
|
{:else if eventsInView.length > 0} |
|
{#each eventsInView as event} |
|
<ArticleHeader {event} /> |
|
{/each} |
|
{:else} |
|
<p class='text-center'>No publications found.</p> |
|
{/if} |
|
{#if !loadingMore && !endOfFeed} |
|
<div class='flex justify-center mt-4 mb-8'> |
|
<Button outline class="w-full" onclick={async () => { |
|
await loadMorePublications(); |
|
}}> |
|
Show more publications |
|
</Button> |
|
</div> |
|
{:else if loadingMore} |
|
<div class='flex justify-center mt-4 mb-8'> |
|
<Button outline disabled class="w-full"> |
|
<Spinner class='mr-3 text-gray-300' size='4' /> |
|
Loading... |
|
</Button> |
|
</div> |
|
{:else} |
|
<div class='flex justify-center mt-4 mb-8'> |
|
<P class='text-sm text-gray-600'>You've reached the end of the feed.</P> |
|
</div> |
|
{/if} |
|
</div>
|
|
|