index.tsx raw

   1  import { useFetchEvent } from '@/hooks'
   2  import { generateBech32IdFromATag, generateBech32IdFromETag } from '@/lib/tag'
   3  import { useNostr } from '@/providers/NostrProvider'
   4  import { useEffect, useMemo, useRef, useState } from 'react'
   5  import { useTranslation } from 'react-i18next'
   6  import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
   7  
   8  const SHOW_COUNT = 10
   9  
  10  export default function BookmarkList() {
  11    const { t } = useTranslation()
  12    const { bookmarkListEvent } = useNostr()
  13    const eventIds = useMemo(() => {
  14      if (!bookmarkListEvent) return []
  15  
  16      return (
  17        bookmarkListEvent.tags
  18          .map((tag) =>
  19            tag[0] === 'e'
  20              ? generateBech32IdFromETag(tag)
  21              : tag[0] === 'a'
  22                ? generateBech32IdFromATag(tag)
  23                : null
  24          )
  25          .filter(Boolean) as (`nevent1${string}` | `naddr1${string}`)[]
  26      ).reverse()
  27    }, [bookmarkListEvent])
  28    const [showCount, setShowCount] = useState(SHOW_COUNT)
  29    const bottomRef = useRef<HTMLDivElement | null>(null)
  30  
  31    useEffect(() => {
  32      const options = {
  33        root: null,
  34        rootMargin: '10px',
  35        threshold: 0.1
  36      }
  37  
  38      const loadMore = () => {
  39        if (showCount < eventIds.length) {
  40          setShowCount((prev) => prev + SHOW_COUNT)
  41        }
  42      }
  43  
  44      const observerInstance = new IntersectionObserver((entries) => {
  45        if (entries[0].isIntersecting) {
  46          loadMore()
  47        }
  48      }, options)
  49  
  50      const currentBottomRef = bottomRef.current
  51  
  52      if (currentBottomRef) {
  53        observerInstance.observe(currentBottomRef)
  54      }
  55  
  56      return () => {
  57        if (observerInstance && currentBottomRef) {
  58          observerInstance.unobserve(currentBottomRef)
  59        }
  60      }
  61    }, [showCount, eventIds])
  62  
  63    if (eventIds.length === 0) {
  64      return (
  65        <div className="mt-2 text-sm text-center text-muted-foreground">
  66          {t('no bookmarks found')}
  67        </div>
  68      )
  69    }
  70  
  71    return (
  72      <div>
  73        {eventIds.slice(0, showCount).map((eventId) => (
  74          <BookmarkedNote key={eventId} eventId={eventId} />
  75        ))}
  76  
  77        {showCount < eventIds.length ? (
  78          <div ref={bottomRef}>
  79            <NoteCardLoadingSkeleton />
  80          </div>
  81        ) : (
  82          <div className="text-center text-sm text-muted-foreground mt-2">
  83            {t('no more bookmarks')}
  84          </div>
  85        )}
  86      </div>
  87    )
  88  }
  89  
  90  function BookmarkedNote({ eventId }: { eventId: string }) {
  91    const { event, isFetching } = useFetchEvent(eventId)
  92  
  93    if (isFetching) {
  94      return <NoteCardLoadingSkeleton className="border-b" />
  95    }
  96  
  97    if (!event) {
  98      return null
  99    }
 100  
 101    return <NoteCard event={event} className="w-full" />
 102  }
 103