-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathblog-mdx.tsx
More file actions
84 lines (78 loc) · 2.67 KB
/
Copy pathblog-mdx.tsx
File metadata and controls
84 lines (78 loc) · 2.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import Link from "next/link";
import type { ComponentProps } from "react";
import { MDXRemote } from "next-mdx-remote/rsc";
import rehypeAutolinkHeadings, {
type Options as RehypeAutolinkOptions,
} from "rehype-autolink-headings";
import rehypePrettyCode, {
type Options as RehypePrettyCodeOptions,
type Theme as RehypePrettyCodeTheme,
} from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import { sqlriteShikiTheme } from "@/lib/highlight";
function isInternal(href: string | undefined): boolean {
if (!href) return false;
return href.startsWith("/") || href.startsWith("#");
}
function Anchor({ href, children, ...rest }: ComponentProps<"a">) {
if (isInternal(href)) {
return (
<Link href={href ?? "#"} {...rest}>
{children}
</Link>
);
}
return (
<a href={href} target="_blank" rel="noreferrer" {...rest}>
{children}
</a>
);
}
const components = {
a: Anchor,
};
// Shiki emits inline styles like `style="color: var(--shiki-token-keyword)"`.
// `globals.css` maps those CSS vars onto the blog's existing color tokens, so
// the highlighter stays a pure data layer and themes stay coherent.
const prettyCodeOptions: RehypePrettyCodeOptions = {
// `createCssVariablesTheme` returns `ThemeRegistration` (all-optional
// fields); rehype-pretty-code wants `ThemeRegistrationRaw` (`settings`
// required). The shapes are structurally compatible — shiki tolerates the
// missing `settings` because the rules live in `tokenColors`.
theme: sqlriteShikiTheme as RehypePrettyCodeTheme,
keepBackground: false,
// Inline `code` keeps the existing `.blog-article-body code:not(pre code)`
// chip style — only fenced blocks get tokenized.
bypassInlineCode: true,
// Fences without a language tag (or with an unknown one) fall back to
// plaintext rather than throwing during the build.
defaultLang: { block: "plaintext" },
};
// Append a `#` anchor link inside every slugged heading. The slugs come
// from rehype-slug (github-slugger), so they're stable across renders and
// match what the article ToC in [slug]/page.tsx computes server-side.
const autolinkOptions: RehypeAutolinkOptions = {
behavior: "append",
properties: {
className: ["heading-anchor"],
ariaLabel: "Link to this section",
},
content: { type: "text", value: "#" },
};
export function BlogMDX({ source }: { source: string }) {
return (
<MDXRemote
source={source}
components={components}
options={{
mdxOptions: {
rehypePlugins: [
rehypeSlug,
[rehypeAutolinkHeadings, autolinkOptions],
[rehypePrettyCode, prettyCodeOptions],
],
},
}}
/>
);
}