From bced24c623336c0afeacd4e14b7db3f98da876fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Demirel?= Date: Sat, 21 Mar 2026 13:02:01 +0300 Subject: [PATCH] feat: katex implemented --- astro.config.mjs | 16 +++ package.json | 4 + pnpm-lock.yaml | 161 +++++++++++++++++++++++++++++ src/layouts/BaseLayout.astro | 1 + src/pages/articles/[slug].astro | 62 ++--------- src/pages/en/articles/[slug].astro | 62 ++--------- src/styles/global.css | 12 +++ uno.config.ts | 5 +- 8 files changed, 209 insertions(+), 114 deletions(-) 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/',