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