diff --git a/src/components/chat/ChatMediaRenderer.tsx b/src/components/chat/ChatMediaRenderer.tsx new file mode 100644 index 0000000..43c9c1b --- /dev/null +++ b/src/components/chat/ChatMediaRenderer.tsx @@ -0,0 +1,126 @@ +/** + * Chat-specific media renderer + * + * Shows inline file info instead of embedded media: + * [icon] filename [size] [blossom-link] + */ + +import { Image, Video, Music, File, Flower2 } from "lucide-react"; +import { useGrimoire } from "@/core/state"; +import { formatFileSize } from "@/lib/imeta"; +import type { MediaRendererProps } from "@/components/nostr/RichText"; + +/** + * Extract filename from URL + */ +function getFilename(url: string): string { + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const lastSegment = pathname.split("/").pop() || ""; + // If it's a hash (64 hex chars), truncate it + if (/^[0-9a-f]{64}$/i.test(lastSegment.replace(/\.[^.]+$/, ""))) { + const ext = lastSegment.includes(".") ? lastSegment.split(".").pop() : ""; + const hash = lastSegment.replace(/\.[^.]+$/, ""); + return ext ? `${hash.slice(0, 8)}...${ext}` : `${hash.slice(0, 8)}...`; + } + // Decode URI component for readable filenames + return decodeURIComponent(lastSegment) || "file"; + } catch { + return "file"; + } +} + +/** + * Check if URL is a blossom URL (has sha256 hash in path) + * Returns the sha256 and server URL if it is, null otherwise + */ +function parseBlossomUrl( + url: string, +): { sha256: string; serverUrl: string } | null { + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + const segments = pathname.split("/").filter(Boolean); + const lastSegment = segments[segments.length - 1] || ""; + // Remove extension if present + const possibleHash = lastSegment.replace(/\.[^.]+$/, ""); + + if (/^[0-9a-f]{64}$/i.test(possibleHash)) { + const serverUrl = `${urlObj.protocol}//${urlObj.host}`; + return { sha256: possibleHash.toLowerCase(), serverUrl }; + } + return null; + } catch { + return null; + } +} + +/** + * Get icon component based on media type + */ +function MediaIcon({ type }: { type: "image" | "video" | "audio" }) { + const iconClass = "size-4 shrink-0"; + switch (type) { + case "image": + return ; + case "video": + return