import { commands } from "@/commands.gen"; import { appSettings, cn, replyTime } from "@/commons"; import { Note } from "@/components/note"; import { type LumeEvent, LumeWindow } from "@/system"; import { CaretDown } from "@phosphor-icons/react"; import { Link, useSearch } from "@tanstack/react-router"; import { useStore } from "@tanstack/react-store"; import { Menu, MenuItem } from "@tauri-apps/api/menu"; import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { nip19 } from "nostr-tools"; import { type ReactNode, memo, useCallback, useEffect, useMemo, useState, } from "react"; import reactStringReplace from "react-string-replace"; import { Hashtag } from "./note/mentions/hashtag"; import { MentionUser } from "./note/mentions/user"; import { User } from "./user"; export const ReplyNote = memo(function ReplyNote({ event, className, }: { event: LumeEvent; className?: string; }) { const trustedOnly = useStore(appSettings, (state) => state.trusted_only); const search = useSearch({ strict: false }); const [isTrusted, setIsTrusted] = useState(null); const showContextMenu = useCallback(async (e: React.MouseEvent) => { e.preventDefault(); const menuItems = await Promise.all([ MenuItem.new({ text: "View Profile", action: () => LumeWindow.openProfile(event.pubkey), }), MenuItem.new({ text: "Copy Public Key", action: async () => { const pubkey = await event.pubkeyAsBech32(); await writeText(pubkey); }, }), ]); const menu = await Menu.new({ items: menuItems, }); await menu.popup().catch((e) => console.error(e)); }, []); useEffect(() => { async function check() { const res = await commands.isTrustedUser(event.pubkey); if (res.status === "ok") { setIsTrusted(res.data); } } if (trustedOnly) { check(); } }, []); if (isTrusted !== null && isTrusted === false) { return null; } return (
{replyTime(event.created_at)}
{event.replies?.length ? (
{event.replies.slice(0, 2).map((reply) => ( ))} {event.replies.length > 2 ? (
All {event.replies.length} replies
) : null}
) : null}
); }); function ChildReply({ event }: { event: LumeEvent }) { const search = useSearch({ strict: false }); const showContextMenu = useCallback(async (e: React.MouseEvent) => { e.preventDefault(); const menuItems = await Promise.all([ MenuItem.new({ text: "View Profile", action: () => LumeWindow.openProfile(event.pubkey), }), MenuItem.new({ text: "Copy Public Key", action: async () => { const pubkey = await event.pubkeyAsBech32(); await writeText(pubkey); }, }), ]); const menu = await Menu.new({ items: menuItems, }); await menu.popup().catch((e) => console.error(e)); }, []); return (
{replyTime(event.created_at)}
{event.replies?.length ? (
{event.replies.slice(0, 2).map((reply) => ( ))} {event.replies.length > 2 ? (
All {event.replies.length} replies
) : null}
) : null}
); } function Content({ text, className }: { text: string; className?: string }) { const content = useMemo(() => { let replacedText: ReactNode[] | string = text.trim(); const nostr = replacedText .split(/\s+/) .filter((w) => w.startsWith("nostr:")); replacedText = reactStringReplace(text, /(https?:\/\/\S+)/g, (match, i) => ( {match} )); replacedText = reactStringReplace(replacedText, /#(\w+)/g, (match, i) => ( )); for (const word of nostr) { const bech32 = word.replace("nostr:", ""); const data = nip19.decode(bech32); switch (data.type) { case "npub": replacedText = reactStringReplace(replacedText, word, (match, i) => ( )); break; case "nprofile": replacedText = reactStringReplace(replacedText, word, (match, i) => ( )); break; default: replacedText = reactStringReplace(replacedText, word, (match, i) => ( {match} )); break; } } return replacedText; }, [text]); return
{content}
; }