diff --git a/src/components/dm/DMView.tsx b/src/components/dm/DMView.tsx index 2a6a917..afc131a 100644 --- a/src/components/dm/DMView.tsx +++ b/src/components/dm/DMView.tsx @@ -1,5 +1,6 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { NDKEvent, NDKKind, nip19 } from "@nostr-dev-kit/ndk"; +import { openUrl } from "@tauri-apps/plugin-opener"; import { useUserStore } from "../../stores/user"; import { useUIStore } from "../../stores/ui"; import { useNotificationsStore } from "../../stores/notifications"; @@ -7,6 +8,8 @@ import { fetchDMConversations, fetchDMThread, sendDM, decryptDM, getNDK } from " import { useProfile } from "../../hooks/useProfile"; import { timeAgo, shortenPubkey, profileName } from "../../lib/utils"; import { debug } from "../../lib/debug"; +import { parseContent } from "../../lib/parsing"; +import { tryHandleUrlInternally, tryOpenNostrEntity } from "../feed/TextSegments"; // ── Helpers ────────────────────────────────────────────────────────────────── @@ -77,6 +80,75 @@ function ConvRow({ ); } +// ── DM text renderer ───────────────────────────────────────────────────────── + +function DMText({ text }: { text: string }) { + const segments = useMemo(() => parseContent(text), [text]); + return ( + + {segments.map((seg, i) => { + if (seg.type === "link") { + return ( + { + e.preventDefault(); + if (!tryHandleUrlInternally(seg.value)) openUrl(seg.value).catch(() => {}); + }} + > + {seg.display || seg.value} + + ); + } + if (seg.type === "image") { + return ( + { (e.target as HTMLImageElement).style.display = "none"; }} + /> + ); + } + if (seg.type === "naddr" || seg.type === "mention") { + return ( + tryOpenNostrEntity(seg.value)} + > + {seg.type === "naddr" ? "🔗 " : "@"}{String(seg.display ?? seg.value).slice(0, 20)}… + + ); + } + if (seg.type === "quote") { + return ( + + ↩ quoted note + + ); + } + // video/audio/youtube/etc. — show URL as a plain link + if (["video", "audio", "youtube", "vimeo", "spotify", "tidal", "fountain"].includes(seg.type)) { + return ( + { e.preventDefault(); openUrl(seg.value).catch(() => {}); }} + > + {seg.value} + + ); + } + return {seg.value}; + })} + + ); +} + // ── Message bubble ──────────────────────────────────────────────────────────── function MessageBubble({ event, myPubkey }: { event: NDKEvent; myPubkey: string }) { @@ -106,7 +178,7 @@ function MessageBubble({ event, myPubkey }: { event: NDKEvent; myPubkey: string ) : text === null ? ( ) : ( - text + )}
{time} diff --git a/src/components/feed/TextSegments.tsx b/src/components/feed/TextSegments.tsx index f02190d..b851ba0 100644 --- a/src/components/feed/TextSegments.tsx +++ b/src/components/feed/TextSegments.tsx @@ -165,6 +165,19 @@ export function renderTextSegments( ); break; + case "quote": + // Inline text placeholder — the QuotePreview card renders separately below + elements.push( + { e.stopPropagation(); tryOpenNostrEntity(`note1${seg.value.slice(0, 8)}`); }} + title="Quoted note" + > + ↩ note + + ); + break; case "hashtag": elements.push(