refactor: subscribe

This commit is contained in:
codytseng
2025-04-11 22:53:51 +08:00
parent 2a4968568a
commit 74d7f9be29
5 changed files with 157 additions and 188 deletions

View File

@@ -22,7 +22,7 @@ export default function Nip22ReplyNoteList({
className?: string
}) {
const { t } = useTranslation()
const { pubkey } = useNostr()
const { pubkey, startLogin } = useNostr()
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
const [until, setUntil] = useState<number | undefined>(() => dayjs().unix())
const [replies, setReplies] = useState<NEvent[]>([])
@@ -76,7 +76,9 @@ export default function Nip22ReplyNoteList({
},
{
onEvents: (evts, eosed) => {
setReplies(evts.reverse())
if (evts.length > 0) {
setReplies(evts.reverse())
}
if (eosed) {
setLoading(false)
setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined)
@@ -85,6 +87,9 @@ export default function Nip22ReplyNoteList({
onNew: (evt) => {
onNewReply(evt)
}
},
{
startLogin
}
)
setTimelineKey(timelineKey)

View File

@@ -14,7 +14,7 @@ import relayInfoService from '@/services/relay-info.service'
import { TNoteListMode } from '@/types'
import dayjs from 'dayjs'
import { Event, Filter, kinds } from 'nostr-tools'
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh'
import NoteCard from '../NoteCard'
@@ -47,7 +47,7 @@ export default function NoteList({
const [newEvents, setNewEvents] = useState<Event[]>([])
const [showCount, setShowCount] = useState(SHOW_COUNT)
const [hasMore, setHasMore] = useState<boolean>(true)
const [refreshing, setRefreshing] = useState(true)
const [loading, setLoading] = useState(true)
const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode())
const bottomRef = useRef<HTMLDivElement | null>(null)
const isPictures = useMemo(() => listMode === 'pictures', [listMode])
@@ -63,7 +63,7 @@ export default function NoteList({
if (relayUrls.length === 0 && !noteFilter.authors?.length) return
async function init() {
setRefreshing(true)
setLoading(true)
setEvents([])
setNewEvents([])
setHasMore(true)
@@ -74,15 +74,11 @@ export default function NoteList({
areAlgoRelays = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo))
}
let eventCount = 0
const { closer, timelineKey } = await client.subscribeTimeline(
[...relayUrls],
{ ...noteFilter, limit: areAlgoRelays ? ALGO_LIMIT : LIMIT },
{
onEvents: (events, eosed) => {
if (eventCount > events.length) return
eventCount = events.length
if (events.length > 0) {
setEvents(events)
}
@@ -90,7 +86,7 @@ export default function NoteList({
setHasMore(false)
}
if (eosed) {
setRefreshing(false)
setLoading(false)
setHasMore(events.length > 0)
}
},
@@ -115,25 +111,6 @@ export default function NoteList({
}
}, [JSON.stringify(relayUrls), noteFilter, refreshCount])
const loadMore = useCallback(async () => {
if (showCount < events.length) {
setShowCount((prev) => prev + SHOW_COUNT)
return
}
if (!timelineKey || refreshing || !hasMore) return
const newEvents = await client.loadMoreTimeline(
timelineKey,
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
LIMIT
)
if (newEvents.length === 0) {
setHasMore(false)
return
}
setEvents((oldEvents) => [...oldEvents, ...newEvents])
}, [timelineKey, refreshing, hasMore, events, noteFilter, showCount])
useEffect(() => {
const options = {
root: null,
@@ -141,6 +118,30 @@ export default function NoteList({
threshold: 0.1
}
const loadMore = async () => {
if (showCount < events.length) {
setShowCount((prev) => prev + SHOW_COUNT)
// preload more
if (events.length - showCount > LIMIT / 2) {
return
}
}
if (!timelineKey || loading || !hasMore) return
setLoading(true)
const newEvents = await client.loadMoreTimeline(
timelineKey,
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
LIMIT
)
setLoading(false)
if (newEvents.length === 0) {
setHasMore(false)
return
}
setEvents((oldEvents) => [...oldEvents, ...newEvents])
}
const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
loadMore()
@@ -158,7 +159,7 @@ export default function NoteList({
observerInstance.unobserve(currentBottomRef)
}
}
}, [loadMore])
}, [timelineKey, loading, hasMore, events, noteFilter, showCount])
const showNewEvents = () => {
topRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
@@ -214,7 +215,7 @@ export default function NoteList({
))}
</div>
)}
{hasMore || refreshing ? (
{hasMore || loading ? (
<div ref={bottomRef}>
<LoadingSkeleton isPictures={isPictures} />
</div>

View File

@@ -10,15 +10,7 @@ import storage from '@/services/local-storage.service'
import { TNotificationType } from '@/types'
import dayjs from 'dayjs'
import { Event, kinds } from 'nostr-tools'
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState
} from 'react'
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh'
import { NotificationItem } from './NotificationItem'
@@ -34,7 +26,7 @@ const NotificationList = forwardRef((_, ref) => {
const [lastReadTime, setLastReadTime] = useState(0)
const [refreshCount, setRefreshCount] = useState(0)
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
const [refreshing, setRefreshing] = useState(true)
const [loading, setLoading] = useState(true)
const [notifications, setNotifications] = useState<Event[]>([])
const [newNotifications, setNewNotifications] = useState<Event[]>([])
const [oldNotifications, setOldNotifications] = useState<Event[]>([])
@@ -57,11 +49,11 @@ const NotificationList = forwardRef((_, ref) => {
ref,
() => ({
refresh: () => {
if (refreshing) return
if (loading) return
setRefreshCount((count) => count + 1)
}
}),
[refreshing]
[loading]
)
useEffect(() => {
@@ -71,12 +63,12 @@ const NotificationList = forwardRef((_, ref) => {
}
const init = async () => {
setRefreshing(true)
setLoading(true)
setNotifications([])
setShowCount(SHOW_COUNT)
setLastReadTime(storage.getLastReadNotificationTime(pubkey))
const relayList = await client.fetchRelayList(pubkey)
let eventCount = 0
const { closer, timelineKey } = await client.subscribeTimeline(
relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
{
@@ -86,11 +78,11 @@ const NotificationList = forwardRef((_, ref) => {
},
{
onEvents: (events, eosed) => {
if (eventCount > events.length) return
eventCount = events.length
setNotifications(events.filter((event) => event.pubkey !== pubkey))
if (events.length > 0) {
setNotifications(events.filter((event) => event.pubkey !== pubkey))
}
if (eosed) {
setRefreshing(false)
setLoading(false)
setUntil(events.length > 0 ? events[events.length - 1].created_at - 1 : undefined)
updateNoteStatsByEvents(events)
}
@@ -132,30 +124,6 @@ const NotificationList = forwardRef((_, ref) => {
}
}, [notifications, lastReadTime, showCount])
const loadMore = useCallback(async () => {
if (showCount < notifications.length) {
setShowCount((count) => count + SHOW_COUNT)
return
}
if (!pubkey || !timelineKey || !until || refreshing) return
const newNotifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
if (newNotifications.length === 0) {
setUntil(undefined)
return
}
if (newNotifications.length > 0) {
setNotifications((oldNotifications) => [
...oldNotifications,
...newNotifications.filter((event) => event.pubkey !== pubkey)
])
}
setUntil(newNotifications[newNotifications.length - 1].created_at - 1)
}, [pubkey, timelineKey, until, refreshing, showCount, notifications])
useEffect(() => {
const options = {
root: null,
@@ -163,6 +131,34 @@ const NotificationList = forwardRef((_, ref) => {
threshold: 1
}
const loadMore = async () => {
if (showCount < notifications.length) {
setShowCount((count) => count + SHOW_COUNT)
// preload more
if (notifications.length - showCount > LIMIT / 2) {
return
}
}
if (!pubkey || !timelineKey || !until || loading) return
setLoading(true)
const newNotifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
setLoading(false)
if (newNotifications.length === 0) {
setUntil(undefined)
return
}
if (newNotifications.length > 0) {
setNotifications((oldNotifications) => [
...oldNotifications,
...newNotifications.filter((event) => event.pubkey !== pubkey)
])
}
setUntil(newNotifications[newNotifications.length - 1].created_at - 1)
}
const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMore()
@@ -180,7 +176,7 @@ const NotificationList = forwardRef((_, ref) => {
observerInstance.unobserve(currentBottomRef)
}
}
}, [loadMore])
}, [pubkey, timelineKey, until, loading, showCount, notifications])
return (
<div>
@@ -214,7 +210,7 @@ const NotificationList = forwardRef((_, ref) => {
<NotificationItem key={notification.id} notification={notification} />
))}
<div className="text-center text-sm text-muted-foreground">
{until || refreshing ? (
{until || loading ? (
<div ref={bottomRef}>
<div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" />

View File

@@ -105,7 +105,9 @@ export default function ReplyNoteList({
},
{
onEvents: (evts, eosed) => {
setEvents(evts.filter((evt) => isReplyNoteEvent(evt)).reverse())
if (evts.length > 0) {
setEvents(evts.filter((evt) => isReplyNoteEvent(evt)).reverse())
}
if (eosed) {
setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined)
setLoading(false)