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