3933905d66
Remove use server from stream helper to fix RSC errors; support OPENAI_API_BASE alias; render HTML tables via rehype-raw with gua-image API; expand regions to 356 prefecture-level cities. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
import Link from "next/link";
|
|
import Markdown from "react-markdown";
|
|
import rehypeRaw from "rehype-raw";
|
|
import type { LearnVariant } from "@/lib/content/gua-utils";
|
|
import { guaNumFromMark } from "@/lib/content/gua-utils";
|
|
|
|
function resolveLearnHref(
|
|
href: string | undefined,
|
|
variant: LearnVariant,
|
|
): string | undefined {
|
|
if (!href) {
|
|
return href;
|
|
}
|
|
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
return href;
|
|
}
|
|
|
|
const base = variant === "traditional" ? "/learn" : "/learn/other";
|
|
|
|
if (href === "index.md" || href === "./index.md") {
|
|
return base;
|
|
}
|
|
if (href === "other/index.md") {
|
|
return "/learn/other";
|
|
}
|
|
if (href.endsWith("/index.md")) {
|
|
const mark = href.replace(/\/index\.md$/, "").replace(/^\.\//, "");
|
|
if (mark.startsWith("other/")) {
|
|
const folder = mark.slice("other/".length);
|
|
const num = folder.split(".")[0];
|
|
return `/learn/other/${num}`;
|
|
}
|
|
const num = mark.split(".")[0];
|
|
return `${base}/${num}`;
|
|
}
|
|
if (/^\.\.\/\d{2}\./.test(href)) {
|
|
const num = href.replace(/^\.\.\//, "").split(".")[0];
|
|
return `${base}/${num}`;
|
|
}
|
|
if (/^\d{2}\./.test(href)) {
|
|
const num = href.split(".")[0];
|
|
return `${base}/${num}`;
|
|
}
|
|
return href;
|
|
}
|
|
|
|
function resolveImageSrc(
|
|
src: string | undefined,
|
|
variant: LearnVariant,
|
|
guaNum: string,
|
|
): string | undefined {
|
|
if (!src) {
|
|
return src;
|
|
}
|
|
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
return src;
|
|
}
|
|
const file = src.replace(/^\.\//, "").split("/").pop() ?? src;
|
|
const apiVariant = variant === "traditional" ? "traditional" : "simplified";
|
|
return `/api/gua-image/${apiVariant}/${guaNum}/${encodeURIComponent(file)}`;
|
|
}
|
|
|
|
export default function MarkdownContent({
|
|
content,
|
|
variant = "traditional",
|
|
guaMark,
|
|
}: {
|
|
content: string;
|
|
variant?: LearnVariant;
|
|
guaMark: string;
|
|
}) {
|
|
const guaNum = guaNumFromMark(guaMark);
|
|
|
|
return (
|
|
<Markdown
|
|
className="prose max-w-none dark:prose-invert prose-headings:scroll-mt-20 prose-a:text-primary prose-table:text-sm"
|
|
rehypePlugins={[rehypeRaw]}
|
|
components={{
|
|
a: ({ href, children, ...props }) => {
|
|
const resolved = resolveLearnHref(href, variant);
|
|
if (resolved?.startsWith("/")) {
|
|
return (
|
|
<Link href={resolved} {...props}>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
return (
|
|
<a href={resolved} {...props}>
|
|
{children}
|
|
</a>
|
|
);
|
|
},
|
|
img: ({ src, alt }) => {
|
|
const resolved = resolveImageSrc(
|
|
typeof src === "string" ? src : undefined,
|
|
variant,
|
|
guaNum,
|
|
);
|
|
if (!resolved) {
|
|
return null;
|
|
}
|
|
return (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src={resolved}
|
|
alt={alt ?? "卦象图片"}
|
|
className="mx-auto h-auto max-w-[120px]"
|
|
loading="lazy"
|
|
/>
|
|
);
|
|
},
|
|
}}
|
|
>
|
|
{content}
|
|
</Markdown>
|
|
);
|
|
}
|