diff --git a/astro.config.mjs b/astro.config.mjs
index 6966d9d..abb1d1f 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -3,6 +3,8 @@ import UnoCSS from 'unocss/astro'
import icon from 'astro-icon'
import sitemap from '@astrojs/sitemap'
import partytown from '@astrojs/partytown'
+import remarkMath from 'remark-math'
+import rehypeKatex from 'rehype-katex'
export default defineConfig({
site: 'https://4byte.dev',
@@ -17,4 +19,18 @@ export default defineConfig({
},
}),
],
+ markdown: {
+ remarkPlugins: [remarkMath],
+ rehypePlugins: [rehypeKatex],
+ shikiConfig: {
+ themes: {
+ dark: 'github-dark-dimmed',
+ light: 'github-light',
+ },
+ },
+ syntaxHighlight: {
+ type: 'shiki',
+ excludeLangs: ['mermaid'],
+ },
+ },
})
diff --git a/package.json b/package.json
index 23ea344..efe412b 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,9 @@
"astro": "^6.0.5",
"astro-icon": "^1.1.5",
"gray-matter": "^4.0.3",
+ "katex": "^0.16.40",
+ "rehype-katex": "^7.0.1",
+ "remark-math": "^6.0.0",
"satori": "^0.25.0",
"slugify": "^1.6.8"
},
@@ -33,6 +36,7 @@
"@astrojs/check": "^0.9.8",
"@eslint/js": "^10.0.1",
"@unocss/astro": "^66.6.6",
+ "@unocss/preset-typography": "^66.6.7",
"@unocss/reset": "^66.6.6",
"astro-eslint-parser": "^1.3.0",
"eslint": "^10.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f07d4d2..73696ba 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,6 +25,15 @@ importers:
gray-matter:
specifier: ^4.0.3
version: 4.0.3
+ katex:
+ specifier: ^0.16.40
+ version: 0.16.40
+ rehype-katex:
+ specifier: ^7.0.1
+ version: 7.0.1
+ remark-math:
+ specifier: ^6.0.0
+ version: 6.0.0
satori:
specifier: ^0.25.0
version: 0.25.0
@@ -41,6 +50,9 @@ importers:
'@unocss/astro':
specifier: ^66.6.6
version: 66.6.6(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2))
+ '@unocss/preset-typography':
+ specifier: ^66.6.7
+ version: 66.6.7
'@unocss/reset':
specifier: ^66.6.6
version: 66.6.6
@@ -1593,6 +1605,12 @@ packages:
integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==,
}
+ '@types/katex@0.16.8':
+ resolution:
+ {
+ integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==,
+ }
+
'@types/mdast@4.0.4':
resolution:
{
@@ -1700,6 +1718,12 @@ packages:
integrity: sha512-Sbbx0ZQqmV8K2lg8E+z9MJzWb1MgRtJnvqzxDIrNuBjXasKhbcFt5wEMBtEZJOr63Z4ck0xThhZK53HmYT2jmg==,
}
+ '@unocss/core@66.6.7':
+ resolution:
+ {
+ integrity: sha512-Q8456iWFtdwrUNYKVOQY8ygRggjZOVtLc6Jc8KIkxig7OiNlUWOgXJTfCh4I8g6jBYzC5eHaHFDLgJOmOrxBsg==,
+ }
+
'@unocss/extractor-arbitrary-variants@66.6.6':
resolution:
{
@@ -1742,6 +1766,12 @@ packages:
integrity: sha512-SM1km5nqt15z4sTabfOobSC633I5Ol5nnme6JFTra4wiyCUNs+Cg31nJ6jnopWDUT4SEAXqfUH7jKSSoCnI6ZA==,
}
+ '@unocss/preset-typography@66.6.7':
+ resolution:
+ {
+ integrity: sha512-RA7MwPDD5N9xGrbWnguVm5tP+F4/n/9X1rJsq2nBjvvK2dbtIRJZjRFM1vBDsR0GIhtvbHMoTchZaSZed5I+Hw==,
+ }
+
'@unocss/preset-uno@66.6.6':
resolution:
{
@@ -1785,6 +1815,13 @@ packages:
}
engines: { node: '>=14' }
+ '@unocss/rule-utils@66.6.7':
+ resolution:
+ {
+ integrity: sha512-4PT/s8yKIShSqP9XPSw4EjbZopcu3wlIB9i3kbGbzQwF91H+0Yy10guK3kHDGtkmWVN6Np6VvaGIj2UcbmaivA==,
+ }
+ engines: { node: '>=14' }
+
'@unocss/transformer-attributify-jsx@66.6.6':
resolution:
{
@@ -2226,6 +2263,13 @@ packages:
}
engines: { node: '>= 10' }
+ commander@8.3.0:
+ resolution:
+ {
+ integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==,
+ }
+ engines: { node: '>= 12' }
+
common-ancestor-path@2.0.0:
resolution:
{
@@ -2951,6 +2995,18 @@ packages:
integrity: sha512-iOH6Vl8mGd9nNfu9C0IZ+GuOAfJHcyf3VriQxWaSWIB76Fg4BnFuk4cxBxjmQSSxJS664+pgjP6e7VBnUzFfcg==,
}
+ hast-util-from-dom@5.0.1:
+ resolution:
+ {
+ integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==,
+ }
+
+ hast-util-from-html-isomorphic@2.0.0:
+ resolution:
+ {
+ integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==,
+ }
+
hast-util-from-html@2.0.3:
resolution:
{
@@ -3212,6 +3268,13 @@ packages:
integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==,
}
+ katex@0.16.40:
+ resolution:
+ {
+ integrity: sha512-1DJcK/L05k1Y9Gf7wMcyuqFOL6BiY3vY0CFcAM/LPRN04NALxcl6u7lOWNsp3f/bCHWxigzQl6FbR95XJ4R84Q==,
+ }
+ hasBin: true
+
keyv@4.5.4:
resolution:
{
@@ -3488,6 +3551,12 @@ packages:
integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==,
}
+ mdast-util-math@3.0.0:
+ resolution:
+ {
+ integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==,
+ }
+
mdast-util-phrasing@4.1.0:
resolution:
{
@@ -3585,6 +3654,12 @@ packages:
integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==,
}
+ micromark-extension-math@3.1.0:
+ resolution:
+ {
+ integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==,
+ }
+
micromark-factory-destination@2.0.1:
resolution:
{
@@ -4166,6 +4241,12 @@ packages:
}
hasBin: true
+ rehype-katex@7.0.1:
+ resolution:
+ {
+ integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==,
+ }
+
rehype-parse@9.0.1:
resolution:
{
@@ -4196,6 +4277,12 @@ packages:
integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==,
}
+ remark-math@6.0.0:
+ resolution:
+ {
+ integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==,
+ }
+
remark-parse@11.0.0:
resolution:
{
@@ -6052,6 +6139,8 @@ snapshots:
'@types/json-schema@7.0.15': {}
+ '@types/katex@0.16.8': {}
+
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -6130,6 +6219,8 @@ snapshots:
'@unocss/core@66.6.6': {}
+ '@unocss/core@66.6.7': {}
+
'@unocss/extractor-arbitrary-variants@66.6.6':
dependencies:
'@unocss/core': 66.6.6
@@ -6167,6 +6258,11 @@ snapshots:
'@unocss/core': 66.6.6
'@unocss/rule-utils': 66.6.6
+ '@unocss/preset-typography@66.6.7':
+ dependencies:
+ '@unocss/core': 66.6.7
+ '@unocss/rule-utils': 66.6.7
+
'@unocss/preset-uno@66.6.6':
dependencies:
'@unocss/core': 66.6.6
@@ -6201,6 +6297,11 @@ snapshots:
'@unocss/core': 66.6.6
magic-string: 0.30.21
+ '@unocss/rule-utils@66.6.7':
+ dependencies:
+ '@unocss/core': 66.6.7
+ magic-string: 0.30.21
+
'@unocss/transformer-attributify-jsx@66.6.6':
dependencies:
'@unocss/core': 66.6.6
@@ -6569,6 +6670,8 @@ snapshots:
commander@7.2.0: {}
+ commander@8.3.0: {}
+
common-ancestor-path@2.0.0: {}
confbox@0.1.8: {}
@@ -6991,6 +7094,19 @@ snapshots:
ufo: 1.6.3
uncrypto: 0.1.3
+ hast-util-from-dom@5.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hastscript: 9.0.1
+ web-namespaces: 2.0.1
+
+ hast-util-from-html-isomorphic@2.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-dom: 5.0.1
+ hast-util-from-html: 2.0.3
+ unist-util-remove-position: 5.0.0
+
hast-util-from-html@2.0.3:
dependencies:
'@types/hast': 3.0.4
@@ -7158,6 +7274,10 @@ snapshots:
jsonc-parser@3.3.1: {}
+ katex@0.16.40:
+ dependencies:
+ commander: 8.3.0
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -7377,6 +7497,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-math@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ longest-streak: 3.1.0
+ mdast-util-from-markdown: 2.0.3
+ mdast-util-to-markdown: 2.1.2
+ unist-util-remove-position: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-phrasing@4.1.0:
dependencies:
'@types/mdast': 4.0.4
@@ -7495,6 +7627,16 @@ snapshots:
micromark-util-combine-extensions: 2.0.1
micromark-util-types: 2.0.2
+ micromark-extension-math@3.1.0:
+ dependencies:
+ '@types/katex': 0.16.8
+ devlop: 1.1.0
+ katex: 0.16.40
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
micromark-factory-destination@2.0.1:
dependencies:
micromark-util-character: 2.1.1
@@ -7864,6 +8006,16 @@ snapshots:
regexp-tree@0.1.27: {}
+ rehype-katex@7.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/katex': 0.16.8
+ hast-util-from-html-isomorphic: 2.0.0
+ hast-util-to-text: 4.0.2
+ katex: 0.16.40
+ unist-util-visit-parents: 6.0.2
+ vfile: 6.0.3
+
rehype-parse@9.0.1:
dependencies:
'@types/hast': 3.0.4
@@ -7900,6 +8052,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ remark-math@6.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-math: 3.0.0
+ micromark-extension-math: 3.1.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 1047fac..f542573 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -1,6 +1,7 @@
---
import '@unocss/reset/tailwind.css'
import '../styles/global.css'
+import 'katex/dist/katex.min.css'
import { ClientRouter } from 'astro:transitions'
import { SITE } from '../config.mjs'
import { LANGUAGES, DEFAULT_LANG } from '../helper.mjs'
diff --git a/src/pages/articles/[slug].astro b/src/pages/articles/[slug].astro
index 94f719f..1f0fa26 100644
--- a/src/pages/articles/[slug].astro
+++ b/src/pages/articles/[slug].astro
@@ -1,7 +1,7 @@
---
import DocsLayout from '../../layouts/DocsLayout.astro'
import ShareButtons from '../../components/ShareButtons.astro'
-import { getCollection } from 'astro:content'
+import { getCollection, render } from 'astro:content'
import { SITE } from '../../config.mjs'
import { getLangFromUrl, t } from '../../i18n/index.ts'
@@ -22,6 +22,8 @@ if (!article || article.data.status !== 'Published') {
return Astro.redirect('/404')
}
+const { Content, headings } = await render(article)
+
const categorySlug = article.data.category.toLowerCase().replace(/\s+/g, '-')
const langPrefix = article.data.lang === 'en' ? '/en' : ''
const articleUrl = `${SITE.origin}${langPrefix}/articles/${article.data.slug}`
@@ -30,10 +32,7 @@ const ogImageUrl = `/og/${article.data.slug}.png`
const publishedDate = new Date(article.data.date)
const isoDate = publishedDate.toISOString()
-const sections = (article.body ?? '')
- .split('\n')
- .filter((line) => line.startsWith('## '))
- .map((line) => line.replace('## ', ''))
+const sections = headings.filter((h) => h.depth === 2).map((h) => h.text)
const jsonLdArticle = {
'@context': 'https://schema.org',
@@ -140,57 +139,8 @@ const onThisPageLabel = t(labels, 'article.onThisPage')
-
-
-
- {
- (article.body ?? '').split('\n\n').map((block) => {
- const trimmed = block.trim()
- if (!trimmed) return null
-
- if (trimmed.startsWith('## ')) {
- const heading = trimmed.replace('## ', '')
- return (
-
- {heading}
-
- )
- }
- if (trimmed.startsWith('- ')) {
- const items = trimmed.split('\n').filter((l) => l.startsWith('- '))
- return (
-
- {items.map((item) => (
- - {item.replace('- ', '')}
- ))}
-
- )
- }
- if (
- trimmed.includes('=') ||
- trimmed.includes('←') ||
- trimmed.includes('→') ||
- trimmed.includes('Σ') ||
- trimmed.includes('π')
- ) {
- return (
-
-
- {trimmed}
-
-
- )
- }
- return (
-
- {trimmed}
-
- )
- })
- }
+
+
diff --git a/src/pages/en/articles/[slug].astro b/src/pages/en/articles/[slug].astro
index df91de4..06f540c 100644
--- a/src/pages/en/articles/[slug].astro
+++ b/src/pages/en/articles/[slug].astro
@@ -1,7 +1,7 @@
---
import DocsLayout from '../../../layouts/DocsLayout.astro'
import ShareButtons from '../../../components/ShareButtons.astro'
-import { getCollection } from 'astro:content'
+import { getCollection, render } from 'astro:content'
import { SITE } from '../../../config.mjs'
import { t } from '../../../i18n/index.ts'
@@ -24,6 +24,8 @@ if (!article || article.data.status !== 'Published') {
return Astro.redirect('/en/404')
}
+const { Content, headings } = await render(article)
+
const categorySlug = article.data.category.toLowerCase().replace(/\s+/g, '-')
const articleUrl = `${SITE.origin}/en/articles/${article.data.slug}`
const ogImageUrl = `/og/${article.data.slug}.png`
@@ -31,10 +33,7 @@ const ogImageUrl = `/og/${article.data.slug}.png`
const publishedDate = new Date(article.data.date)
const isoDate = publishedDate.toISOString()
-const sections = (article.body ?? '')
- .split('\n')
- .filter((line) => line.startsWith('## '))
- .map((line) => line.replace('## ', ''))
+const sections = headings.filter((h) => h.depth === 2).map((h) => h.text)
const jsonLdArticle = {
'@context': 'https://schema.org',
@@ -139,57 +138,8 @@ const onThisPageLabel = t(labels, 'article.onThisPage')
-
-
-
- {
- (article.body ?? '').split('\n\n').map((block) => {
- const trimmed = block.trim()
- if (!trimmed) return null
-
- if (trimmed.startsWith('## ')) {
- const heading = trimmed.replace('## ', '')
- return (
-
- {heading}
-
- )
- }
- if (trimmed.startsWith('- ')) {
- const items = trimmed.split('\n').filter((l) => l.startsWith('- '))
- return (
-
- {items.map((item) => (
- - {item.replace('- ', '')}
- ))}
-
- )
- }
- if (
- trimmed.includes('=') ||
- trimmed.includes('←') ||
- trimmed.includes('→') ||
- trimmed.includes('Σ') ||
- trimmed.includes('π')
- ) {
- return (
-
-
- {trimmed}
-
-
- )
- }
- return (
-
- {trimmed}
-
- )
- })
- }
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
index 18fc14d..ba0e342 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -65,3 +65,15 @@ h6 {
-webkit-box-orient: vertical;
overflow: hidden;
}
+
+html.dark .astro-code,
+html.dark .astro-code span {
+ color: var(--shiki-dark) !important;
+ background-color: var(--shiki-dark-bg) !important;
+}
+
+html .astro-code,
+html .astro-code span {
+ color: var(--shiki-light) !important;
+ background-color: var(--shiki-light-bg) !important;
+}
diff --git a/uno.config.ts b/uno.config.ts
index 1b25c0d..4f43e19 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -1,8 +1,9 @@
-import { defineConfig, presetUno, presetIcons, presetWebFonts } from 'unocss'
+import { defineConfig, presetWind4, presetTypography, presetIcons, presetWebFonts } from 'unocss'
export default defineConfig({
presets: [
- presetUno(),
+ presetWind4(),
+ presetTypography(),
presetIcons({
scale: 1.2,
cdn: 'https://esm.sh/',