import { formatDate } from 'date-fns' import { useAppSelector, useBodyScrollDisable, useDidMount, useNDKContext, useReplies } from 'hooks' import { nip19 } from 'nostr-tools' import { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Link, useLoaderData, useLocation, useNavigate, useNavigation, useParams, useSubmit } from 'react-router-dom' import { appRoutes, getBlogPageRoute, getModPageRoute, getProfilePageRoute } from 'routes' import { CommentEvent, UserProfile } from 'types' import { CommentsLoaderResult } from 'types/comments' import { adjustTextareaHeight, handleCommentSubmit, hexToNpub } from 'utils' import { Reactions } from './Reactions' import { Zap } from './Zap' import { Comment } from './Comment' import { useComments } from 'hooks/useComments' import { CommentContent } from './CommentContent' import { Dots } from 'components/Spinner' import { NDKEvent, NDKFilter, NDKKind, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk' import { NoteQuoteRepostPopup } from 'components/Notes/NoteQuoteRepostPopup' import { NoteRepostPopup } from 'components/Notes/NoteRepostPopup' import _ from 'lodash' interface CommentsPopupProps { title: string } export const CommentsPopup = ({ title }: CommentsPopupProps) => { const { naddr } = useParams() const location = useLocation() const { ndk } = useNDKContext() useBodyScrollDisable(true) const isMod = location.pathname.includes('/mod/') const isBlog = location.pathname.includes('/blog/') const isNote = location.pathname.includes('/feed') const baseUrl = naddr ? isMod ? getModPageRoute(naddr) : isBlog ? getBlogPageRoute(naddr) : undefined : isNote ? `${appRoutes.feed}/` : undefined const { event } = useLoaderData() as CommentsLoaderResult const eTags = event.getMatchingTags('e') const lastETag = _.last(eTags) const { size, parent: replyEvent, isComplete, root: rootEvent } = useReplies(lastETag?.[1]) const isRoot = event.kind === NDKKind.Text ? !event.getMatchingTags('e').length : event.tagValue('a') === event.tagValue('A') const [profile, setProfile] = useState() const { commentEvents, setCommentEvents } = useComments( event.author.pubkey, undefined, event.id ) useEffect(() => { event.author.fetchProfile().then((res) => setProfile(res)) }, [event.author]) const profileRoute = useMemo( () => getProfilePageRoute( nip19.nprofileEncode({ pubkey: event.pubkey }) ), [event.pubkey] ) const navigate = useNavigate() const ref = useRef(null) const [isSubmitting, setIsSubmitting] = useState(false) const [replyText, setReplyText] = useState('') const handleChange = useCallback((e: ChangeEvent) => { const value = e.currentTarget.value setReplyText(value) }, []) useEffect(() => { if (ref.current) adjustTextareaHeight(ref.current) }, [replyText]) const [visible, setVisible] = useState([]) const discoveredCount = commentEvents.length - visible.length const [isLoading, setIsLoading] = useState(true) useEffect(() => { // Initial loading to indicate comments fetching (stop after 5 seconds) const t = window.setTimeout(() => setIsLoading(false), 5000) return () => { window.clearTimeout(t) } }, []) useEffect(() => { if (isLoading) { setVisible(commentEvents) } }, [commentEvents, isLoading]) const handleDiscoveredClick = () => { setVisible(commentEvents) } const handleSubmit = handleCommentSubmit( event, setCommentEvents, setVisible, ndk ) const handleComment = async () => { setIsSubmitting(true) const submitted = await handleSubmit(replyText) if (submitted) setReplyText('') setIsSubmitting(false) } const submit = useSubmit() const navigation = useNavigation() const [repostEvents, setRepostEvents] = useState([]) const [quoteRepostEvents, setQuoteRepostEvents] = useState([]) const [hasReposted, setHasReposted] = useState(false) const [hasQuoted, setHasQuoted] = useState(false) const [showRepostPopup, setShowRepostPopup] = useState(false) const [showQuoteRepostPopup, setShowQuoteRepostPopup] = useState(false) const userState = useAppSelector((state) => state.user) const userPubkey = userState.user?.pubkey as string | undefined useDidMount(() => { const repostFilter: NDKFilter = { kinds: [NDKKind.Repost], '#e': [event.id] } const quoteFilter: NDKFilter = { kinds: [NDKKind.Text], '#q': [event.id] } ndk .fetchEvents([repostFilter, quoteFilter], { closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL }) .then((ndkEventSet) => { const ndkEvents = Array.from(ndkEventSet) if (ndkEventSet.size) { const quoteRepostEvents = ndkEvents.filter( (n) => n.kind === NDKKind.Text ) userPubkey && setHasQuoted( quoteRepostEvents.some((qr) => qr.pubkey === userPubkey) ) setQuoteRepostEvents(quoteRepostEvents) const repostEvents = ndkEvents.filter( (n) => n.kind === NDKKind.Repost ) userPubkey && setHasReposted(repostEvents.some((qr) => qr.pubkey === userPubkey)) setRepostEvents(repostEvents) } }) .finally(() => { setIsLoading(false) }) }) const [showQuoteReposts, setShowQuoteReposts] = useState(false) const handleRepost = async (confirm: boolean) => { if (navigation.state !== 'idle') return setShowRepostPopup(false) // Cancel if not confirmed if (!confirm) return const repostNdkEvent = await event.repost(false) const rawEvent = repostNdkEvent.rawEvent() submit( JSON.stringify({ intent: 'repost', data: rawEvent }), { method: 'post', encType: 'application/json', action: appRoutes.feed } ) } return (

{title}

navigate('..')} >
{replyEvent && ( )}

Reply Depth: {size} {!isComplete && }

{!isRoot && rootEvent && ( Main Post {!isComplete && } )}
{profile?.displayName || profile?.name || ''}{' '} {hexToNpub(event.pubkey)}
{event.created_at && ( )}
{event.kind === NDKKind.Text && ( <> {/* Quote Repost, Kind 1 */}
setShowQuoteRepostPopup(true) } >

{isLoading ? : quoteRepostEvents.length}

{showQuoteRepostPopup && ( setShowQuoteRepostPopup(false)} /> )} {/* Repost, Kind 6 */}
setShowRepostPopup(true) } >

{isLoading ? : repostEvents.length}

{showRepostPopup && ( setShowRepostPopup(false)} /> )} )} {typeof profile?.lud16 !== 'undefined' && profile.lud16 !== '' && }

{commentEvents.length}

Replies

{/* Quote-Repost */}
{commentEvents.length + quoteRepostEvents.length > 0 && ( <>

{isNote ? (
setShowQuoteReposts(false)} > Replies
setShowQuoteReposts(true)} > Quote-Reposts
) : ( <>Replies )}

{(showQuoteReposts ? quoteRepostEvents.length : commentEvents.length) === 0 && !isLoading ? (

There are no{' '} {showQuoteReposts ? 'quote-reposts' : 'replies'} to show

) : (
{showQuoteReposts ? quoteRepostEvents.map((reply) => ( )) : commentEvents // Filter out events with 'q' tag since we are showing them with a dropdown .filter((r) => r.event.tagValue('q') !== event.id) .map((reply) => ( ))}
)} )}
) }