refactor: reverse top-level replies order
This commit is contained in:
@@ -92,6 +92,7 @@ const NoteList = forwardRef<
|
||||
const supportTouch = useMemo(() => isTouchDevice(), [])
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
const topRef = useRef<HTMLDivElement | null>(null)
|
||||
const loadingRef = useRef(false)
|
||||
|
||||
const shouldHideEvent = useCallback(
|
||||
(evt: Event) => {
|
||||
@@ -273,12 +274,14 @@ const NoteList = forwardRef<
|
||||
if (!subRequests.length) return
|
||||
|
||||
async function init() {
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
setEvents([])
|
||||
setNewEvents([])
|
||||
setHasMore(true)
|
||||
|
||||
if (showKinds?.length === 0 && subRequests.every(({ filter }) => !filter.kinds)) {
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
setHasMore(false)
|
||||
return () => {}
|
||||
@@ -309,6 +312,7 @@ const NoteList = forwardRef<
|
||||
setHasMore(false)
|
||||
}
|
||||
if (eosed) {
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
addReplies(events)
|
||||
}
|
||||
@@ -374,13 +378,15 @@ const NoteList = forwardRef<
|
||||
}
|
||||
}
|
||||
|
||||
if (!timelineKey || loading || !hasMore) return
|
||||
if (!timelineKey || loadingRef.current || !hasMore) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
const newEvents = await client.loadMoreTimeline(
|
||||
timelineKey,
|
||||
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
|
||||
LIMIT
|
||||
)
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
if (newEvents.length === 0) {
|
||||
setHasMore(false)
|
||||
@@ -406,7 +412,7 @@ const NoteList = forwardRef<
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [loading, hasMore, events, showCount, timelineKey])
|
||||
}, [hasMore, events, showCount, timelineKey])
|
||||
|
||||
const showNewEvents = () => {
|
||||
setEvents((oldEvents) => [...newEvents, ...oldEvents])
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function ReplyNote({
|
||||
)}
|
||||
onClick={() => push(toNote(event))}
|
||||
>
|
||||
{hasReplies && <div className="absolute left-[34px] top-14 bottom-0 border-l" />}
|
||||
{hasReplies && <div className="absolute left-[34px] top-14 bottom-0 border-l z-20" />}
|
||||
<Collapsible>
|
||||
<div className="flex space-x-2 items-start px-4 pt-3">
|
||||
<UserAvatar userId={event.pubkey} size="medium" className="shrink-0 mt-0.5" />
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function SubReplies({ parentKey }: { parentKey: string }) {
|
||||
className="relative w-full flex items-center gap-1.5 pl-14 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors clickable"
|
||||
>
|
||||
<div
|
||||
className={cn('absolute left-[34px] top-0 bottom-0 w-px text-border')}
|
||||
className={cn('absolute left-[34px] top-0 bottom-0 w-px text-border z-20')}
|
||||
style={{
|
||||
background: isExpanded
|
||||
? 'currentColor'
|
||||
@@ -132,9 +132,9 @@ export default function SubReplies({ parentKey }: { parentKey: string }) {
|
||||
key={currentReplyKey}
|
||||
className="scroll-mt-12 flex relative"
|
||||
>
|
||||
<div className="absolute left-[34px] top-0 h-8 w-4 rounded-bl-lg border-l border-b" />
|
||||
<div className="absolute left-[34px] top-0 h-8 w-4 rounded-bl-lg border-l border-b z-20" />
|
||||
{index < replies.length - 1 && (
|
||||
<div className="absolute left-[34px] top-0 bottom-0 border-l" />
|
||||
<div className="absolute left-[34px] top-0 bottom-0 border-l z-20" />
|
||||
)}
|
||||
<ReplyNote
|
||||
className="flex-1 w-0 pl-10"
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useReply } from '@/providers/ReplyProvider'
|
||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { Filter, Event as NEvent, kinds } from 'nostr-tools'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { LoadingBar } from '../LoadingBar'
|
||||
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
|
||||
@@ -69,7 +69,7 @@ export default function ReplyNoteList({
|
||||
replyKeySet.add(key)
|
||||
return true
|
||||
})
|
||||
return replyEvents.sort((a, b) => a.created_at - b.created_at)
|
||||
return replyEvents.sort((a, b) => b.created_at - a.created_at)
|
||||
}, [
|
||||
stuffKey,
|
||||
repliesMap,
|
||||
@@ -79,8 +79,9 @@ export default function ReplyNoteList({
|
||||
])
|
||||
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
|
||||
const [until, setUntil] = useState<number | undefined>(undefined)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const loadingRef = useRef(false)
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -125,9 +126,10 @@ export default function ReplyNoteList({
|
||||
}, [event])
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || !rootInfo || currentIndex !== index) return
|
||||
if (loadingRef.current || !rootInfo || currentIndex !== index) return
|
||||
|
||||
const init = async () => {
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
@@ -195,6 +197,7 @@ export default function ReplyNoteList({
|
||||
addReplies(evts)
|
||||
}
|
||||
if (eosed) {
|
||||
loadingRef.current = false
|
||||
setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined)
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -207,6 +210,7 @@ export default function ReplyNoteList({
|
||||
setTimelineKey(timelineKey)
|
||||
return closer
|
||||
} catch {
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
}
|
||||
return
|
||||
@@ -218,12 +222,6 @@ export default function ReplyNoteList({
|
||||
}
|
||||
}, [rootInfo, currentIndex, index])
|
||||
|
||||
useEffect(() => {
|
||||
if (replies.length === 0) {
|
||||
loadMore()
|
||||
}
|
||||
}, [replies])
|
||||
|
||||
useEffect(() => {
|
||||
const options = {
|
||||
root: null,
|
||||
@@ -231,9 +229,28 @@ export default function ReplyNoteList({
|
||||
threshold: 0.1
|
||||
}
|
||||
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && showCount < replies.length) {
|
||||
const loadMore = async () => {
|
||||
if (showCount < replies.length) {
|
||||
setShowCount((prev) => prev + SHOW_COUNT)
|
||||
// preload more
|
||||
if (replies.length - showCount > LIMIT / 2) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (loadingRef.current || !until || !timelineKey) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
const events = await client.loadMoreTimeline(timelineKey, until, LIMIT)
|
||||
addReplies(events)
|
||||
setUntil(events.length ? events[events.length - 1].created_at - 1 : undefined)
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !!until) {
|
||||
loadMore()
|
||||
}
|
||||
}, options)
|
||||
|
||||
@@ -248,41 +265,24 @@ export default function ReplyNoteList({
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [replies, showCount])
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loading || !until || !timelineKey) return
|
||||
|
||||
setLoading(true)
|
||||
const events = await client.loadMoreTimeline(timelineKey, until, LIMIT)
|
||||
addReplies(events)
|
||||
setUntil(events.length ? events[events.length - 1].created_at - 1 : undefined)
|
||||
setLoading(false)
|
||||
}, [loading, until, timelineKey])
|
||||
}, [replies, showCount, until, timelineKey])
|
||||
|
||||
return (
|
||||
<div className="min-h-[80vh]">
|
||||
{loading && <LoadingBar />}
|
||||
{!loading && until && (!event || until > event.created_at) && (
|
||||
<div
|
||||
className={`text-sm text-center text-muted-foreground border-b py-2 ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}
|
||||
onClick={loadMore}
|
||||
>
|
||||
{t('load more older replies')}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{replies.slice(0, showCount).map((reply) => (
|
||||
<Item key={reply.id} reply={reply} />
|
||||
))}
|
||||
</div>
|
||||
{!loading && (
|
||||
{!!until || showCount < replies.length || loading ? (
|
||||
<ReplyNoteSkeleton />
|
||||
) : (
|
||||
<div className="text-sm mt-2 mb-3 text-center text-muted-foreground">
|
||||
{replies.length > 0 ? t('no more replies') : t('no replies')}
|
||||
</div>
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
{loading && <ReplyNoteSkeleton />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user