feat: display message reactions
This commit is contained in:
@ -222,7 +222,6 @@
|
|||||||
.zap-pill {
|
.zap-pill {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 4px;
|
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
@ -237,6 +236,18 @@
|
|||||||
color: #FF8D2B;
|
color: #FF8D2B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-reactions {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-reaction {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.zap-pill-amount {
|
.zap-pill-amount {
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -33,6 +33,7 @@ import { useUserProfile } from "@snort/system-react";
|
|||||||
import { formatSats } from "number";
|
import { formatSats } from "number";
|
||||||
import useTopZappers from "hooks/top-zappers";
|
import useTopZappers from "hooks/top-zappers";
|
||||||
import { LIVE_STREAM_CHAT } from "const";
|
import { LIVE_STREAM_CHAT } from "const";
|
||||||
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export interface LiveChatOptions {
|
export interface LiveChatOptions {
|
||||||
canWrite?: boolean;
|
canWrite?: boolean;
|
||||||
@ -79,10 +80,6 @@ export function LiveChat({
|
|||||||
.filter((ev) => ev.kind === EventKind.ZapReceipt)
|
.filter((ev) => ev.kind === EventKind.ZapReceipt)
|
||||||
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
|
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
|
||||||
.filter((z) => z && z.valid);
|
.filter((z) => z && z.valid);
|
||||||
const reactions = feed.reactions
|
|
||||||
.filter((ev) => ev.kind === EventKind.ZapReceipt)
|
|
||||||
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
|
|
||||||
.filter((z) => z && z.valid);
|
|
||||||
const events = useMemo(() => {
|
const events = useMemo(() => {
|
||||||
return [...feed.messages, ...feed.zaps].sort(
|
return [...feed.messages, ...feed.zaps].sort(
|
||||||
(a, b) => b.created_at - a.created_at
|
(a, b) => b.created_at - a.created_at
|
||||||
@ -109,7 +106,7 @@ export function LiveChat({
|
|||||||
ev={a}
|
ev={a}
|
||||||
link={link}
|
link={link}
|
||||||
key={a.id}
|
key={a.id}
|
||||||
reactions={reactions}
|
reactions={feed.reactions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -134,6 +131,16 @@ export function LiveChat({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emojifyReaction(reaction: string) {
|
||||||
|
if (reaction === "+") {
|
||||||
|
return "💜";
|
||||||
|
}
|
||||||
|
if (reaction === "-") {
|
||||||
|
return "👎";
|
||||||
|
}
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
function ChatMessage({
|
function ChatMessage({
|
||||||
streamer,
|
streamer,
|
||||||
ev,
|
ev,
|
||||||
@ -143,16 +150,28 @@ function ChatMessage({
|
|||||||
streamer: string;
|
streamer: string;
|
||||||
ev: TaggedRawEvent;
|
ev: TaggedRawEvent;
|
||||||
link: NostrLink;
|
link: NostrLink;
|
||||||
reactions: ParsedZap[];
|
reactions: readonly TaggedRawEvent[];
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const isHovering = useHover(ref);
|
const isHovering = useHover(ref);
|
||||||
const profile = useUserProfile(System, ev.pubkey);
|
const profile = useUserProfile(System, ev.pubkey);
|
||||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||||
|
const zaps = reactions
|
||||||
|
.filter((ev) => ev.kind === EventKind.ZapReceipt)
|
||||||
|
.map((ev) => parseZap(ev, System.ProfileLoader.Cache))
|
||||||
|
.filter((z) => z && z.valid);
|
||||||
|
const emojis = useMemo(() => {
|
||||||
|
const emojified = reactions
|
||||||
|
.filter((e) => e.kind === EventKind.Reaction && findTag(e, "e") === ev.id)
|
||||||
|
.map((ev) => emojifyReaction(ev.content));
|
||||||
|
return [...new Set(emojified)];
|
||||||
|
}, [ev, reactions]);
|
||||||
|
const hasReactions = emojis.length > 0;
|
||||||
const totalZaps = useMemo(() => {
|
const totalZaps = useMemo(() => {
|
||||||
const messageZaps = reactions.filter((z) => z.event === ev.id);
|
const messageZaps = zaps.filter((z) => z.event === ev.id);
|
||||||
return messageZaps.reduce((acc, z) => acc + z.amount, 0);
|
return messageZaps.reduce((acc, z) => acc + z.amount, 0);
|
||||||
}, [reactions, ev]);
|
}, [reactions, ev]);
|
||||||
|
const hasZaps = totalZaps > 0;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -188,12 +207,19 @@ function ChatMessage({
|
|||||||
)}
|
)}
|
||||||
<Profile pubkey={ev.pubkey} />
|
<Profile pubkey={ev.pubkey} />
|
||||||
<Text content={ev.content} tags={ev.tags} />
|
<Text content={ev.content} tags={ev.tags} />
|
||||||
{totalZaps !== 0 && (
|
{(hasReactions || hasZaps) && (
|
||||||
|
<div className="message-reactions">
|
||||||
|
{hasZaps && (
|
||||||
<div className="zap-pill">
|
<div className="zap-pill">
|
||||||
<Icon name="zap-filled" className="zap-pill-icon" />
|
<Icon name="zap-filled" className="zap-pill-icon" />
|
||||||
<span className="zap-pill-amount">{formatSats(totalZaps)}</span>
|
<span className="zap-pill-amount">{formatSats(totalZaps)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{emojis.map((e) => (
|
||||||
|
<span className="message-reaction">{e}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user