From 6ff877e8a50b609b7980de9cb8f6593e355c08b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Mon, 15 Dec 2025 15:54:45 +0100 Subject: [PATCH] refactor: unify nostr markdown rendering --- .../nostr/kinds/IssueDetailRenderer.tsx | 226 +----------------- .../nostr/kinds/PullRequestDetailRenderer.tsx | 211 +--------------- 2 files changed, 8 insertions(+), 429 deletions(-) diff --git a/src/components/nostr/kinds/IssueDetailRenderer.tsx b/src/components/nostr/kinds/IssueDetailRenderer.tsx index fc6cf04..8a549c4 100644 --- a/src/components/nostr/kinds/IssueDetailRenderer.tsx +++ b/src/components/nostr/kinds/IssueDetailRenderer.tsx @@ -1,13 +1,7 @@ import { useMemo } from "react"; -import ReactMarkdown, { defaultUrlTransform } from "react-markdown"; -import remarkGfm from "remark-gfm"; -import { remarkNostrMentions } from "applesauce-content/markdown"; -import { nip19 } from "nostr-tools"; import { Tag, FolderGit2 } from "lucide-react"; import { UserName } from "../UserName"; -import { EmbeddedEvent } from "../EmbeddedEvent"; -import { MediaEmbed } from "../MediaEmbed"; -import { SyntaxHighlight } from "@/components/SyntaxHighlight"; +import { MarkdownContent } from "../MarkdownContent"; import { useGrimoire } from "@/core/state"; import { useNostrEvent } from "@/hooks/useNostrEvent"; import type { NostrEvent } from "@/types/nostr"; @@ -15,127 +9,23 @@ import { getIssueTitle, getIssueLabels, getIssueRepositoryAddress, -} from "@/lib/nip34-helpers"; -import { getRepositoryName, getRepositoryIdentifier, } from "@/lib/nip34-helpers"; import { Label } from "@/components/ui/Label"; /** - * Component to render nostr: mentions inline - */ -function NostrMention({ href }: { href: string }) { - const { addWindow } = useGrimoire(); - - try { - // Remove nostr: prefix and any trailing characters - const cleanHref = href.replace(/^nostr:/, "").trim(); - - // If it doesn't look like a nostr identifier, just return the href as-is - if (!cleanHref.match(/^(npub|nprofile|note|nevent|naddr)/)) { - return ( - - {href} - - ); - } - - const parsed = nip19.decode(cleanHref); - - switch (parsed.type) { - case "npub": - return ( - - - - ); - case "nprofile": - return ( - - - - ); - case "note": - return ( - { - addWindow( - "open", - { id: id as string }, - `Event ${(id as string).slice(0, 8)}...`, - ); - }} - /> - ); - case "nevent": - return ( - { - addWindow( - "open", - { id: id as string }, - `Event ${(id as string).slice(0, 8)}...`, - ); - }} - /> - ); - case "naddr": - return ( - { - addWindow( - "open", - pointer, - `${parsed.data.kind}:${parsed.data.identifier.slice(0, 8)}...`, - ); - }} - /> - ); - default: - return {cleanHref}; - } - } catch (error) { - // If parsing fails, just render as a regular link - console.error("Failed to parse nostr link:", href, error); - return ( - - {href} - - ); - } -} - -/** - * Detail renderer for Kind 1621 - Issue - * Displays full issue content with markdown rendering + * Detail renderer for Kind 1621 - Issue (NIP-34) + * Full view with repository context and markdown description */ export function IssueDetailRenderer({ event }: { event: NostrEvent }) { const { addWindow } = useGrimoire(); + const title = useMemo(() => getIssueTitle(event), [event]); const labels = useMemo(() => getIssueLabels(event), [event]); const repoAddress = useMemo(() => getIssueRepositoryAddress(event), [event]); - // Parse repository address + // Parse repository address if present const repoPointer = useMemo(() => { if (!repoAddress) return null; try { @@ -226,111 +116,7 @@ export function IssueDetailRenderer({ event }: { event: NostrEvent }) { {/* Issue Body - Markdown */} {event.content ? ( -
- { - if (url.startsWith("nostr:")) return url; - return defaultUrlTransform(url); - }} - components={{ - // Enable images with zoom - img: ({ src, alt }) => - src ? ( - - ) : null, - // Handle nostr: links - a: ({ href, children, ...props }) => { - if (!href) return null; - - // Render nostr: mentions inline - if (href.startsWith("nostr:")) { - return ; - } - - // Regular links - return ( - - {children} - - ); - }, - // Style adjustments for dark theme - h1: ({ ...props }) => ( -

- ), - h2: ({ ...props }) => ( -

- ), - h3: ({ ...props }) => ( -

- ), - p: ({ ...props }) => ( -

- ), - code: ({ className, children, ...props }: any) => { - const match = /language-(\w+)/.exec(className || ""); - const language = match ? match[1] : null; - const code = String(children).replace(/\n$/, ""); - - // Inline code (no language) - if (!language) { - return ( - - {children} - - ); - } - - // Block code with syntax highlighting - return ( - - ); - }, - blockquote: ({ ...props }) => ( -

- ), - ul: ({ ...props }) => ( -
    - ), - ol: ({ ...props }) => ( -
      - ), - hr: () =>
      , - }} - > - {event.content.replace(/\\n/g, "\n")} - -

+ ) : (

(No description provided) diff --git a/src/components/nostr/kinds/PullRequestDetailRenderer.tsx b/src/components/nostr/kinds/PullRequestDetailRenderer.tsx index a29491c..bb43274 100644 --- a/src/components/nostr/kinds/PullRequestDetailRenderer.tsx +++ b/src/components/nostr/kinds/PullRequestDetailRenderer.tsx @@ -1,13 +1,7 @@ import { useMemo } from "react"; -import ReactMarkdown, { defaultUrlTransform } from "react-markdown"; -import remarkGfm from "remark-gfm"; -import { remarkNostrMentions } from "applesauce-content/markdown"; -import { nip19 } from "nostr-tools"; import { GitBranch, FolderGit2, Tag, Copy, CopyCheck } from "lucide-react"; import { UserName } from "../UserName"; -import { EmbeddedEvent } from "../EmbeddedEvent"; -import { MediaEmbed } from "../MediaEmbed"; -import { SyntaxHighlight } from "@/components/SyntaxHighlight"; +import { MarkdownContent } from "../MarkdownContent"; import { useCopy } from "@/hooks/useCopy"; import { useGrimoire } from "@/core/state"; import { useNostrEvent } from "@/hooks/useNostrEvent"; @@ -27,106 +21,6 @@ import { } from "@/lib/nip34-helpers"; import { Label } from "@/components/ui/Label"; -/** - * Component to render nostr: mentions inline - */ -function NostrMention({ href }: { href: string }) { - const { addWindow } = useGrimoire(); - - try { - const cleanHref = href.replace(/^nostr:/, "").trim(); - - if (!cleanHref.match(/^(npub|nprofile|note|nevent|naddr)/)) { - return ( - - {href} - - ); - } - - const parsed = nip19.decode(cleanHref); - - switch (parsed.type) { - case "npub": - return ( - - - - ); - case "nprofile": - return ( - - - - ); - case "note": - return ( - { - addWindow( - "open", - { id: id as string }, - `Event ${(id as string).slice(0, 8)}...`, - ); - }} - /> - ); - case "nevent": - return ( - { - addWindow( - "open", - { id: id as string }, - `Event ${(id as string).slice(0, 8)}...`, - ); - }} - /> - ); - case "naddr": - return ( - { - addWindow( - "open", - pointer, - `${parsed.data.kind}:${parsed.data.identifier.slice(0, 8)}...`, - ); - }} - /> - ); - default: - return {cleanHref}; - } - } catch (error) { - console.error("Failed to parse nostr link:", href, error); - return ( - - {href} - - ); - } -} - /** * Detail renderer for Kind 1618 - Pull Request * Displays full PR content with markdown rendering @@ -344,108 +238,7 @@ export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) { {/* PR Description - Markdown */} {event.content ? ( - <> -

- { - if (url.startsWith("nostr:")) return url; - return defaultUrlTransform(url); - }} - components={{ - img: ({ src, alt }) => - src ? ( - - ) : null, - a: ({ href, children, ...props }) => { - if (!href) return null; - - if (href.startsWith("nostr:")) { - return ; - } - - return ( - - {children} - - ); - }, - h1: ({ ...props }) => ( -

- ), - h2: ({ ...props }) => ( -

- ), - h3: ({ ...props }) => ( -

- ), - p: ({ ...props }) => ( -

- ), - code: ({ className, children, ...props }: any) => { - const match = /language-(\w+)/.exec(className || ""); - const language = match ? match[1] : null; - const code = String(children).replace(/\n$/, ""); - - // Inline code (no language) - if (!language) { - return ( - - {children} - - ); - } - - // Block code with syntax highlighting - return ( - - ); - }, - blockquote: ({ ...props }) => ( -

- ), - ul: ({ ...props }) => ( -
    - ), - ol: ({ ...props }) => ( -
      - ), - hr: () =>
      , - }} - > - {event.content.replace(/\\n/g, "\n")} - -

- + ) : (

(No description provided)