ReplyButton.tsx raw
1 import { useStuff } from '@/hooks/useStuff'
2 import { useAllDescendantThreads } from '@/hooks/useThread'
3 import { getEventKey, isMentioningMutedUsers } from '@/lib/event'
4 import { cn } from '@/lib/utils'
5 import { useContentPolicy } from '@/providers/ContentPolicyProvider'
6 import { useMuteList } from '@/providers/MuteListProvider'
7 import { useNostr } from '@/providers/NostrProvider'
8 import { useUserTrust } from '@/providers/UserTrustProvider'
9 import { MessageCircle } from 'lucide-react'
10 import { Event } from 'nostr-tools'
11 import { useMemo, useState } from 'react'
12 import { useTranslation } from 'react-i18next'
13 import PostEditor from '../PostEditor'
14 import KeyboardShortcut from './KeyboardShortcut'
15 import { formatCount } from './utils'
16
17 export default function ReplyButton({
18 stuff,
19 onReplyClick
20 }: {
21 stuff: Event | string
22 onReplyClick?: () => void
23 }) {
24 const { t } = useTranslation()
25 const { pubkey, checkLogin } = useNostr()
26 const { event, stuffKey } = useStuff(stuff)
27 const allThreads = useAllDescendantThreads(stuffKey)
28 const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
29 const { mutePubkeySet } = useMuteList()
30 const { hideContentMentioningMutedUsers } = useContentPolicy()
31 const { replyCount, hasReplied } = useMemo(() => {
32 const hasReplied = pubkey
33 ? allThreads.get(stuffKey)?.some((evt) => evt.pubkey === pubkey)
34 : false
35
36 let replyCount = 0
37 const replies = [...(allThreads.get(stuffKey) ?? [])]
38 while (replies.length > 0) {
39 const reply = replies.pop()
40 if (!reply) break
41
42 const replyKey = getEventKey(reply)
43 const nestedReplies = allThreads.get(replyKey) ?? []
44 replies.push(...nestedReplies)
45
46 if (hideUntrustedInteractions && !isUserTrusted(reply.pubkey)) {
47 continue
48 }
49 if (mutePubkeySet.has(reply.pubkey)) {
50 continue
51 }
52 if (hideContentMentioningMutedUsers && isMentioningMutedUsers(reply, mutePubkeySet)) {
53 continue
54 }
55 replyCount++
56 }
57
58 return { replyCount, hasReplied }
59 }, [allThreads, event, stuffKey, hideUntrustedInteractions])
60 const [open, setOpen] = useState(false)
61
62 return (
63 <>
64 <button
65 className={cn(
66 'flex gap-1 items-center enabled:hover:text-blue-400 pr-3 h-full group',
67 hasReplied ? 'text-blue-400' : 'text-muted-foreground'
68 )}
69 onClick={(e) => {
70 e.stopPropagation()
71 checkLogin(() => {
72 if (onReplyClick) {
73 onReplyClick()
74 } else {
75 setOpen(true)
76 }
77 })
78 }}
79 title={t('Reply (r)')}
80 data-action="reply"
81 >
82 <span className="relative">
83 <MessageCircle />
84 <KeyboardShortcut shortcut="r" />
85 </span>
86 {!!replyCount && <div className="text-sm">{formatCount(replyCount)}</div>}
87 </button>
88 {!onReplyClick && <PostEditor parentStuff={stuff} open={open} setOpen={setOpen} />}
89 </>
90 )
91 }
92