import { useFetchEvent } from '@/hooks' import { PROFILE_FETCH_RELAY_URLS } from '@/constants' import { getLatestEvent } from '@/lib/event' import { generateBech32IdFromATag, generateBech32IdFromETag } from '@/lib/tag' import { normalizeUrl } from '@/lib/url' import { useNostr } from '@/providers/NostrProvider' import { syncUserDeletionTombstones } from '@/lib/sync-user-deletions' import { queryService } from '@/services/client.service' import { kinds } from 'nostr-tools' import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard' const SHOW_COUNT = 10 const BookmarkList = forwardRef(function BookmarkList(_, ref) { const { t } = useTranslation() const { bookmarkListEvent, pubkey, relayList, updateBookmarkListEvent } = useNostr() const eventIds = useMemo(() => { if (!bookmarkListEvent) return [] return ( bookmarkListEvent.tags .map((tag) => tag[0] === 'e' ? generateBech32IdFromETag(tag) : tag[0] === 'a' ? generateBech32IdFromATag(tag) : null ) .filter(Boolean) as (`nevent1${string}` | `naddr1${string}`)[] ).reverse() }, [bookmarkListEvent]) const [showCount, setShowCount] = useState(SHOW_COUNT) const bottomRef = useRef(null) useImperativeHandle( ref, () => ({ refresh: async () => { if (!pubkey) return await syncUserDeletionTombstones(pubkey, relayList) const urls = Array.from( new Set( [ ...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...(relayList?.write ?? []).map((u) => normalizeUrl(u) || u) ].filter(Boolean) ) ).slice(0, 12) if (urls.length === 0) return try { const events = await queryService.fetchEvents(urls, { kinds: [kinds.BookmarkList], authors: [pubkey], limit: 5 }) const latest = getLatestEvent(events) if (latest) await updateBookmarkListEvent(latest) } catch { /* ignore */ } } }), [pubkey, relayList, updateBookmarkListEvent] ) useEffect(() => { const options = { root: null, rootMargin: '10px', threshold: 0.1 } const loadMore = () => { if (showCount < eventIds.length) { setShowCount((prev) => prev + SHOW_COUNT) } } const observerInstance = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { loadMore() } }, options) const currentBottomRef = bottomRef.current if (currentBottomRef) { observerInstance.observe(currentBottomRef) } return () => { if (observerInstance && currentBottomRef) { observerInstance.unobserve(currentBottomRef) } } }, [showCount, eventIds]) if (eventIds.length === 0) { return (
{t('no bookmarks found')}
) } return (
{eventIds.slice(0, showCount).map((eventId) => ( ))} {showCount < eventIds.length ? (
) : (
{t('no more bookmarks')}
)}
) }) BookmarkList.displayName = 'BookmarkList' export default BookmarkList function BookmarkedNote({ eventId }: { eventId: string }) { const { event, isFetching } = useFetchEvent(eventId) if (isFetching) { return } if (!event) { return null } return }