refactor: replies
This commit is contained in:
parent
25233344e7
commit
63c9713ea8
5 changed files with 166 additions and 138 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { isMentioningMutedUsers } from '@/lib/event'
|
import { getEventKey, isMentioningMutedUsers } from '@/lib/event'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
|
|
@ -20,27 +20,35 @@ export default function ReplyButton({ event }: { event: Event }) {
|
||||||
const { mutePubkeySet } = useMuteList()
|
const { mutePubkeySet } = useMuteList()
|
||||||
const { hideContentMentioningMutedUsers } = useContentPolicy()
|
const { hideContentMentioningMutedUsers } = useContentPolicy()
|
||||||
const { replyCount, hasReplied } = useMemo(() => {
|
const { replyCount, hasReplied } = useMemo(() => {
|
||||||
|
const key = getEventKey(event)
|
||||||
const hasReplied = pubkey
|
const hasReplied = pubkey
|
||||||
? repliesMap.get(event.id)?.events.some((evt) => evt.pubkey === pubkey)
|
? repliesMap.get(key)?.events.some((evt) => evt.pubkey === pubkey)
|
||||||
: false
|
: false
|
||||||
|
|
||||||
return {
|
let replyCount = 0
|
||||||
replyCount:
|
const replies = [...(repliesMap.get(key)?.events || [])]
|
||||||
repliesMap.get(event.id)?.events.filter((evt) => {
|
while (replies.length > 0) {
|
||||||
if (hideUntrustedInteractions && !isUserTrusted(evt.pubkey)) {
|
const reply = replies.pop()
|
||||||
return false
|
if (!reply) break
|
||||||
|
|
||||||
|
const replyKey = getEventKey(reply)
|
||||||
|
const nestedReplies = repliesMap.get(replyKey)?.events ?? []
|
||||||
|
replies.push(...nestedReplies)
|
||||||
|
|
||||||
|
if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if (mutePubkeySet.has(evt.pubkey)) {
|
if (mutePubkeySet.has(reply.pubkey)) {
|
||||||
return false
|
continue
|
||||||
}
|
}
|
||||||
if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) {
|
if (hideContentMentioningMutedUsers && isMentioningMutedUsers(reply, mutePubkeySet)) {
|
||||||
return false
|
continue
|
||||||
}
|
}
|
||||||
return true
|
replyCount++
|
||||||
}).length ?? 0,
|
|
||||||
hasReplied
|
|
||||||
}
|
}
|
||||||
}, [repliesMap, event.id, hideUntrustedInteractions])
|
|
||||||
|
return { replyCount, hasReplied }
|
||||||
|
}, [repliesMap, event, hideUntrustedInteractions])
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
||||||
import {
|
import {
|
||||||
getParentETag,
|
getEventKey,
|
||||||
|
getEventKeyFromTag,
|
||||||
|
getParentTag,
|
||||||
getReplaceableCoordinateFromEvent,
|
getReplaceableCoordinateFromEvent,
|
||||||
getRootATag,
|
getRootTag,
|
||||||
getRootETag,
|
|
||||||
getRootEventHexId,
|
|
||||||
isMentioningMutedUsers,
|
isMentioningMutedUsers,
|
||||||
isReplaceableEvent,
|
isReplaceableEvent,
|
||||||
isReplyNoteEvent
|
isReplyNoteEvent
|
||||||
} from '@/lib/event'
|
} from '@/lib/event'
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
import { generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
import { generateBech32IdFromATag, generateBech32IdFromETag, tagNameEquals } from '@/lib/tag'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
|
|
@ -40,23 +40,22 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
const [rootInfo, setRootInfo] = useState<TRootInfo | undefined>(undefined)
|
const [rootInfo, setRootInfo] = useState<TRootInfo | undefined>(undefined)
|
||||||
const { repliesMap, addReplies } = useReply()
|
const { repliesMap, addReplies } = useReply()
|
||||||
const replies = useMemo(() => {
|
const replies = useMemo(() => {
|
||||||
const replyIdSet = new Set<string>()
|
const replyKeySet = new Set<string>()
|
||||||
const replyEvents: NEvent[] = []
|
const replyEvents: NEvent[] = []
|
||||||
const currentEventKey = isReplaceableEvent(event.kind)
|
const currentEventKey = getEventKey(event)
|
||||||
? getReplaceableCoordinateFromEvent(event)
|
|
||||||
: event.id
|
|
||||||
let parentEventKeys = [currentEventKey]
|
let parentEventKeys = [currentEventKey]
|
||||||
while (parentEventKeys.length > 0) {
|
while (parentEventKeys.length > 0) {
|
||||||
const events = parentEventKeys.flatMap((id) => repliesMap.get(id)?.events || [])
|
const events = parentEventKeys.flatMap((key) => repliesMap.get(key)?.events || [])
|
||||||
events.forEach((evt) => {
|
events.forEach((evt) => {
|
||||||
if (replyIdSet.has(evt.id)) return
|
const key = getEventKey(evt)
|
||||||
|
if (replyKeySet.has(key)) return
|
||||||
if (mutePubkeySet.has(evt.pubkey)) return
|
if (mutePubkeySet.has(evt.pubkey)) return
|
||||||
if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) return
|
if (hideContentMentioningMutedUsers && isMentioningMutedUsers(evt, mutePubkeySet)) return
|
||||||
|
|
||||||
replyIdSet.add(evt.id)
|
replyKeySet.add(key)
|
||||||
replyEvents.push(evt)
|
replyEvents.push(evt)
|
||||||
})
|
})
|
||||||
parentEventKeys = events.map((evt) => evt.id)
|
parentEventKeys = events.map((evt) => getEventKey(evt))
|
||||||
}
|
}
|
||||||
return replyEvents.sort((a, b) => a.created_at - b.created_at)
|
return replyEvents.sort((a, b) => a.created_at - b.created_at)
|
||||||
}, [event.id, repliesMap])
|
}, [event.id, repliesMap])
|
||||||
|
|
@ -64,7 +63,7 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
const [until, setUntil] = useState<number | undefined>(undefined)
|
const [until, setUntil] = useState<number | undefined>(undefined)
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||||
const [highlightReplyId, setHighlightReplyId] = useState<string | undefined>(undefined)
|
const [highlightReplyKey, setHighlightReplyKey] = useState<string | undefined>(undefined)
|
||||||
const replyRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
const replyRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
||||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
|
@ -79,13 +78,14 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
relay: client.getEventHint(event.id)
|
relay: client.getEventHint(event.id)
|
||||||
}
|
}
|
||||||
: { type: 'E', id: event.id, pubkey: event.pubkey }
|
: { type: 'E', id: event.id, pubkey: event.pubkey }
|
||||||
const rootETag = getRootETag(event)
|
|
||||||
if (rootETag) {
|
const rootTag = getRootTag(event)
|
||||||
const [, rootEventHexId, , , rootEventPubkey] = rootETag
|
if (rootTag?.type === 'e') {
|
||||||
|
const [, rootEventHexId, , , rootEventPubkey] = rootTag.tag
|
||||||
if (rootEventHexId && rootEventPubkey) {
|
if (rootEventHexId && rootEventPubkey) {
|
||||||
root = { type: 'E', id: rootEventHexId, pubkey: rootEventPubkey }
|
root = { type: 'E', id: rootEventHexId, pubkey: rootEventPubkey }
|
||||||
} else {
|
} else {
|
||||||
const rootEventId = generateBech32IdFromETag(rootETag)
|
const rootEventId = generateBech32IdFromETag(rootTag.tag)
|
||||||
if (rootEventId) {
|
if (rootEventId) {
|
||||||
const rootEvent = await client.fetchEvent(rootEventId)
|
const rootEvent = await client.fetchEvent(rootEventId)
|
||||||
if (rootEvent) {
|
if (rootEvent) {
|
||||||
|
|
@ -93,13 +93,11 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (event.kind === ExtendedKind.COMMENT) {
|
} else if (rootTag?.type === 'a') {
|
||||||
const rootATag = getRootATag(event)
|
const [, coordinate, relay] = rootTag.tag
|
||||||
if (rootATag) {
|
|
||||||
const [, coordinate, relay] = rootATag
|
|
||||||
const [, pubkey] = coordinate.split(':')
|
const [, pubkey] = coordinate.split(':')
|
||||||
root = { type: 'A', id: coordinate, eventId: event.id, pubkey, relay }
|
root = { type: 'A', id: coordinate, eventId: event.id, pubkey, relay }
|
||||||
}
|
} else {
|
||||||
const rootITag = event.tags.find(tagNameEquals('I'))
|
const rootITag = event.tags.find(tagNameEquals('I'))
|
||||||
if (rootITag) {
|
if (rootITag) {
|
||||||
root = { type: 'I', id: rootITag[1] }
|
root = { type: 'I', id: rootITag[1] }
|
||||||
|
|
@ -110,27 +108,6 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
fetchRootEvent()
|
fetchRootEvent()
|
||||||
}, [event])
|
}, [event])
|
||||||
|
|
||||||
const onNewReply = useCallback((evt: NEvent) => {
|
|
||||||
addReplies([evt])
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!rootInfo) return
|
|
||||||
const handleEventPublished = (data: Event) => {
|
|
||||||
const customEvent = data as CustomEvent<NEvent>
|
|
||||||
const evt = customEvent.detail
|
|
||||||
const rootId = getRootEventHexId(evt)
|
|
||||||
if (rootId === rootInfo.id && isReplyNoteEvent(evt)) {
|
|
||||||
onNewReply(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.addEventListener('newEvent', handleEventPublished)
|
|
||||||
return () => {
|
|
||||||
client.removeEventListener('newEvent', handleEventPublished)
|
|
||||||
}
|
|
||||||
}, [rootInfo, onNewReply])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || !rootInfo || currentIndex !== index) return
|
if (loading || !rootInfo || currentIndex !== index) return
|
||||||
|
|
||||||
|
|
@ -222,7 +199,7 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
return () => {
|
return () => {
|
||||||
promise.then((closer) => closer?.())
|
promise.then((closer) => closer?.())
|
||||||
}
|
}
|
||||||
}, [rootInfo, currentIndex, index, onNewReply])
|
}, [rootInfo, currentIndex, index])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (replies.length === 0) {
|
if (replies.length === 0) {
|
||||||
|
|
@ -269,16 +246,23 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [loading, until, timelineKey])
|
}, [loading, until, timelineKey])
|
||||||
|
|
||||||
const highlightReply = useCallback((eventId: string, scrollTo = true) => {
|
const highlightReply = useCallback((key: string, eventId?: string, scrollTo = true) => {
|
||||||
|
let found = false
|
||||||
if (scrollTo) {
|
if (scrollTo) {
|
||||||
const ref = replyRefs.current[eventId]
|
const ref = replyRefs.current[key]
|
||||||
if (ref) {
|
if (ref) {
|
||||||
|
found = true
|
||||||
ref.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
ref.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setHighlightReplyId(eventId)
|
if (!found) {
|
||||||
|
if (eventId) push(toNote(eventId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setHighlightReplyKey(key)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setHighlightReplyId((pre) => (pre === eventId ? undefined : pre))
|
setHighlightReplyKey((pre) => (pre === key ? undefined : pre))
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
@ -296,7 +280,8 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
<div>
|
<div>
|
||||||
{replies.slice(0, showCount).map((reply) => {
|
{replies.slice(0, showCount).map((reply) => {
|
||||||
if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) {
|
if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) {
|
||||||
const repliesForThisReply = repliesMap.get(reply.id)
|
const replyKey = getEventKey(reply)
|
||||||
|
const repliesForThisReply = repliesMap.get(replyKey)
|
||||||
// If the reply is not trusted and there are no trusted replies for this reply, skip rendering
|
// If the reply is not trusted and there are no trusted replies for this reply, skip rendering
|
||||||
if (
|
if (
|
||||||
!repliesForThisReply ||
|
!repliesForThisReply ||
|
||||||
|
|
@ -306,27 +291,29 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentETag = getParentETag(reply)
|
const rootEventKey = getEventKey(event)
|
||||||
const parentEventHexId = parentETag?.[1]
|
const currentReplyKey = getEventKey(reply)
|
||||||
const parentEventId = parentETag ? generateBech32IdFromETag(parentETag) : undefined
|
const parentTag = getParentTag(reply)
|
||||||
|
const parentEventKey = parentTag ? getEventKeyFromTag(parentTag.tag) : undefined
|
||||||
|
const parentEventId = parentTag
|
||||||
|
? parentTag.type === 'e'
|
||||||
|
? generateBech32IdFromETag(parentTag.tag)
|
||||||
|
: generateBech32IdFromATag(parentTag.tag)
|
||||||
|
: undefined
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={(el) => (replyRefs.current[reply.id] = el)}
|
ref={(el) => (replyRefs.current[currentReplyKey] = el)}
|
||||||
key={reply.id}
|
key={currentReplyKey}
|
||||||
className="scroll-mt-12"
|
className="scroll-mt-12"
|
||||||
>
|
>
|
||||||
<ReplyNote
|
<ReplyNote
|
||||||
event={reply}
|
event={reply}
|
||||||
parentEventId={event.id !== parentEventHexId ? parentEventId : undefined}
|
parentEventId={rootEventKey !== parentEventKey ? parentEventId : undefined}
|
||||||
onClickParent={() => {
|
onClickParent={() => {
|
||||||
if (!parentEventHexId) return
|
if (!parentEventKey) return
|
||||||
if (replies.every((r) => r.id !== parentEventHexId)) {
|
highlightReply(parentEventKey, parentEventId)
|
||||||
push(toNote(parentEventId ?? parentEventHexId))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
highlightReply(parentEventHexId)
|
|
||||||
}}
|
}}
|
||||||
highlight={highlightReplyId === reply.id}
|
highlight={highlightReplyKey === currentReplyKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,13 @@ export function isReplyNoteEvent(event: Event) {
|
||||||
const cache = EVENT_IS_REPLY_NOTE_CACHE.get(event.id)
|
const cache = EVENT_IS_REPLY_NOTE_CACHE.get(event.id)
|
||||||
if (cache !== undefined) return cache
|
if (cache !== undefined) return cache
|
||||||
|
|
||||||
const isReply = !!getParentETag(event) || !!getParentATag(event)
|
const isReply = !!getParentTag(event)
|
||||||
EVENT_IS_REPLY_NOTE_CACHE.set(event.id, isReply)
|
EVENT_IS_REPLY_NOTE_CACHE.set(event.id, isReply)
|
||||||
return isReply
|
return isReply
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isReplaceableEvent(kind: number) {
|
export function isReplaceableEvent(kind: number) {
|
||||||
|
if (isNaN(kind)) return false
|
||||||
return kinds.isReplaceableKind(kind) || kinds.isAddressableKind(kind)
|
return kinds.isReplaceableKind(kind) || kinds.isAddressableKind(kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,16 +99,32 @@ export function getParentEventHexId(event?: Event) {
|
||||||
return tag?.[1]
|
return tag?.[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getParentBech32Id(event?: Event) {
|
export function getParentTag(event?: Event): { type: 'e' | 'a'; tag: string[] } | undefined {
|
||||||
const eTag = getParentETag(event)
|
if (!event) return undefined
|
||||||
if (!eTag) {
|
|
||||||
const aTag = getParentATag(event)
|
|
||||||
if (!aTag) return undefined
|
|
||||||
|
|
||||||
return generateBech32IdFromATag(aTag)
|
if (event.kind === kinds.ShortTextNote) {
|
||||||
|
const tag = getParentETag(event)
|
||||||
|
return tag ? { type: 'e', tag } : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateBech32IdFromETag(eTag)
|
// NIP-22
|
||||||
|
const parentKindStr = event.tags.find(tagNameEquals('k'))?.[1]
|
||||||
|
if (parentKindStr && isReplaceableEvent(parseInt(parentKindStr))) {
|
||||||
|
const tag = getParentATag(event)
|
||||||
|
return tag ? { type: 'a', tag } : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = getParentETag(event)
|
||||||
|
return tag ? { type: 'e', tag } : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getParentBech32Id(event?: Event) {
|
||||||
|
const parentTag = getParentTag(event)
|
||||||
|
if (!parentTag) return undefined
|
||||||
|
|
||||||
|
return parentTag.type === 'e'
|
||||||
|
? generateBech32IdFromETag(parentTag.tag)
|
||||||
|
: generateBech32IdFromATag(parentTag.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRootETag(event?: Event) {
|
export function getRootETag(event?: Event) {
|
||||||
|
|
@ -147,16 +164,42 @@ export function getRootEventHexId(event?: Event) {
|
||||||
return tag?.[1]
|
return tag?.[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRootBech32Id(event?: Event) {
|
export function getRootTag(event?: Event): { type: 'e' | 'a'; tag: string[] } | undefined {
|
||||||
const eTag = getRootETag(event)
|
if (!event) return undefined
|
||||||
if (!eTag) {
|
|
||||||
const aTag = getRootATag(event)
|
|
||||||
if (!aTag) return undefined
|
|
||||||
|
|
||||||
return generateBech32IdFromATag(aTag)
|
if (event.kind === kinds.ShortTextNote) {
|
||||||
|
const tag = getRootETag(event)
|
||||||
|
return tag ? { type: 'e', tag } : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateBech32IdFromETag(eTag)
|
// NIP-22
|
||||||
|
const rootKindStr = event.tags.find(tagNameEquals('K'))?.[1]
|
||||||
|
if (rootKindStr && isReplaceableEvent(parseInt(rootKindStr))) {
|
||||||
|
const tag = getRootATag(event)
|
||||||
|
return tag ? { type: 'a', tag } : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = getRootETag(event)
|
||||||
|
return tag ? { type: 'e', tag } : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRootBech32Id(event?: Event) {
|
||||||
|
const rootTag = getRootTag(event)
|
||||||
|
if (!rootTag) return undefined
|
||||||
|
|
||||||
|
return rootTag.type === 'e'
|
||||||
|
? generateBech32IdFromETag(rootTag.tag)
|
||||||
|
: generateBech32IdFromATag(rootTag.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For internal identification of events
|
||||||
|
export function getEventKey(event: Event) {
|
||||||
|
return isReplaceableEvent(event.kind) ? getReplaceableCoordinateFromEvent(event) : event.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for e, E, a, A tags
|
||||||
|
export function getEventKeyFromTag([, tagValue]: (string | undefined)[]) {
|
||||||
|
return tagValue
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReplaceableCoordinate(kind: number, pubkey: string, d: string = '') {
|
export function getReplaceableCoordinate(kind: number, pubkey: string, d: string = '') {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,13 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||||
import { ExtendedKind } from '@/constants'
|
import { ExtendedKind } from '@/constants'
|
||||||
import { useFetchEvent } from '@/hooks'
|
import { useFetchEvent } from '@/hooks'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { getParentBech32Id, getParentETag, getRootBech32Id } from '@/lib/event'
|
import {
|
||||||
|
getEventKey,
|
||||||
|
getEventKeyFromTag,
|
||||||
|
getParentBech32Id,
|
||||||
|
getParentTag,
|
||||||
|
getRootBech32Id
|
||||||
|
} from '@/lib/event'
|
||||||
import { toNote, toNoteList } from '@/lib/link'
|
import { toNote, toNoteList } from '@/lib/link'
|
||||||
import { tagNameEquals } from '@/lib/tag'
|
import { tagNameEquals } from '@/lib/tag'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
@ -173,8 +179,10 @@ function ParentNote({
|
||||||
}
|
}
|
||||||
|
|
||||||
function isConsecutive(rootEvent?: Event, parentEvent?: Event) {
|
function isConsecutive(rootEvent?: Event, parentEvent?: Event) {
|
||||||
const eTag = getParentETag(parentEvent)
|
if (!rootEvent || !parentEvent) return false
|
||||||
if (!eTag) return false
|
|
||||||
|
|
||||||
return rootEvent?.id === eTag[1]
|
const tag = getParentTag(parentEvent)
|
||||||
|
if (!tag) return false
|
||||||
|
|
||||||
|
return getEventKey(rootEvent) === getEventKeyFromTag(tag.tag)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { getParentATag, getParentETag, getRootATag, getRootETag } from '@/lib/event'
|
import { getEventKey, getEventKeyFromTag, getParentTag } from '@/lib/event'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { createContext, useCallback, useContext, useState } from 'react'
|
import { createContext, useCallback, useContext, useState } from 'react'
|
||||||
|
|
||||||
type TReplyContext = {
|
type TReplyContext = {
|
||||||
repliesMap: Map<string, { events: Event[]; eventIdSet: Set<string> }>
|
repliesMap: Map<string, { events: Event[]; eventKeySet: Set<string> }>
|
||||||
addReplies: (replies: Event[]) => void
|
addReplies: (replies: Event[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,56 +19,38 @@ export const useReply = () => {
|
||||||
|
|
||||||
export function ReplyProvider({ children }: { children: React.ReactNode }) {
|
export function ReplyProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [repliesMap, setRepliesMap] = useState<
|
const [repliesMap, setRepliesMap] = useState<
|
||||||
Map<string, { events: Event[]; eventIdSet: Set<string> }>
|
Map<string, { events: Event[]; eventKeySet: Set<string> }>
|
||||||
>(new Map())
|
>(new Map())
|
||||||
|
|
||||||
const addReplies = useCallback((replies: Event[]) => {
|
const addReplies = useCallback((replies: Event[]) => {
|
||||||
const newReplyIdSet = new Set<string>()
|
const newReplyKeySet = new Set<string>()
|
||||||
const newReplyEventMap = new Map<string, Event[]>()
|
const newReplyEventMap = new Map<string, Event[]>()
|
||||||
replies.forEach((reply) => {
|
replies.forEach((reply) => {
|
||||||
if (newReplyIdSet.has(reply.id)) return
|
const key = getEventKey(reply)
|
||||||
newReplyIdSet.add(reply.id)
|
if (newReplyKeySet.has(key)) return
|
||||||
|
newReplyKeySet.add(key)
|
||||||
|
|
||||||
let rootId: string | undefined
|
const parentTag = getParentTag(reply)
|
||||||
const rootETag = getRootETag(reply)
|
if (parentTag) {
|
||||||
if (rootETag) {
|
const parentKey = getEventKeyFromTag(parentTag.tag)
|
||||||
rootId = rootETag[1]
|
if (parentKey) {
|
||||||
} else {
|
newReplyEventMap.set(parentKey, [...(newReplyEventMap.get(parentKey) || []), reply])
|
||||||
const rootATag = getRootATag(reply)
|
|
||||||
if (rootATag) {
|
|
||||||
rootId = rootATag[1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rootId) {
|
|
||||||
newReplyEventMap.set(rootId, [...(newReplyEventMap.get(rootId) || []), reply])
|
|
||||||
}
|
|
||||||
|
|
||||||
let parentId: string | undefined
|
|
||||||
const parentETag = getParentETag(reply)
|
|
||||||
if (parentETag) {
|
|
||||||
parentId = parentETag[1]
|
|
||||||
} else {
|
|
||||||
const parentATag = getParentATag(reply)
|
|
||||||
if (parentATag) {
|
|
||||||
parentId = parentATag[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parentId && parentId !== rootId) {
|
|
||||||
newReplyEventMap.set(parentId, [...(newReplyEventMap.get(parentId) || []), reply])
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
if (newReplyEventMap.size === 0) return
|
if (newReplyEventMap.size === 0) return
|
||||||
|
|
||||||
setRepliesMap((prev) => {
|
setRepliesMap((prev) => {
|
||||||
for (const [id, newReplyEvents] of newReplyEventMap.entries()) {
|
for (const [key, newReplyEvents] of newReplyEventMap.entries()) {
|
||||||
const replies = prev.get(id) || { events: [], eventIdSet: new Set() }
|
const replies = prev.get(key) || { events: [], eventKeySet: new Set() }
|
||||||
newReplyEvents.forEach((reply) => {
|
newReplyEvents.forEach((reply) => {
|
||||||
if (!replies.eventIdSet.has(reply.id)) {
|
const key = getEventKey(reply)
|
||||||
|
if (!replies.eventKeySet.has(key)) {
|
||||||
replies.events.push(reply)
|
replies.events.push(reply)
|
||||||
replies.eventIdSet.add(reply.id)
|
replies.eventKeySet.add(key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
prev.set(id, replies)
|
prev.set(key, replies)
|
||||||
}
|
}
|
||||||
return new Map(prev)
|
return new Map(prev)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue