import React from "react";
import ReactMarkdown from "react-markdown";
import type { Components } from "react-markdown";
import type { Element } from "hast";
function GoldDot() {
return (
);
}
function BlueDot() {
return (
);
}
function KeyBadge({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
// Extracts plain text from a HAST element's text children,
// stripping a trailing colon (e.g. "Backend:" → "Backend").
function extractHastText(node: Element): string {
return (node.children ?? [])
.filter((c): c is { type: "text"; value: string } => c.type === "text")
.map((c) => c.value)
.join("")
.replace(/:$/, "")
.trim();
}
const components: Components = {
h1: ({ children }) => (
{children}
),
h2: ({ children }) => (
{children}
),
h3: ({ children }) => (
{children}
),
p: ({ children }) => (
{children}
),
ul: ({ children }) => (
),
li: ({ node, children }) => {
// Inspect HAST to detect the `- **Key:** value` pattern.
// HAST tagName is always 'strong' regardless of custom component overrides.
const firstHast = node?.children?.[0];
const isKeyValue =
firstHast?.type === "element" &&
firstHast.tagName === "strong";
if (isKeyValue) {
const keyText = extractHastText(firstHast);
// Skip index 0 (the rendered element) — works because react-markdown
// emits strong as child[0] for tight lists with the `- **Key:** value` pattern.
// Tight lists only: loose lists wrap content in , changing the HAST structure.
const rest = React.Children.toArray(children).slice(1);
return (
{keyText}
{rest}
);
}
return (
{children}
);
},
a: ({ href, children }) => {
const isExternal = !!href && /^https?:\/\//.test(href);
return (
{children}
);
},
strong: ({ children }) => (
{children}
),
em: ({ children }) => (
{children}
),
// `pre` reads code text directly from the HAST node so `code` renderer
// is only ever called for inline code — no client-side context needed.
pre: ({ node }) => {
const codeEl = node?.children?.[0] as Element | undefined;
const codeText = (codeEl?.children ?? [])
.filter((c): c is { type: "text"; value: string } => c.type === "text")
.map((c) => c.value)
.join("");
return (
{codeText}
);
},
// Always inline — block code is handled entirely by the `pre` renderer above.
code: ({ children }) => (
{children}
),
};
export default function MarkdownContent({ content }: { content: string }) {
return {content};
}