import "./Note.css"; import { useCallback, useMemo, useState, useLayoutEffect, ReactNode, } from "react"; import { useNavigate, Link } from "react-router-dom"; import { useInView } from "react-intersection-observer"; import { default as NEvent } from "Nostr/Event"; import ProfileImage from "Element/ProfileImage"; import Text from "Element/Text"; import { eventLink, getReactions, hexToBech32 } from "Util"; import NoteFooter, { Translation } from "Element/NoteFooter"; import NoteTime from "Element/NoteTime"; import ShowMore from "Element/ShowMore"; import EventKind from "Nostr/EventKind"; import { useUserProfiles } from "Feed/ProfileFeed"; import { TaggedRawEvent, u256 } from "Nostr"; import useModeration from "Hooks/useModeration"; export interface NoteProps { data?: TaggedRawEvent; className?: string; related: TaggedRawEvent[]; highlight?: boolean; ignoreModeration?: boolean; options?: { showHeader?: boolean; showTime?: boolean; showFooter?: boolean; }; ["data-ev"]?: NEvent; } const HiddenNote = ({ children }: any) => { const [show, setShow] = useState(false); return show ? ( children ) : (

This author has been muted

); }; export default function Note(props: NoteProps) { const navigate = useNavigate(); const { data, className, related, highlight, options: opt, ["data-ev"]: parsedEvent, ignoreModeration = false, } = props; const ev = useMemo(() => parsedEvent ?? new NEvent(data), [data]); const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]); const users = useUserProfiles(pubKeys); const deletions = useMemo( () => getReactions(related, ev.Id, EventKind.Deletion), [related] ); const { isMuted } = useModeration(); const isOpMuted = isMuted(ev.PubKey); const { ref, inView, entry } = useInView({ triggerOnce: true }); const [extendable, setExtendable] = useState(false); const [showMore, setShowMore] = useState(false); const baseClassname = `note card ${props.className ? props.className : ""}`; const [translated, setTranslated] = useState(); const replyId = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event; const options = { showHeader: true, showTime: true, showFooter: true, ...opt, }; const transformBody = useCallback(() => { let body = ev?.Content ?? ""; if (deletions?.length > 0) { return Deleted; } return ( ); }, [ev]); useLayoutEffect(() => { if (entry && inView && extendable === false) { let h = entry?.target.clientHeight ?? 0; if (h > 650) { setExtendable(true); } } }, [inView, entry, extendable]); function goToEvent(e: any, id: u256) { e.stopPropagation(); navigate(eventLink(id)); } function replyTag() { if (ev.Thread === null) { return null; } const maxMentions = 2; let replyId = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event; let mentions: { pk: string; name: string; link: ReactNode }[] = []; for (let pk of ev.Thread?.PubKeys) { const u = users?.get(pk); const npub = hexToBech32("npub", pk); const shortNpub = npub.substring(0, 12); if (u) { mentions.push({ pk, name: u.name ?? shortNpub, link: ( {u.name ? `@${u.name}` : shortNpub} ), }); } else { mentions.push({ pk, name: shortNpub, link: {shortNpub}, }); } } mentions.sort((a, b) => (a.name.startsWith("npub") ? 1 : -1)); let othersLength = mentions.length - maxMentions; const renderMention = (m: any, idx: number) => { return ( <> {idx > 0 && ", "} {m.link} ); }; const pubMentions = mentions.length > maxMentions ? mentions?.slice(0, maxMentions).map(renderMention) : mentions?.map(renderMention); const others = mentions.length > maxMentions ? ` & ${othersLength} other${othersLength > 1 ? "s" : ""}` : ""; return (
re:  {(mentions?.length ?? 0) > 0 ? ( <> {pubMentions} {others} ) : ( replyId && ( {hexToBech32("note", replyId)?.substring(0, 12)} ) )}
); } if (ev.Kind !== EventKind.TextNote) { return ( <>

Unknown event kind: {ev.Kind}

{JSON.stringify(ev.ToObject(), undefined, "  ")}
); } function translation() { if (translated && translated.confidence > 0.5) { return ( <>

Translated from {translated.fromLanguage}:

{translated.text} ); } else if (translated) { return

Translation failed

; } } function content() { if (!inView) return null; return ( <> {options.showHeader ? (
{options.showTime ? (
) : null}
) : null}
goToEvent(e, ev.Id)}> {transformBody()} {translation()}
{extendable && !showMore && ( setShowMore(true)} > Show more )} {options.showFooter && ( setTranslated(t)} /> )} ); } const note = (
{content()}
); return !ignoreModeration && isOpMuted ? ( {note} ) : ( note ); }