import { SnortContext, useEventReactions, useReactions, useUserProfile } from "@snort/system-react"; import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system"; import React, { Suspense, lazy, useContext, useMemo, useRef, useState } from "react"; import { useHover, useOnClickOutside } from "usehooks-ts"; import { dedupe } from "@snort/shared"; import dayjs from "dayjs"; const EmojiPicker = lazy(() => import("../emoji-picker")); import { Icon } from "../icon"; import { Emoji as EmojiComponent } from "../emoji"; import { Profile } from "../profile"; import { Text } from "../text"; import { useMute } from "../mute-button"; import { SendZaps } from "../send-zap"; import { CollapsibleEvent } from "../collapsible"; import { useLogin } from "@/hooks/login"; import { formatSats } from "@/number"; import type { Badge, Emoji, EmojiPack } from "@/types"; import { IconButton } from "../buttons"; import Pill from "../pill"; import classNames from "classnames"; import Modal from "../modal"; function emojifyReaction(reaction: string) { if (reaction === "+") { return "💜"; } if (reaction === "-") { return "👎"; } return reaction; } export function ChatMessage({ streamer, ev, emojiPacks, badges, }: { ev: TaggedNostrEvent; streamer: string; emojiPacks: EmojiPack[]; badges: Badge[]; }) { const system = useContext(SnortContext); const ref = useRef(null); const emojiRef = useRef(null); const link = NostrLink.fromEvent(ev); const isHovering = useHover(ref); const { mute } = useMute(ev.pubkey); const [showZapDialog, setShowZapDialog] = useState(false); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [zapping, setZapping] = useState(false); const login = useLogin(); const profile = useUserProfile(ev.pubkey); const shouldShowMuteButton = ev.pubkey !== streamer && ev.pubkey !== login?.pubkey; const zapTarget = profile?.lud16 ?? profile?.lud06; const related = useReactions("reactions", [link], undefined, false); const { zaps, reactions } = useEventReactions(link, related); const emojiNames = emojiPacks.map(p => p.emojis).flat(); const filteredReactions = useMemo(() => { return reactions.all.filter(a => link.isReplyToThis(a)); }, [ev, reactions.all]); const hasReactions = filteredReactions.length > 0; const totalZaps = useMemo(() => { return zaps.filter(a => a.event?.id === ev.id).reduce((acc, z) => acc + z.amount, 0); }, [zaps, ev]); const hasZaps = totalZaps > 0; const awardedBadges = badges.filter(b => b.awardees.has(ev.pubkey) && b.accepted.has(ev.pubkey)); useOnClickOutside(ref, () => { setShowZapDialog(false); }); useOnClickOutside(emojiRef, () => { setShowEmojiPicker(false); }); function getEmojiById(id: string) { return emojiNames.find(e => e.at(1) === id); } async function onEmojiSelect(emoji: Emoji) { setShowEmojiPicker(false); setShowZapDialog(false); let reply = null; try { const pub = login?.publisher(); if (emoji.native) { reply = await pub?.react(ev, emoji.native || "+1"); } else if (emoji.id) { const e = getEmojiById(emoji.id); if (e) { reply = await pub?.generic(eb => { return eb .kind(EventKind.Reaction) .content(`:${emoji.id}:`) .tag(["e", ev.id]) .tag(["p", ev.pubkey]) .tag(["emoji", e[1], e[2]]); }); } } if (reply) { console.debug(reply); system.BroadcastEvent(reply); } } catch { //ignore } } const topOffset = ref.current?.getBoundingClientRect().top; const leftOffset = ref.current?.getBoundingClientRect().left; function pickEmoji(e: React.MouseEvent) { e.stopPropagation(); setShowEmojiPicker(!showEmojiPicker); } function muteUser(e: React.MouseEvent) { e.stopPropagation(); mute(); } return ( <>
) : ( awardedBadges.map(badge => { return {badge.name}; }) ) } pubkey={ev.pubkey} />{" "} {(hasReactions || hasZaps) && (
{hasZaps && ( {formatSats(totalZaps)} )} {dedupe(filteredReactions.map(v => emojifyReaction(v.content))).map(e => { const isCustomEmojiReaction = e.length > 1 && e.startsWith(":") && e.endsWith(":"); const emojiName = e.replace(/:/g, ""); const emoji = isCustomEmojiReaction && getEmojiById(emojiName); return (
{isCustomEmojiReaction && emoji ? : e}
); })}
)} {ref.current && isHovering && (
{zapTarget && ( setZapping(true)} /> )} {shouldShowMuteButton && ( )}
)} {zapping && zapTarget && ( setZapping(false)}> setZapping(false)} /> )}
{showEmojiPicker && ( setShowEmojiPicker(false)} ref={emojiRef} /> )} ); }