From aadd30bbb71407917e58efb50d42aaddfe8a388e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 10:23:09 +0000 Subject: [PATCH] feat: improve ChatMediaRenderer UX - Full dotted border with rounded corners (was bottom-only) - Increased gap between elements - Images/videos open in MediaDialog zoom viewer on click - Audio still opens in new tab - Better extension extraction for display https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD --- src/components/chat/ChatMediaRenderer.tsx | 80 ++++++++++++++++------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/src/components/chat/ChatMediaRenderer.tsx b/src/components/chat/ChatMediaRenderer.tsx index e9284ed..e6233fd 100644 --- a/src/components/chat/ChatMediaRenderer.tsx +++ b/src/components/chat/ChatMediaRenderer.tsx @@ -5,12 +5,31 @@ * [icon] filename [size] [blossom-link] */ +import { useState } from "react"; import { Image, Video, Music, File, Flower2 } from "lucide-react"; import { getHashFromURL } from "blossom-client-sdk/helpers/url"; import { useGrimoire } from "@/core/state"; import { formatFileSize } from "@/lib/imeta"; +import { MediaDialog } from "@/components/nostr/MediaDialog"; import type { MediaRendererProps } from "@/components/nostr/RichText"; +/** + * Extract file extension from URL + */ +function getExtension(url: string): string | null { + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const lastSegment = pathname.split("/").pop() || ""; + if (lastSegment.includes(".")) { + return lastSegment.split(".").pop() || null; + } + return null; + } catch { + return null; + } +} + /** * Extract filename from URL */ @@ -22,7 +41,7 @@ function getFilename(url: string): string { // If it's a blossom hash, truncate it const hash = getHashFromURL(url); if (hash) { - const ext = lastSegment.includes(".") ? lastSegment.split(".").pop() : ""; + const ext = getExtension(url); return ext ? `${hash.slice(0, 8)}...${ext}` : `${hash.slice(0, 8)}...`; } // Decode URI component for readable filenames @@ -69,6 +88,7 @@ function MediaIcon({ type }: { type: "image" | "video" | "audio" }) { export function ChatMediaRenderer({ url, type, imeta }: MediaRendererProps) { const { addWindow } = useGrimoire(); + const [dialogOpen, setDialogOpen] = useState(false); const filename = imeta?.alt || getFilename(url); const size = imeta?.size ? formatFileSize(imeta.size) : null; @@ -86,35 +106,47 @@ export function ChatMediaRenderer({ url, type, imeta }: MediaRendererProps) { } }; - const handleFileClick = (e: React.MouseEvent) => { + const handleMediaClick = (e: React.MouseEvent) => { + e.preventDefault(); e.stopPropagation(); + // Images and videos open in dialog, audio opens in new tab + if (type === "image" || type === "video") { + setDialogOpen(true); + } else { + window.open(url, "_blank", "noopener,noreferrer"); + } }; return ( - - - - {filename} - - {size && ( - {size} - )} - {blossom && ( + <> + + - )} - + {size && ( + {size} + )} + {blossom && ( + + )} + + + ); }