From 5b0f2821d681e4f64dc034ed8bbd167bc73e979e Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 12:58:01 +0200 Subject: [PATCH] feat: parse and render nostr identifiers in highlight comments - Detect and decode nostr: URIs (npub, nprofile, naddr, note, nevent) in comments - Render profiles as clickable links with shortened pubkeys (@abc12345...) - Render blog posts (kind:30023) as clickable article links - Shorten other event identifiers to prevent layout breaks - Add monospace styling for shortened nostr IDs - Maintains DRY principles by extending existing CommentContent component --- src/components/HighlightItem.tsx | 108 +++++++++++++++++++++++++++++-- src/styles/layout/highlights.css | 1 + 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/components/HighlightItem.tsx b/src/components/HighlightItem.tsx index f08e8fd9..c413dd57 100644 --- a/src/components/HighlightItem.tsx +++ b/src/components/HighlightItem.tsx @@ -29,16 +29,115 @@ const isImageUrl = (url: string): boolean => { } } -// Component to render comment with links and inline images +// Helper to render a nostr identifier +const renderNostrId = (nostrUri: string, index: number): JSX.Element => { + try { + // Remove nostr: prefix + const identifier = nostrUri.replace(/^nostr:/, '') + const decoded = nip19.decode(identifier) + + switch (decoded.type) { + case 'npub': { + const pubkey = decoded.data + return ( + e.stopPropagation()} + > + @{pubkey.slice(0, 8)}... + + ) + } + case 'nprofile': { + const { pubkey } = decoded.data + const npub = nip19.npubEncode(pubkey) + return ( + e.stopPropagation()} + > + @{pubkey.slice(0, 8)}... + + ) + } + case 'naddr': { + const { kind, pubkey, identifier } = decoded.data + // Check if it's a blog post (kind:30023) + if (kind === 30023) { + const naddr = nip19.naddrEncode({ kind, pubkey, identifier }) + return ( + e.stopPropagation()} + > + {identifier || 'Article'} + + ) + } + // For other kinds, show shortened identifier + return ( + + nostr:{identifier.slice(0, 12)}... + + ) + } + case 'note': { + const eventId = decoded.data + return ( + + note:{eventId.slice(0, 12)}... + + ) + } + case 'nevent': { + const { id } = decoded.data + return ( + + event:{id.slice(0, 12)}... + + ) + } + default: + // Fallback for unrecognized types + return ( + + {identifier.slice(0, 20)}... + + ) + } + } catch (error) { + // If decoding fails, show shortened identifier + const identifier = nostrUri.replace(/^nostr:/, '') + return ( + + {identifier.slice(0, 20)}... + + ) + } +} + +// Component to render comment with links, inline images, and nostr identifiers const CommentContent: React.FC<{ text: string }> = ({ text }) => { - // URL regex pattern - const urlPattern = /(https?:\/\/[^\s]+)/g + // Pattern to match both http(s) URLs and nostr: URIs + const urlPattern = /((?:https?:\/\/|nostr:)[^\s]+)/g const parts = text.split(urlPattern) return ( <> {parts.map((part, index) => { - if (part.match(urlPattern)) { + // Handle nostr: URIs + if (part.startsWith('nostr:')) { + return renderNostrId(part, index) + } + + // Handle http(s) URLs + if (part.match(/^https?:\/\//)) { if (isImageUrl(part)) { return ( = ({ text }) => { ) } } + return {part} })} diff --git a/src/styles/layout/highlights.css b/src/styles/layout/highlights.css index f341fe89..af0bbad9 100644 --- a/src/styles/layout/highlights.css +++ b/src/styles/layout/highlights.css @@ -134,6 +134,7 @@ .highlight-comment-text { flex: 1; min-width: 0; } .highlight-comment-link { color: var(--color-primary); text-decoration: underline; word-wrap: break-word; overflow-wrap: break-word; } .highlight-comment-link:hover { opacity: 0.8; } +.highlight-comment-nostr-id { font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace; font-size: 0.8em; color: var(--color-text-secondary); background: rgba(255, 255, 255, 0.05); padding: 0.125rem 0.375rem; border-radius: 3px; word-wrap: break-word; overflow-wrap: break-word; } .highlight-comment-image { display: block; max-width: 100%; height: auto; margin-top: 0.5rem; border-radius: 6px; border: 1px solid var(--color-border); } /* Level-colored comment icons */