mirror of
https://github.com/mroxso/zelo-news.git
synced 2026-06-04 09:31:14 +02:00
feat: enhance MarkdownContent to support Nostr mentions and improve URI handling
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
|
||||
interface MarkdownContentProps {
|
||||
content: string;
|
||||
@@ -14,6 +17,36 @@ interface MarkdownContentProps {
|
||||
* Used for rendering NIP-23 long-form blog posts
|
||||
*/
|
||||
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
// Preprocess content to convert plain nostr: URIs into markdown links
|
||||
const processedContent = useMemo(() => {
|
||||
// Regex to find nostr: URIs that are NOT already in markdown link format
|
||||
// This matches nostr:npub1..., nostr:note1..., etc. that aren't part of [text](nostr:...)
|
||||
const nostrUriRegex = /(?<!\]\()nostr:(npub1|note1|nprofile1|nevent1|naddr1)([023456789acdefghjklmnpqrstuvwxyz]+)/g;
|
||||
|
||||
return content.replace(nostrUriRegex, (match, prefix, data) => {
|
||||
const nostrId = `${prefix}${data}`;
|
||||
|
||||
try {
|
||||
// Validate it's a proper NIP-19 identifier
|
||||
const decoded = nip19.decode(nostrId);
|
||||
|
||||
// For npub/nprofile, we'll show @username in the link text
|
||||
// For other types, show the nostr: URI
|
||||
if (decoded.type === 'npub' || decoded.type === 'nprofile') {
|
||||
const pubkey = decoded.type === 'npub' ? decoded.data : decoded.data.pubkey;
|
||||
// We'll use a special format that we can detect in the link component
|
||||
return `[nostr-mention:${pubkey}](/${nostrId})`;
|
||||
} else {
|
||||
// For note, nevent, naddr - show the nostr: URI as link text
|
||||
return `[${match}](/${nostrId})`;
|
||||
}
|
||||
} catch {
|
||||
// If decoding fails, leave it as-is
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}, [content]);
|
||||
|
||||
return (
|
||||
<div className={cn('prose prose-slate dark:prose-invert prose-headings:font-bold prose-h1:text-4xl prose-h2:text-3xl prose-h3:text-2xl prose-h4:text-xl prose-h5:text-lg prose-h6:text-base max-w-none break-words overflow-wrap-anywhere', className)}>
|
||||
<ReactMarkdown
|
||||
@@ -52,7 +85,13 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
// Custom link renderer to handle nostr: URIs
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
// Handle nostr: URIs
|
||||
// Handle nostr-mention links (generated by preprocessor)
|
||||
if (typeof children === 'string' && children.startsWith('nostr-mention:')) {
|
||||
const pubkey = children.substring(14); // Remove "nostr-mention:" prefix
|
||||
return <NostrMention pubkey={pubkey} href={href} />;
|
||||
}
|
||||
|
||||
// Handle nostr: URIs in markdown links [text](nostr:npub1...)
|
||||
if (href?.startsWith('nostr:')) {
|
||||
const nostrId = href.substring(6); // Remove "nostr:" prefix
|
||||
|
||||
@@ -74,7 +113,19 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// Regular links open in new tab
|
||||
// Handle internal links (starting with /)
|
||||
if (href?.startsWith('/')) {
|
||||
return (
|
||||
<Link
|
||||
to={href}
|
||||
className="text-blue-500 hover:underline break-all"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Regular external links open in new tab
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
@@ -123,8 +174,29 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
||||
),
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
{processedContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper component to display user mentions
|
||||
function NostrMention({ pubkey, href }: { pubkey: string; href?: string }) {
|
||||
const author = useAuthor(pubkey);
|
||||
const hasRealName = !!author.data?.metadata?.name;
|
||||
const displayName = author.data?.metadata?.name ?? genUserName(pubkey);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={href || `/${nip19.npubEncode(pubkey)}`}
|
||||
className={cn(
|
||||
"font-medium hover:underline",
|
||||
hasRealName
|
||||
? "text-blue-500"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
)}
|
||||
>
|
||||
@{displayName}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user