💨
This commit is contained in:
@@ -6,7 +6,7 @@ import { useMuteList } from '@/providers/MuteListProvider'
|
|||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import threadService from '@/services/thread.service'
|
import threadService from '@/services/thread.service'
|
||||||
import { Event as NEvent } from 'nostr-tools'
|
import { Event as NEvent } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { LoadingBar } from '../LoadingBar'
|
import { LoadingBar } from '../LoadingBar'
|
||||||
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
|
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
|
||||||
@@ -57,15 +57,15 @@ export default function ReplyNoteList({ stuff }: { stuff: NEvent | string }) {
|
|||||||
])
|
])
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const loadingRef = useRef(false)
|
|
||||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const stateRef = useRef({ loading, hasMore, showCount, repliesLength: replies.length })
|
||||||
|
stateRef.current = { loading, hasMore, showCount, repliesLength: replies.length }
|
||||||
|
|
||||||
|
// Initial subscription
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadingRef.current = true
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
threadService.subscribe(stuff, LIMIT).finally(() => {
|
threadService.subscribe(stuff, LIMIT).finally(() => {
|
||||||
loadingRef.current = false
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -74,52 +74,50 @@ export default function ReplyNoteList({ stuff }: { stuff: NEvent | string }) {
|
|||||||
}
|
}
|
||||||
}, [stuff])
|
}, [stuff])
|
||||||
|
|
||||||
useEffect(() => {
|
const loadMore = useCallback(async () => {
|
||||||
const options = {
|
const { loading, hasMore, showCount, repliesLength } = stateRef.current
|
||||||
root: null,
|
|
||||||
rootMargin: '10px',
|
|
||||||
threshold: 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadMore = async () => {
|
if (loading || !hasMore) return
|
||||||
if (showCount < replies.length) {
|
|
||||||
|
// If there are more items to show, increase showCount first
|
||||||
|
if (showCount < repliesLength) {
|
||||||
setShowCount((prev) => prev + SHOW_COUNT)
|
setShowCount((prev) => prev + SHOW_COUNT)
|
||||||
// preload more
|
// Only fetch more data when remaining items are running low
|
||||||
if (replies.length - showCount > LIMIT / 2) {
|
if (repliesLength - showCount > LIMIT / 2) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingRef.current) return
|
|
||||||
|
|
||||||
loadingRef.current = true
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const newHasMore = await threadService.loadMore(stuff, LIMIT)
|
const newHasMore = await threadService.loadMore(stuff, LIMIT)
|
||||||
|
|
||||||
setHasMore(newHasMore)
|
setHasMore(newHasMore)
|
||||||
loadingRef.current = false
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}, [stuff])
|
||||||
|
|
||||||
const observerInstance = new IntersectionObserver((entries) => {
|
// IntersectionObserver setup
|
||||||
if (entries[0].isIntersecting && hasMore) {
|
useEffect(() => {
|
||||||
|
const currentBottomRef = bottomRef.current
|
||||||
|
if (!currentBottomRef) return
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (entries[0].isIntersecting) {
|
||||||
loadMore()
|
loadMore()
|
||||||
}
|
}
|
||||||
}, options)
|
},
|
||||||
|
{
|
||||||
const currentBottomRef = bottomRef.current
|
root: null,
|
||||||
|
rootMargin: '100px',
|
||||||
if (currentBottomRef) {
|
threshold: 0
|
||||||
observerInstance.observe(currentBottomRef)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
observer.observe(currentBottomRef)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (observerInstance && currentBottomRef) {
|
observer.disconnect()
|
||||||
observerInstance.unobserve(currentBottomRef)
|
|
||||||
}
|
}
|
||||||
}
|
}, [loadMore])
|
||||||
}, [replies, showCount, loading, stuff, hasMore])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[80vh]">
|
<div className="min-h-[80vh]">
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ class ThreadService {
|
|||||||
private subscriptions = new Map<
|
private subscriptions = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
closer?: () => void
|
promise: Promise<{
|
||||||
timelineKey?: string
|
closer: () => void
|
||||||
|
timelineKey: string
|
||||||
|
}>
|
||||||
count: number
|
count: number
|
||||||
until?: number
|
until?: number
|
||||||
}
|
}
|
||||||
@@ -54,15 +56,13 @@ class ThreadService {
|
|||||||
const rootInfo = await this.parseRootInfo(stuff)
|
const rootInfo = await this.parseRootInfo(stuff)
|
||||||
if (!rootInfo) return
|
if (!rootInfo) return
|
||||||
|
|
||||||
let subscription = this.subscriptions.get(rootInfo.id)
|
const subscription = this.subscriptions.get(rootInfo.id)
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
subscription.count += 1
|
subscription.count += 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription = { count: 1, until: dayjs().unix() }
|
const _subscribe = async () => {
|
||||||
this.subscriptions.set(rootInfo.id, subscription)
|
|
||||||
|
|
||||||
let relayUrls: string[] = []
|
let relayUrls: string[] = []
|
||||||
const rootPubkey = (rootInfo as { pubkey?: string }).pubkey ?? event?.pubkey
|
const rootPubkey = (rootInfo as { pubkey?: string }).pubkey ?? event?.pubkey
|
||||||
if (rootPubkey) {
|
if (rootPubkey) {
|
||||||
@@ -116,10 +116,7 @@ class ThreadService {
|
|||||||
limit
|
limit
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const { closer, timelineKey } = await client.subscribeTimeline(
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
client
|
|
||||||
.subscribeTimeline(
|
|
||||||
filters.map((filter) => ({
|
filters.map((filter) => ({
|
||||||
urls: relayUrls.slice(0, 8),
|
urls: relayUrls.slice(0, 8),
|
||||||
filter
|
filter
|
||||||
@@ -130,9 +127,11 @@ class ThreadService {
|
|||||||
this.addRepliesToThread(events)
|
this.addRepliesToThread(events)
|
||||||
}
|
}
|
||||||
if (eosed) {
|
if (eosed) {
|
||||||
|
const subscription = this.subscriptions.get(rootInfo.id)
|
||||||
|
if (subscription) {
|
||||||
subscription.until =
|
subscription.until =
|
||||||
events.length >= limit ? events[events.length - 1].created_at - 1 : undefined
|
events.length >= limit ? events[events.length - 1].created_at - 1 : undefined
|
||||||
resolve()
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onNew: (evt) => {
|
onNew: (evt) => {
|
||||||
@@ -140,15 +139,16 @@ class ThreadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(({ closer, timelineKey }) => {
|
return { closer, timelineKey }
|
||||||
subscription.closer = closer
|
}
|
||||||
subscription.timelineKey = timelineKey
|
|
||||||
})
|
const promise = _subscribe()
|
||||||
.catch(() => {
|
this.subscriptions.set(rootInfo.id, {
|
||||||
this.subscriptions.delete(rootInfo.id)
|
promise,
|
||||||
resolve()
|
count: 1,
|
||||||
})
|
until: dayjs().unix()
|
||||||
})
|
})
|
||||||
|
await promise
|
||||||
}
|
}
|
||||||
|
|
||||||
async unsubscribe(stuff: NostrEvent | string) {
|
async unsubscribe(stuff: NostrEvent | string) {
|
||||||
@@ -162,7 +162,9 @@ class ThreadService {
|
|||||||
subscription.count -= 1
|
subscription.count -= 1
|
||||||
if (subscription.count <= 0) {
|
if (subscription.count <= 0) {
|
||||||
this.subscriptions.delete(rootInfo.id)
|
this.subscriptions.delete(rootInfo.id)
|
||||||
subscription.closer?.()
|
subscription.promise.then(({ closer }) => {
|
||||||
|
closer()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
@@ -172,12 +174,13 @@ class ThreadService {
|
|||||||
if (!rootInfo) return false
|
if (!rootInfo) return false
|
||||||
|
|
||||||
const subscription = this.subscriptions.get(rootInfo.id)
|
const subscription = this.subscriptions.get(rootInfo.id)
|
||||||
if (!subscription) return false
|
if (!subscription || !subscription.until) return false
|
||||||
|
|
||||||
const { timelineKey, until } = subscription
|
const { timelineKey } = await subscription.promise
|
||||||
if (!timelineKey || !until) return false
|
console.log('loadMore', { timelineKey, until: subscription.until })
|
||||||
|
if (!timelineKey) return false
|
||||||
|
|
||||||
const events = await client.loadMoreTimeline(timelineKey, until, limit)
|
const events = await client.loadMoreTimeline(timelineKey, subscription.until, limit)
|
||||||
this.addRepliesToThread(events)
|
this.addRepliesToThread(events)
|
||||||
|
|
||||||
const { event } = this.resolveStuff(stuff)
|
const { event } = this.resolveStuff(stuff)
|
||||||
|
|||||||
Reference in New Issue
Block a user