Files
2026-10-08 11:09:37 +08:00

75 lines
2.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};