Support naddr/nevent/note slugs for unindexed posts, cache naddr lookups, render Nostr embeds in markdown, and add a consistency check for events mirrors. Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
import type { Components } from "react-markdown";
|
|
import { NostrEntity } from "./NostrEmbeds";
|
|
|
|
// Shared markdown renderers for blog article bodies. The `remarkNostr` plugin
|
|
// rewrites NIP-27 `nostr:` references into links with that scheme, which the
|
|
// `a` renderer below swaps for live profile/note embeds.
|
|
export const markdownComponents: Components = {
|
|
h1: ({ children }) => (
|
|
<h1 className="text-3xl font-bold text-on-surface mb-4 mt-10">{children}</h1>
|
|
),
|
|
h2: ({ children }) => (
|
|
<h2 className="text-2xl font-bold text-on-surface mb-4 mt-8">{children}</h2>
|
|
),
|
|
h3: ({ children }) => (
|
|
<h3 className="text-xl font-bold text-on-surface mb-3 mt-6">{children}</h3>
|
|
),
|
|
h4: ({ children }) => (
|
|
<h4 className="text-lg font-semibold text-on-surface mb-2 mt-4">{children}</h4>
|
|
),
|
|
p: ({ children }) => (
|
|
<p className="text-on-surface-variant leading-relaxed mb-6">{children}</p>
|
|
),
|
|
a: ({ href, children }) => {
|
|
if (href?.startsWith("nostr:")) {
|
|
return <NostrEntity bech32={href.slice("nostr:".length)} />;
|
|
}
|
|
return (
|
|
<a
|
|
href={href}
|
|
className="text-primary hover:underline"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
},
|
|
ul: ({ children }) => (
|
|
<ul className="list-disc ml-6 mb-6 space-y-2 text-on-surface-variant">{children}</ul>
|
|
),
|
|
ol: ({ children }) => (
|
|
<ol className="list-decimal ml-6 mb-6 space-y-2 text-on-surface-variant">{children}</ol>
|
|
),
|
|
li: ({ children }) => (
|
|
<li className="leading-relaxed">{children}</li>
|
|
),
|
|
blockquote: ({ children }) => (
|
|
<blockquote className="border-l-4 border-primary/30 pl-4 italic text-on-surface-variant mb-6">
|
|
{children}
|
|
</blockquote>
|
|
),
|
|
code: ({ className, children }) => {
|
|
const isBlock = className?.includes("language-");
|
|
if (isBlock) {
|
|
return (
|
|
<code className={`${className} block`}>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|
|
return (
|
|
<code className="bg-surface-container-high px-2 py-1 rounded text-sm text-primary">
|
|
{children}
|
|
</code>
|
|
);
|
|
},
|
|
pre: ({ children }) => (
|
|
<pre className="bg-surface-container-highest p-4 rounded-lg overflow-x-auto mb-6 text-sm">
|
|
{children}
|
|
</pre>
|
|
),
|
|
img: ({ src, alt }) => (
|
|
<img src={src} alt={alt || ""} className="rounded-lg max-w-full mb-6" />
|
|
),
|
|
hr: () => <hr className="border-surface-container-high my-8" />,
|
|
table: ({ children }) => (
|
|
<div className="overflow-x-auto mb-6">
|
|
<table className="w-full text-left text-on-surface-variant">{children}</table>
|
|
</div>
|
|
),
|
|
th: ({ children }) => (
|
|
<th className="px-4 py-2 font-semibold text-on-surface bg-surface-container-high">
|
|
{children}
|
|
</th>
|
|
),
|
|
td: ({ children }) => (
|
|
<td className="px-4 py-2">{children}</td>
|
|
),
|
|
};
|