From f4878f9d4f141a1453808244865bae9a33527f31 Mon Sep 17 00:00:00 2001
From: Jure <44338+hoornet@users.noreply.github.com>
Date: Fri, 10 Apr 2026 17:49:11 +0200
Subject: [PATCH] Polish DM view: clickable links, inline images, nostr entity
rendering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- DM messages now parse and render URLs as clickable accent links
- Image URLs show inline in the bubble (max-h-48)
- nostr:naddr and mentions rendered as styled clickable links
- nostr:nevent quotes show a subtle indicator
- Media URLs (video/audio/youtube etc.) rendered as clickable links
- parseContent regex now case-insensitive for nostr: prefix; entity
lowercased before nip19.decode for robustness
- renderTextSegments: add case for quote type (↩ note indicator)
---
src/components/dm/DMView.tsx | 76 +++++++++++++++++++++++++++-
src/components/feed/TextSegments.tsx | 13 +++++
src/lib/parsing.ts | 6 +--
3 files changed, 90 insertions(+), 5 deletions(-)
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
+