mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 16:37:06 +02:00
refactor: consolidate markdown renderer
This commit is contained in:
@@ -1,233 +0,0 @@
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type { Components } from "react-markdown";
|
||||
import { useMemo } from "react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { MediaEmbed } from "@/components/nostr/MediaEmbed";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Markdown({ content, className = "" }: MarkdownProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
|
||||
const components: Components = useMemo(
|
||||
() => ({
|
||||
// Headings
|
||||
h1: ({ children }) => (
|
||||
<h1
|
||||
dir="auto"
|
||||
className="text-lg font-bold mt-4 mb-3 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2
|
||||
dir="auto"
|
||||
className="text-base font-bold mt-4 mb-2 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3
|
||||
dir="auto"
|
||||
className="text-sm font-bold mt-3 mb-2 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4
|
||||
dir="auto"
|
||||
className="text-sm font-bold mt-3 mb-2 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children }) => (
|
||||
<h5
|
||||
dir="auto"
|
||||
className="text-xs font-bold mt-2 mb-1 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children }) => (
|
||||
<h6
|
||||
dir="auto"
|
||||
className="text-xs font-bold mt-2 mb-1 first:mt-0 text-start"
|
||||
>
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
|
||||
// Paragraphs and text
|
||||
p: ({ children }) => (
|
||||
<p
|
||||
dir="auto"
|
||||
className="mb-3 leading-relaxed text-sm last:mb-0 break-words text-start"
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
|
||||
// Links
|
||||
a: ({ href, children }) => {
|
||||
console.log("[Markdown Link]", { href, children });
|
||||
|
||||
// Check if it's a relative NIP link (e.g., "./01.md" or "01.md" or "30.md")
|
||||
// Must be relative (not start with http:// or https://)
|
||||
const isRelativeLink =
|
||||
href && !href.startsWith("http://") && !href.startsWith("https://");
|
||||
if (isRelativeLink && (href.endsWith(".md") || href.includes(".md#"))) {
|
||||
console.log("[Markdown] Detected relative .md link:", href);
|
||||
|
||||
// Extract NIP number from various formats (numeric 1-3 digits or hex A0-FF)
|
||||
const nipMatch = href.match(/([0-9A-F]{1,3})\.md/i);
|
||||
console.log("[Markdown] Regex match result:", nipMatch);
|
||||
|
||||
if (nipMatch) {
|
||||
const nipNumber = nipMatch[1].toUpperCase();
|
||||
console.log("[Markdown] Creating NIP link for NIP-" + nipNumber);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`#nip-${nipNumber}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log("[Markdown] NIP link clicked! NIP-" + nipNumber);
|
||||
console.log("[Markdown] Calling addWindow directly");
|
||||
addWindow("nip", { number: nipNumber }, `NIP ${nipNumber}`);
|
||||
console.log("[Markdown] addWindow called");
|
||||
}}
|
||||
className="text-accent underline decoration-dotted cursor-crosshair hover:text-accent/80 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Regular external link
|
||||
console.log("[Markdown] Creating regular external link for:", href);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary underline decoration-dotted cursor-crosshair hover:text-primary/80 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
||||
// Lists
|
||||
ul: ({ children }) => (
|
||||
<ul
|
||||
dir="auto"
|
||||
className="list-disc list-inside mb-3 space-y-1 text-sm text-start"
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol
|
||||
dir="auto"
|
||||
className="list-decimal list-inside mb-3 space-y-1 text-sm text-start"
|
||||
>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li dir="auto" className="leading-relaxed text-start">
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
|
||||
// Blockquotes
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote
|
||||
dir="auto"
|
||||
className="border-s-4 border-muted-foreground/30 ps-3 py-2 my-3 italic text-muted-foreground text-sm text-start"
|
||||
>
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
|
||||
// Code
|
||||
code: (props) => {
|
||||
const { children, className } = props;
|
||||
const inline = !className?.includes("language-");
|
||||
|
||||
return inline ? (
|
||||
<code className="bg-muted px-1.5 py-0.5 rounded text-xs break-all">
|
||||
{children}
|
||||
</code>
|
||||
) : (
|
||||
<code className="block bg-muted p-3 rounded-lg my-3 overflow-x-auto text-xs leading-relaxed max-w-full">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
pre: ({ children }) => <pre className="my-3">{children}</pre>,
|
||||
|
||||
// Horizontal rule
|
||||
hr: () => <hr className="my-4 border-border" />,
|
||||
|
||||
// Tables
|
||||
table: ({ children }) => (
|
||||
<div className="overflow-x-auto my-3">
|
||||
<table className="min-w-full border-collapse border border-border text-sm">
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }) => <thead className="bg-muted">{children}</thead>,
|
||||
tbody: ({ children }) => <tbody>{children}</tbody>,
|
||||
tr: ({ children }) => (
|
||||
<tr className="border-b border-border">{children}</tr>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="px-3 py-1.5 text-left font-bold border border-border">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="px-3 py-1.5 border border-border">{children}</td>
|
||||
),
|
||||
|
||||
// Images - Inline with zoom
|
||||
img: ({ src, alt }) =>
|
||||
src ? (
|
||||
<MediaEmbed
|
||||
url={src}
|
||||
alt={alt}
|
||||
preset="preview"
|
||||
enableZoom
|
||||
className="my-3"
|
||||
/>
|
||||
) : null,
|
||||
|
||||
// Emphasis
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-bold">{children}</strong>
|
||||
),
|
||||
em: ({ children }) => <em className="italic">{children}</em>,
|
||||
}),
|
||||
[addWindow],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ReactMarkdown components={components} remarkPlugins={[remarkGfm]}>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNip } from "@/hooks/useNip";
|
||||
import { Markdown } from "./Markdown";
|
||||
import { MarkdownContent } from "./nostr/MarkdownContent";
|
||||
import { KindBadge } from "./KindBadge";
|
||||
import { getKindsForNip } from "@/lib/nip-kinds";
|
||||
|
||||
@@ -38,7 +38,7 @@ export function NipRenderer({ nipId, className = "" }: NipRendererProps) {
|
||||
|
||||
return (
|
||||
<div className={`p-4 overflow-x-hidden ${className}`}>
|
||||
<Markdown content={content} />
|
||||
<MarkdownContent content={content} />
|
||||
|
||||
{kinds.length > 0 && (
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
|
||||
Reference in New Issue
Block a user