* Rework docs navigation and move alternatives to top-level pages - Add nested docs nav with categories, collapsible groups, breadcrumbs, and a right-hand page outline with heading slugs. - Move alternatives out of /docs/alternatives into /alternatives/* marketing pages with 301 redirects. - Add github-slugger for heading IDs and require explicit width on SiteShell so new pages can't accidentally pick the narrow prose layout. - Remove the best-practices doc from public-docs. * Make docs content column prose-width
84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
import * as React from "react";
|
|
import ReactMarkdown, { type Components } from "react-markdown";
|
|
import { getDocsHighlighter, docsRehypePlugins, docsRemarkPlugins } from "~/docs-rehype";
|
|
|
|
function getCodeText(children: React.ReactNode): string {
|
|
return React.Children.toArray(children).join("");
|
|
}
|
|
|
|
function getLanguage(className: string | undefined): string | undefined {
|
|
return className?.match(/language-([^\s]+)/)?.[1];
|
|
}
|
|
|
|
function DocsPre({
|
|
children,
|
|
node: _node,
|
|
...props
|
|
}: React.ComponentProps<"pre"> & { node?: unknown }) {
|
|
const codeElement = React.Children.only(children);
|
|
const codeProps = React.isValidElement<React.ComponentProps<"code">>(codeElement)
|
|
? codeElement.props
|
|
: undefined;
|
|
const code = getCodeText(codeProps?.children);
|
|
const language = getLanguage(codeProps?.className);
|
|
const [highlightedHtml, setHighlightedHtml] = React.useState<string | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
setHighlightedHtml(null);
|
|
if (!language) return;
|
|
const lang = language;
|
|
|
|
async function highlight() {
|
|
try {
|
|
const highlighter = await getDocsHighlighter();
|
|
if (cancelled) return;
|
|
setHighlightedHtml(
|
|
highlighter.codeToHtml(code.replace(/\n$/, ""), {
|
|
lang,
|
|
theme: "catppuccin-mocha",
|
|
}),
|
|
);
|
|
} catch {
|
|
if (!cancelled) setHighlightedHtml(null);
|
|
}
|
|
}
|
|
|
|
void highlight();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [code, language]);
|
|
|
|
const highlightedMarkup = React.useMemo(
|
|
() => (highlightedHtml ? { __html: highlightedHtml } : undefined),
|
|
[highlightedHtml],
|
|
);
|
|
|
|
if (highlightedMarkup) {
|
|
return <div dangerouslySetInnerHTML={highlightedMarkup} />;
|
|
}
|
|
|
|
return <pre {...props}>{children}</pre>;
|
|
}
|
|
|
|
const docsMarkdownComponents: Components = {
|
|
pre: DocsPre,
|
|
};
|
|
|
|
export function DocsMarkdown({ children }: { children: string }) {
|
|
return (
|
|
<div className="docs-prose">
|
|
<ReactMarkdown
|
|
remarkPlugins={docsRemarkPlugins}
|
|
rehypePlugins={docsRehypePlugins}
|
|
components={docsMarkdownComponents}
|
|
>
|
|
{children}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
}
|