diff --git a/components/markdown-content.tsx b/components/markdown-content.tsx new file mode 100644 index 0000000..d0f47d0 --- /dev/null +++ b/components/markdown-content.tsx @@ -0,0 +1,308 @@ +"use client"; + +import React, { createContext, useContext } from "react"; +import ReactMarkdown from "react-markdown"; +import type { Components } from "react-markdown"; +import type { Element } from "hast"; + +// Lets the `code` renderer know it's inside a `pre` block vs. inline. +// React renders parent-to-child, so the Provider in `pre` is active +// when `code` renders inside it. +const InsidePreContext = createContext(false); + +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 as Element).tagName === "strong"; + + if (isKeyValue) { + const keyText = extractHastText(firstHast as Element); + // Skip the first rendered child (the ) — we replace it with KeyBadge. + const rest = React.Children.toArray(children).slice(1); + return ( +
  • + + {keyText} + {rest} +
  • + ); + } + + return ( +
  • + + {children} +
  • + ); + }, + + a: ({ href, children }) => ( + + {children} + + ), + + strong: ({ children }) => ( + {children} + ), + + em: ({ children }) => ( + {children} + ), + + // `pre` wraps `code` for fenced code blocks. The Provider lets the + // nested `code` renderer know it's in a block context (not inline). + pre: ({ children }) => ( + +
    +        {children}
    +      
    +
    + ), + + code: ({ children }) => { + const insidePre = useContext(InsidePreContext); + + if (insidePre) { + return ( + + {children} + + ); + } + + // Inline code + return ( + + {children} + + ); + }, +}; + +export default function MarkdownContent({ content }: { content: string }) { + return {content}; +}