Skip to content

Commit 8ba62e0

Browse files
committed
refactor: extract CodeBlock component for blog post code syntax highlighting
1 parent 3b2aad4 commit 8ba62e0

File tree

2 files changed

+58
-65
lines changed

2 files changed

+58
-65
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { CopyButton } from "@/components/ui/copy-button";
2+
import prettier from "prettier";
3+
import { codeToHtml } from "shiki";
4+
import type { BundledLanguage } from "shiki/bundle/web";
5+
6+
interface LanguageProps {
7+
children: string;
8+
lang: BundledLanguage;
9+
}
10+
11+
const getParserForLanguage = (language: string): string => {
12+
const languageMap: { [key: string]: string } = {
13+
js: "babel",
14+
jsx: "babel",
15+
ts: "typescript",
16+
tsx: "typescript",
17+
json: "json",
18+
css: "css",
19+
scss: "scss",
20+
less: "less",
21+
html: "html",
22+
xml: "xml",
23+
markdown: "markdown",
24+
md: "markdown",
25+
yaml: "yaml",
26+
yml: "yaml",
27+
};
28+
29+
return languageMap[language.toLowerCase()] || "babel";
30+
};
31+
32+
export async function CodeBlock(props: LanguageProps) {
33+
const format = await prettier.format(props.children, {
34+
semi: true,
35+
singleQuote: true,
36+
tabWidth: 2,
37+
useTabs: false,
38+
printWidth: 120,
39+
parser: getParserForLanguage(props.lang),
40+
});
41+
const out = await codeToHtml(format, {
42+
lang: props.lang,
43+
theme: "houston",
44+
});
45+
46+
return (
47+
<div className="group relative">
48+
<CopyButton text={format} />
49+
<div
50+
dangerouslySetInnerHTML={{ __html: out }}
51+
className="text-sm p-4 rounded-lg bg-[#18191F] overflow-auto"
52+
/>
53+
</div>
54+
);
55+
}

apps/website/app/[locale]/blog/[slug]/page.tsx

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
import { CopyButton } from "@/components/ui/copy-button";
21
import { getPost, getPosts } from "@/lib/ghost";
32
import type { Metadata, ResolvingMetadata } from "next";
4-
import { getTranslations, setRequestLocale } from "next-intl/server";
3+
import { getTranslations } from "next-intl/server";
54
import Image from "next/image";
65
import Link from "next/link";
76
import { notFound } from "next/navigation";
8-
import prettier from "prettier";
9-
import type { DetailedHTMLProps, HTMLAttributes } from "react";
107
import type React from "react";
118
import ReactMarkdown from "react-markdown";
129
import type { Components } from "react-markdown";
1310
import rehypeRaw from "rehype-raw";
1411
import remarkGfm from "remark-gfm";
1512
import remarkToc from "remark-toc";
16-
import { codeToHtml } from "shiki";
1713
import type { BundledLanguage } from "shiki/bundle/web";
18-
import slugify from "slugify";
1914
import TurndownService from "turndown";
2015
// @ts-ignore
2116
import * as turndownPluginGfm from "turndown-plugin-gfm";
17+
import { CodeBlock } from "./components/CodeBlock";
2218
import { H1, H2, H3 } from "./components/Headings";
2319
import { TableOfContents } from "./components/TableOfContents";
2420
import { ZoomableImage } from "./components/ZoomableImage";
25-
2621
type Props = {
2722
params: { locale: string; slug: string };
2823
};
@@ -84,62 +79,6 @@ export async function generateStaticParams() {
8479
);
8580
}
8681

87-
interface CodeProps
88-
extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
89-
inline?: boolean;
90-
className?: string;
91-
children?: React.ReactNode;
92-
}
93-
interface LanguageProps {
94-
children: string;
95-
lang: BundledLanguage;
96-
}
97-
98-
const getParserForLanguage = (language: string): string => {
99-
const languageMap: { [key: string]: string } = {
100-
js: "babel",
101-
jsx: "babel",
102-
ts: "typescript",
103-
tsx: "typescript",
104-
json: "json",
105-
css: "css",
106-
scss: "scss",
107-
less: "less",
108-
html: "html",
109-
xml: "xml",
110-
markdown: "markdown",
111-
md: "markdown",
112-
yaml: "yaml",
113-
yml: "yaml",
114-
};
115-
116-
return languageMap[language.toLowerCase()] || "babel";
117-
};
118-
119-
async function CodeBlock(props: LanguageProps) {
120-
const format = await prettier.format(props.children, {
121-
semi: true,
122-
singleQuote: true,
123-
tabWidth: 2,
124-
useTabs: false,
125-
printWidth: 120,
126-
parser: getParserForLanguage(props.lang),
127-
});
128-
const out = await codeToHtml(format, {
129-
lang: props.lang,
130-
theme: "houston",
131-
});
132-
133-
return (
134-
<div className="group relative">
135-
<CopyButton text={format} />
136-
<div
137-
dangerouslySetInnerHTML={{ __html: out }}
138-
className="text-sm p-4 rounded-lg bg-[#18191F] overflow-auto"
139-
/>
140-
</div>
141-
);
142-
}
14382
export default async function BlogPostPage({ params }: Props) {
14483
const { locale, slug } = await params;
14584
// setRequestLocale(locale);
@@ -235,9 +174,8 @@ export default async function BlogPostPage({ params }: Props) {
235174
className="object-cover max-w-lg mx-auto rounded-lg border max-lg:w-64 border-border overflow-hidden"
236175
/>
237176
),
238-
code: ({ inline, className, children, ...props }: CodeProps) => {
177+
code: ({ className, children }) => {
239178
const match = /language-(\w+)/.exec(className || "");
240-
241179
return (
242180
<CodeBlock lang={match ? (match[1] as BundledLanguage) : "ts"}>
243181
{children?.toString() || ""}

0 commit comments

Comments
 (0)