import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card' import { Skeleton } from '@/components/ui/skeleton' import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' import { useNoteStatsById } from '@/hooks/useNoteStatsById' import { shouldHideInteractions } from '@/lib/event-filtering' import { createReactionDraftEvent } from '@/lib/draft-event' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import { useUserTrust } from '@/contexts/user-trust-context' import noteStatsService from '@/services/note-stats.service' import storage from '@/services/local-storage.service' import { TEmoji } from '@/types' import { Event } from 'nostr-tools' import { useMemo, useRef, useState } from 'react' import Emoji from '../Emoji' import Username from '../Username' import logger from '@/lib/logger' export default function Likes({ event }: { event: Event }) { const inQuietMode = shouldHideInteractions(event) const { pubkey, checkLogin, publish } = useNostr() const { hideUntrustedInteractions, isUserTrusted } = useUserTrust() const noteStats = useNoteStatsById(event.id) const [liking, setLiking] = useState(null) const longPressTimerRef = useRef(null) const [isLongPressing, setIsLongPressing] = useState(null) const [isCompleted, setIsCompleted] = useState(null) const likes = useMemo(() => { let _likes = noteStats?.likes if (!_likes) return [] if (hideUntrustedInteractions) { _likes = _likes.filter((item) => isUserTrusted(item.pubkey)) } const stats = new Map }>() _likes.forEach((item) => { // In quiet mode, normalize all emojis to "+" to prevent trolling with funny emojis if (inQuietMode) { const key = '+' if (!stats.has(key)) { stats.set(key, { key, pubkeys: new Set(), emoji: '+' }) } stats.get(key)?.pubkeys.add(item.pubkey) } else { const key = typeof item.emoji === 'string' ? item.emoji : item.emoji.url if (!stats.has(key)) { stats.set(key, { key, pubkeys: new Set(), emoji: item.emoji }) } stats.get(key)?.pubkeys.add(item.pubkey) } }) return Array.from(stats.values()) .filter((g) => g.pubkeys.size > 0) .sort((a, b) => b.pubkeys.size - a.pubkeys.size) }, [noteStats, event, inQuietMode, hideUntrustedInteractions, isUserTrusted]) if (!likes.length) return null const like = async (key: string, emoji: TEmoji | string) => { checkLogin(async () => { if (liking || !pubkey) return setLiking(key) const timer = setTimeout(() => setLiking((prev) => (prev === key ? null : prev)), 5000) try { const reaction = createReactionDraftEvent(event, emoji) const evt = await publish(reaction, { addClientTag: storage.getAddClientTag() }) noteStatsService.updateNoteStatsByEvents([evt], undefined, { interactionTargetNoteId: event.id }) } catch (error) { logger.error('Like failed', { error, eventId: event.id }) } finally { setLiking(null) clearTimeout(timer) } }) } const handleMouseDown = (key: string) => { if (pubkey && likes.find((l) => l.key === key)?.pubkeys.has(pubkey)) { return } setIsLongPressing(key) longPressTimerRef.current = setTimeout(() => { setIsCompleted(key) setIsLongPressing(null) }, 800) } const handleMouseUp = () => { if (longPressTimerRef.current) { clearTimeout(longPressTimerRef.current) longPressTimerRef.current = null } if (isCompleted) { const completedKey = isCompleted const row = likes.find((l) => l.key === completedKey) if (row) { like(completedKey, row.emoji) } } setIsLongPressing(null) setIsCompleted(null) } const handleMouseLeave = () => { if (longPressTimerRef.current) { clearTimeout(longPressTimerRef.current) longPressTimerRef.current = null } setIsLongPressing(null) setIsCompleted(null) } const handleTouchMove = (e: React.TouchEvent) => { const touch = e.touches[0] const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() const isInside = touch.clientX >= rect.left && touch.clientX <= rect.right && touch.clientY >= rect.top && touch.clientY <= rect.bottom if (!isInside) { handleMouseLeave() } } return (
{likes.map(({ key, emoji, pubkeys }) => { const contributorIds = Array.from(pubkeys).sort() return (
e.stopPropagation()} onMouseDown={() => handleMouseDown(key)} onMouseUp={handleMouseUp} onMouseLeave={handleMouseLeave} onTouchStart={() => handleMouseDown(key)} onTouchMove={handleTouchMove} onTouchEnd={handleMouseUp} onTouchCancel={handleMouseLeave} > {(isLongPressing === key || isCompleted === key) && (
)}
{liking === key ? ( ) : (
)}
{pubkeys.size}
e.stopPropagation()} >
{contributorIds.map((userId) => ( ))}
) })}
) }