This commit is contained in:
z.ai committed 2026-10-08 11:09:37 +08:00
1 parent c1541b80df
commit b4c7908029
97 files changed
+12403 -154

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
import Image from "next/image";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import { Callout } from "./callout";
import { CodeBlock } from "./code-block";
/**
* MDX 组件映射。
*
* 通过替换原生元素,把 DESIGN.md 的排版规则与可访问性要求统一注入正文:
* - img → next/image(图片优化 + 保留 alt)
* - pre → 带语言标签与复制按钮的代码块
* - a → 外链自动补 rel/target
*
* 类型说明:这里不使用 `mdx/types` 的 MDXComponents,因为它依赖可选安装的
* @types/mdx。改为按组件签名手写映射类型,避免引入额外类型包。
*/
type MdxImageProps = ComponentPropsWithoutRef<"img">;
type MdxAnchorProps = ComponentPropsWithoutRef<"a"> & { children?: ReactNode };
/**
* MDX 组件映射类型。
*
* `next-mdx-remote` 内部使用 `MDXComponents`(带字符串索引签名),
* 因此这里显式声明索引签名,保证类型兼容的同时仍然约束了我们实际用到的键。
*/
export interface MdxComponentMap {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
img: (props: MdxImageProps) => ReactNode;
a: (props: MdxAnchorProps) => ReactNode;
pre: typeof CodeBlock;
Callout: typeof Callout;
}
export const mdxComponents: MdxComponentMap = {
// 文章内图片改用 next/image,alt 缺失时降级为空字符串(装饰性图片)
img: ({ src, alt, width, height }) => {
if (typeof src !== "string") {
return null;
}
return (
<Image
src={src}
alt={alt ?? ""}
width={typeof width === "number" ? width : 1200}
height={typeof height === "number" ? height : 630}
className="rounded-lg border border-hairline"
sizes="(max-width: 768px) 100vw, 720px"
/>
);
},
// 外链统一加上安全属性;站内锚点(# 开头)与相对路径保持原样
a: ({ href, children, ...props }) => {
const isExternal = typeof href === "string" && /^https?:\/\//.test(href);
return (
<a
href={href}
{...(isExternal
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
{...props}
>
{children}
</a>
);
},
pre: CodeBlock,
Callout,
};