<3/products/personal-site/components/

WorkspaceMarkdown.tsx

import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { inventory, mediaURL, prepareMarkdown, resolveLink } from "@/lib/workspace.mjs";
import styles from "./GhostEssay.module.css";

export function WorkspaceMarkdown({ source, markdown, className }: { source: string; markdown: string; className?: string }) {
  return <div className={className ?? styles.body}><Markdown remarkPlugins={[remarkGfm]} components={{
    a({ href, title, children }) {
      const url = resolveLink(source, href ?? "");
      if (title === "video") return <video className={styles.video} src={url} controls playsInline preload="metadata" aria-label={typeof children === "string" ? children : "video"} />;
      if (title === "audio") return <audio src={url} controls preload="metadata" aria-label={typeof children === "string" ? children : "audio"} />;
      return <a href={url} title={title} target={source === "README.md" && /^https?:/.test(url) ? "_blank" : undefined} rel={source === "README.md" ? "noopener noreferrer" : undefined}>{children}</a>;
    },
    img({ src, alt }) {
      let url = typeof src === "string" ? resolveLink(source, src) : "";
      try {
        const candidate = decodeURIComponent(url).replace(/^\//, "");
        if (inventory().files.get(candidate)?.kind === "media") url = mediaURL(candidate);
      } catch { /* Malformed URL is left for the browser, never read as a path. */ }
      // Native images preserve source dimensions and work for notebook scans.
      // eslint-disable-next-line @next/next/no-img-element
      return <img src={url} alt={alt ?? ""} loading="lazy" />;
    },
  }}>{prepareMarkdown(source, markdown)}</Markdown></div>;
}