Skip to content

Commit 1d685c2

Browse files
committed
feat: add syntax highlighting for markdown code blocks
1 parent dd6ab8f commit 1d685c2

File tree

5 files changed

+881
-3
lines changed

5 files changed

+881
-3
lines changed

lib/markdownToHtml.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,48 @@
11
import remark from 'remark'
22
import html from 'remark-html'
33
import gfm from 'remark-gfm'
4+
import { visit } from 'unist-util-visit'
5+
import prism from 'prismjs'
6+
7+
// Load Prism languages
8+
require('prismjs/components/prism-bash')
9+
require('prismjs/components/prism-javascript')
10+
require('prismjs/components/prism-typescript')
11+
require('prismjs/components/prism-css')
12+
require('prismjs/components/prism-jsx')
13+
require('prismjs/components/prism-json')
14+
require('prismjs/components/prism-markdown')
15+
require('prismjs/components/prism-python')
16+
require('prismjs/components/prism-ruby')
17+
require('prismjs/components/prism-go')
18+
require('prismjs/components/prism-rust')
19+
require('prismjs/components/prism-yaml')
20+
require('prismjs/components/prism-nix')
21+
22+
// Custom plugin to add syntax highlighting
23+
function syntaxHighlight() {
24+
return (tree) => {
25+
visit(tree, 'code', (node) => {
26+
const lang = node.lang || '';
27+
if (lang && prism.languages[lang]) {
28+
node.type = 'html';
29+
const highlighted = prism.highlight(
30+
node.value,
31+
prism.languages[lang],
32+
lang
33+
);
34+
node.value = `<pre class="language-${lang}"><code class="language-${lang}">${highlighted}</code></pre>`;
35+
}
36+
});
37+
};
38+
}
439

540
export default async function markdownToHtml(markdown) {
6-
const result = await remark().use(gfm).use(html).process(markdown)
7-
return result.toString()
41+
const result = await remark()
42+
.use(gfm)
43+
.use(syntaxHighlight)
44+
.use(html, { sanitize: false })
45+
.process(markdown);
46+
47+
return result.toString();
848
}

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@
1616
"date-fns": "2.16.1",
1717
"gray-matter": "4.0.2",
1818
"next": "15.2.3",
19+
"prismjs": "^1.30.0",
1920
"react": "^18.2.0",
2021
"react-dom": "^18.2.0",
22+
"rehype-prism-plus": "^2.0.0",
23+
"rehype-stringify": "^10.0.1",
2124
"remark": "13.0.0",
2225
"remark-gfm": "1.0.0",
23-
"remark-html": "^13.0.2"
26+
"remark-html": "^13.0.2",
27+
"remark-parse": "^11.0.0",
28+
"remark-rehype": "^11.1.1",
29+
"unified": "^11.0.5",
30+
"unist-util-visit": "^5.0.0"
2431
},
2532
"devDependencies": {
2633
"autoprefixer": "^10.2.1",

pages/_app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import '../styles/index.css'
2+
import '../styles/prism.css'
23

34
export default function MyApp({ Component, pageProps }) {
45
return <Component {...pageProps} />

styles/prism.css

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Prism.js syntax highlighting styles
3+
* Based on the default theme with some customizations
4+
*/
5+
6+
code[class*="language-"],
7+
pre[class*="language-"] {
8+
color: #f8f8f2;
9+
background: none;
10+
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
11+
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
12+
font-size: 1em;
13+
text-align: left;
14+
white-space: pre;
15+
word-spacing: normal;
16+
word-break: normal;
17+
word-wrap: normal;
18+
line-height: 1.5;
19+
-moz-tab-size: 4;
20+
-o-tab-size: 4;
21+
tab-size: 4;
22+
-webkit-hyphens: none;
23+
-moz-hyphens: none;
24+
-ms-hyphens: none;
25+
hyphens: none;
26+
}
27+
28+
/* Code blocks */
29+
pre[class*="language-"] {
30+
padding: 1em;
31+
margin: .5em 0;
32+
overflow: auto;
33+
border-radius: 0.3em;
34+
}
35+
36+
:not(pre) > code[class*="language-"],
37+
pre[class*="language-"] {
38+
background: #282a36;
39+
}
40+
41+
/* Inline code */
42+
:not(pre) > code[class*="language-"] {
43+
padding: .1em;
44+
border-radius: .3em;
45+
white-space: normal;
46+
}
47+
48+
.token.comment,
49+
.token.prolog,
50+
.token.doctype,
51+
.token.cdata {
52+
color: #6272a4;
53+
}
54+
55+
.token.punctuation {
56+
color: #f8f8f2;
57+
}
58+
59+
.namespace {
60+
opacity: .7;
61+
}
62+
63+
.token.property,
64+
.token.tag,
65+
.token.constant,
66+
.token.symbol,
67+
.token.deleted {
68+
color: #ff79c6;
69+
}
70+
71+
.token.boolean,
72+
.token.number {
73+
color: #bd93f9;
74+
}
75+
76+
.token.selector,
77+
.token.attr-name,
78+
.token.string,
79+
.token.char,
80+
.token.builtin,
81+
.token.inserted {
82+
color: #50fa7b;
83+
}
84+
85+
.token.operator,
86+
.token.entity,
87+
.token.url,
88+
.language-css .token.string,
89+
.style .token.string {
90+
color: #f8f8f2;
91+
}
92+
93+
.token.atrule,
94+
.token.attr-value,
95+
.token.keyword {
96+
color: #ff79c6;
97+
}
98+
99+
.token.function,
100+
.token.class-name {
101+
color: #8be9fd;
102+
}
103+
104+
.token.regex,
105+
.token.important,
106+
.token.variable {
107+
color: #f1fa8c;
108+
}
109+
110+
.token.important,
111+
.token.bold {
112+
font-weight: bold;
113+
}
114+
115+
.token.italic {
116+
font-style: italic;
117+
}
118+
119+
.token.entity {
120+
cursor: help;
121+
}
122+
123+
/* Line highlighting */
124+
.line-highlight {
125+
background: rgba(255, 255, 255, 0.1);
126+
border-left: 3px solid #ff79c6;
127+
}
128+
129+
/* Line numbers */
130+
.line-numbers .line-numbers-rows {
131+
border-right: 1px solid #44475a;
132+
}
133+
134+
.line-numbers-rows > span:before {
135+
color: #6272a4;
136+
}
137+
138+
/* Command line */
139+
.command-line-prompt {
140+
border-right: 1px solid #44475a;
141+
}
142+
143+
.command-line-prompt > span:before {
144+
color: #6272a4;
145+
}

0 commit comments

Comments
 (0)