Commit 5b5075e

HPCesia <me@hpcesia.com>
2026-08-15 14:31:28
Tufted like sidenotes
1 parent e913667
Changed files (9)
src/contents/_lib/components/sidenote.typ
@@ -0,0 +1,35 @@
+#import "@preview/bullseye:0.1.0": match-target
+
+/// Side notes.
+///
+/// - content (content):
+/// - block (boolean): Whether to render as a block element. Default is `false`.
+///   - When `block: false`:
+///     - In HTML output with `block: false`, it just like footnotes in web but no numbering,
+///     wrapped in a span with class "sidenote".
+///     - In PDF output, it will be a footnotes without numbering.
+///   - When `block: true`:
+///     - In HTML output with `block: true`, it will be wrapped in a div with class "sidenote".
+///     - In PDF output, it will be a normal content wrapped in `#block()`.
+/// -> content
+#let sidenote(content, block: false) = context {
+  match-target(
+    html: {
+      if block {
+        html.div(
+          class: "sidenote",
+          content,
+        )
+      } else {
+        html.span(
+          class: "sidenote",
+          content,
+        )
+      }
+    },
+    paged: {
+      if block { std.block(content) } else { footnote(numbering: (..i) => "", content) }
+    },
+  )
+}
+}
src/contents/_lib/templates/footnote.typ
@@ -0,0 +1,36 @@
+#import "@preview/bullseye:0.1.0": show-target
+
+#let side-footnote-html(body) = {
+  show footnote: it => {
+    let number = counter(footnote).display(it.numbering)
+    let fn-id = "fn-" + number
+    let ref-id = "fnref-" + number
+
+    html.sup(
+      id: ref-id,
+      html.a(
+        href: "#" + fn-id,
+        number,
+      ),
+    )
+    html.span(
+      class: "sidenote",
+      id: fn-id,
+      html.sup(html.a(href: "#" + ref-id, number)) + [ ] + it.body,
+    )
+  }
+
+  body
+}
+
+#let footnote-template-paged(body) = {
+  // TODO: Headings style in PDF target
+
+  body
+}
+
+#let footnote-template(body) = {
+  show: show-target(paged: footnote-template-paged, html: side-footnote-html)
+
+  body
+}
src/contents/_lib/lib.typ
@@ -1,9 +1,11 @@
 #let post-template(body, lang: "en", region: none) = {
   set text(lang: lang, region: region)
 
+  import "./templates/footnote.typ": footnote-template
   import "./templates/heading.typ": heading-template
   import "./templates/raw.typ": raw-template
 
+  show: footnote-template
   show: heading-template
   show: raw-template
 
src/styles/article/index.scss
@@ -1,2 +1,13 @@
 @use "./math.scss";
 @use "./raw.scss";
+@use "./sidenote.scss";
+
+article {
+  max-width: 100%;
+  @media (min-width: 48rem) {
+    max-width: calc(3/4 * 100%);
+    > h1 {
+      width: calc(4/3 * 100%);
+    }
+  }
+}
src/styles/article/sidenote.scss
@@ -0,0 +1,18 @@
+article .sidenote {
+  position: relative;
+  display: block;
+
+  @media (min-width:48rem) {
+    width: calc(1/3 * 100%);
+    float: right;
+    clear: right;
+    margin-right: calc(-1/3 * 100% - 1rem);
+  }
+
+  & > :first-child {
+    margin-top: 0;
+  }
+  & > :last-child {
+    margin-bottom: 0;
+  }
+}
src/utils/plugins/clean-endnotes.ts
@@ -0,0 +1,16 @@
+import type { RehypePlugin } from "@astrojs/markdown-remark";
+import { visit } from "unist-util-visit";
+
+// Clean auto generated endnote, because we generate sidenotes instead.
+export const rehypeCleanEndnotes: RehypePlugin = () => (tree) => {
+  visit(tree, "element", (node, index, parent) => {
+    if (
+      node.tagName === "section" &&
+      node.properties.role === "doc-endnotes" &&
+      parent &&
+      typeof index === "number"
+    ) {
+      parent.children.splice(index, 1);
+    }
+  });
+};
astro.config.ts
@@ -4,6 +4,8 @@ import { typst } from "astro-typst";
 
 import { SITE_CONFIG } from "./src/config.ts";
 
+import { rehypeCleanEndnotes } from "./src/utils/plugins/clean-endnotes.ts";
+
 const protocol = SITE_CONFIG.website.split("://")[0];
 const site = SITE_CONFIG.website.split("://")[1].split("/")[0];
 const base = SITE_CONFIG.website.split(site).slice(1).join("/") || undefined;
@@ -20,6 +22,15 @@ export default defineConfig({
       target: "html",
     }),
   ],
+  markdown: {
+    // NOTE: `markdown.rehypePlugins` is deprecated in Astro v7.
+    // But astro-typst only read this entry for it's own hast process
+    // TODO: Replace with `markdown.processor = unified({rehypePlugins: [...]})`
+    // after astro-typst add Astro v7 style config support
+    rehypePlugins: [
+      rehypeCleanEndnotes,
+    ],
+  },
   fonts: [
     {
       provider: fontProviders.google(),
deno.lock
@@ -1,14 +1,16 @@
 {
   "version": "5",
   "specifiers": {
+    "npm:@astrojs/markdown-remark@^7.2.2": "7.2.2",
     "npm:@myriaddreamin/typst-ts-node-compiler@~0.8.0-rc3": "0.8.0-rc3",
     "npm:@myriaddreamin/typst-ts-renderer@~0.8.0-rc3": "0.8.0-rc3",
     "npm:@myriaddreamin/typst.ts@~0.8.0-rc3": "0.8.0-rc3_@myriaddreamin+typst-ts-renderer@0.8.0-rc3",
-    "npm:astro-typst@~0.12.4": "0.12.4_@myriaddreamin+typst-ts-node-compiler@0.8.0-rc3_@myriaddreamin+typst-ts-renderer@0.8.0-rc3_@myriaddreamin+typst.ts@0.8.0-rc3__@myriaddreamin+typst-ts-renderer@0.8.0-rc3_astro@7.2.0__@emnapi+core@2.0.0-alpha.3__@emnapi+runtime@2.0.0-alpha.3__sass@1.102.0_cheerio@1.2.0",
-    "npm:astro@^7.2.0": "7.2.0_@emnapi+core@2.0.0-alpha.3_@emnapi+runtime@2.0.0-alpha.3_sass@1.102.0",
+    "npm:astro-typst@~0.12.4": "0.12.4_@myriaddreamin+typst-ts-node-compiler@0.8.0-rc3_@myriaddreamin+typst-ts-renderer@0.8.0-rc3_@myriaddreamin+typst.ts@0.8.0-rc3__@myriaddreamin+typst-ts-renderer@0.8.0-rc3_astro@7.2.0__@astrojs+markdown-remark@7.2.2_@astrojs+markdown-remark@7.2.2",
+    "npm:astro@^7.2.0": "7.2.0_@astrojs+markdown-remark@7.2.2",
     "npm:sanitize.css@13": "13.0.0",
     "npm:sass@^1.102.0": "1.102.0",
-    "npm:typescript@^6.0.3": "6.0.3"
+    "npm:typescript@^6.0.3": "6.0.3",
+    "npm:unist-util-visit@^5.1.0": "5.1.0"
   },
   "npm": {
     "@astrojs/compiler-binding-darwin-arm64@0.3.2": {
@@ -91,6 +93,28 @@
         "unified"
       ]
     },
+    "@astrojs/markdown-remark@7.2.2": {
+      "integrity": "sha512-FGfmK84zSNcrsBd0dl1gXE9JvZYElp8EXQa2jpHVAxG4deGKAp43wspxFupjADJX7MSsMRHwYCnfT6EyVmgeFQ==",
+      "dependencies": [
+        "@astrojs/internal-helpers",
+        "@astrojs/prism",
+        "github-slugger",
+        "hast-util-from-html",
+        "hast-util-to-text",
+        "mdast-util-definitions",
+        "rehype-raw",
+        "rehype-stringify",
+        "remark-gfm",
+        "remark-parse",
+        "remark-rehype",
+        "remark-smartypants",
+        "unified",
+        "unist-util-remove-position",
+        "unist-util-visit",
+        "unist-util-visit-parents",
+        "vfile"
+      ]
+    },
     "@astrojs/markdown-satteri@0.3.5": {
       "integrity": "sha512-CvWVEFAbay7YO+i9SaqDJubipA5ckiVB89QWoMJ5XC0m5CtFg8JwZ7Kau6X9sYY7FZURH0w2l03ISH2jOS/RDQ==",
       "dependencies": [
@@ -976,11 +1000,14 @@
     "aria-query@5.3.2": {
       "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
     },
+    "array-iterate@2.0.1": {
+      "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="
+    },
     "astring@1.9.0": {
       "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
       "bin": true
     },
-    "astro-integration-kit@0.19.1_astro@7.2.0__@emnapi+core@2.0.0-alpha.3__@emnapi+runtime@2.0.0-alpha.3__sass@1.102.0": {
+    "astro-integration-kit@0.19.1_astro@7.2.0__@astrojs+markdown-remark@7.2.2_@astrojs+markdown-remark@7.2.2": {
       "integrity": "sha512-0MORMlwJXM3d2RlYoFMKpdBTEOqFEZ7rY5TnQssLsUTfaZgF4XkNJW0mNsDiOdM9LgDn9Px2dZtz8NbuUW75nw==",
       "dependencies": [
         "astro",
@@ -988,7 +1015,7 @@
       ],
       "deprecated": true
     },
-    "astro-typst@0.12.4_@myriaddreamin+typst-ts-node-compiler@0.8.0-rc3_@myriaddreamin+typst-ts-renderer@0.8.0-rc3_@myriaddreamin+typst.ts@0.8.0-rc3__@myriaddreamin+typst-ts-renderer@0.8.0-rc3_astro@7.2.0__@emnapi+core@2.0.0-alpha.3__@emnapi+runtime@2.0.0-alpha.3__sass@1.102.0_cheerio@1.2.0": {
+    "astro-typst@0.12.4_@myriaddreamin+typst-ts-node-compiler@0.8.0-rc3_@myriaddreamin+typst-ts-renderer@0.8.0-rc3_@myriaddreamin+typst.ts@0.8.0-rc3__@myriaddreamin+typst-ts-renderer@0.8.0-rc3_astro@7.2.0__@astrojs+markdown-remark@7.2.2_@astrojs+markdown-remark@7.2.2": {
       "integrity": "sha512-Mfc4U73wfGrtCAHGO9816He+zyi79PMgq0hYmLRNZNBiMW98amcyy+blZt5yubvV+uDF+dSe+mceeZn6FnKXOQ==",
       "dependencies": [
         "@myriaddreamin/typst-ts-node-compiler",
@@ -1003,11 +1030,12 @@
         "unified"
       ]
     },
-    "astro@7.2.0_@emnapi+core@2.0.0-alpha.3_@emnapi+runtime@2.0.0-alpha.3_sass@1.102.0": {
+    "astro@7.2.0_@astrojs+markdown-remark@7.2.2": {
       "integrity": "sha512-lLTYzx3fOvCmtwD3JVBLQcbORbIOW1/j0R+3IvJx/XKwMGrk7mFnF0BYSOeRiNw1qHUR5mdA6+hRnyvyDfqrWQ==",
       "dependencies": [
         "@astrojs/compiler-rs",
         "@astrojs/internal-helpers",
+        "@astrojs/markdown-remark",
         "@astrojs/markdown-satteri",
         "@astrojs/telemetry",
         "@capsizecss/unpack",
@@ -1062,6 +1090,9 @@
       "optionalDependencies": [
         "sharp"
       ],
+      "optionalPeers": [
+        "@astrojs/markdown-remark"
+      ],
       "bin": true
     },
     "axobject-query@4.1.0": {
@@ -1324,6 +1355,9 @@
       "scripts": true,
       "bin": true
     },
+    "escape-string-regexp@5.0.0": {
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
+    },
     "estree-util-attach-comments@3.0.0": {
       "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==",
       "dependencies": [
@@ -1479,12 +1513,36 @@
         "web-namespaces"
       ]
     },
+    "hast-util-is-element@3.0.0": {
+      "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+      "dependencies": [
+        "@types/hast"
+      ]
+    },
     "hast-util-parse-selector@4.0.0": {
       "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
       "dependencies": [
         "@types/hast"
       ]
     },
+    "hast-util-raw@9.1.0": {
+      "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
+      "dependencies": [
+        "@types/hast",
+        "@types/unist@3.0.3",
+        "@ungap/structured-clone",
+        "hast-util-from-parse5",
+        "hast-util-to-parse5",
+        "html-void-elements",
+        "mdast-util-to-hast",
+        "parse5",
+        "unist-util-position",
+        "unist-util-visit",
+        "vfile",
+        "web-namespaces",
+        "zwitch"
+      ]
+    },
     "hast-util-to-estree@3.1.3": {
       "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==",
       "dependencies": [
@@ -1542,6 +1600,27 @@
         "vfile-message"
       ]
     },
+    "hast-util-to-parse5@8.0.1": {
+      "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==",
+      "dependencies": [
+        "@types/hast",
+        "comma-separated-tokens",
+        "devlop",
+        "property-information",
+        "space-separated-tokens",
+        "web-namespaces",
+        "zwitch"
+      ]
+    },
+    "hast-util-to-text@4.0.2": {
+      "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+      "dependencies": [
+        "@types/hast",
+        "@types/unist@3.0.3",
+        "hast-util-is-element",
+        "unist-util-find-after"
+      ]
+    },
     "hast-util-whitespace@3.0.0": {
       "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
       "dependencies": [
@@ -1742,6 +1821,26 @@
     "markdown-extensions@2.0.0": {
       "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="
     },
+    "markdown-table@3.0.4": {
+      "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="
+    },
+    "mdast-util-definitions@6.0.0": {
+      "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==",
+      "dependencies": [
+        "@types/mdast",
+        "@types/unist@3.0.3",
+        "unist-util-visit"
+      ]
+    },
+    "mdast-util-find-and-replace@3.0.2": {
+      "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+      "dependencies": [
+        "@types/mdast",
+        "escape-string-regexp",
+        "unist-util-is",
+        "unist-util-visit-parents"
+      ]
+    },
     "mdast-util-from-markdown@2.0.3": {
       "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
       "dependencies": [
@@ -1759,6 +1858,65 @@
         "unist-util-stringify-position"
       ]
     },
+    "mdast-util-gfm-autolink-literal@2.0.1": {
+      "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+      "dependencies": [
+        "@types/mdast",
+        "ccount",
+        "devlop",
+        "mdast-util-find-and-replace",
+        "micromark-util-character"
+      ]
+    },
+    "mdast-util-gfm-footnote@2.1.0": {
+      "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+      "dependencies": [
+        "@types/mdast",
+        "devlop",
+        "mdast-util-from-markdown",
+        "mdast-util-to-markdown",
+        "micromark-util-normalize-identifier"
+      ]
+    },
+    "mdast-util-gfm-strikethrough@2.0.0": {
+      "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+      "dependencies": [
+        "@types/mdast",
+        "mdast-util-from-markdown",
+        "mdast-util-to-markdown"
+      ]
+    },
+    "mdast-util-gfm-table@2.0.0": {
+      "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+      "dependencies": [
+        "@types/mdast",
+        "devlop",
+        "markdown-table",
+        "mdast-util-from-markdown",
+        "mdast-util-to-markdown"
+      ]
+    },
+    "mdast-util-gfm-task-list-item@2.0.0": {
+      "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+      "dependencies": [
+        "@types/mdast",
+        "devlop",
+        "mdast-util-from-markdown",
+        "mdast-util-to-markdown"
+      ]
+    },
+    "mdast-util-gfm@3.1.0": {
+      "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+      "dependencies": [
+        "mdast-util-from-markdown",
+        "mdast-util-gfm-autolink-literal",
+        "mdast-util-gfm-footnote",
+        "mdast-util-gfm-strikethrough",
+        "mdast-util-gfm-table",
+        "mdast-util-gfm-task-list-item",
+        "mdast-util-to-markdown"
+      ]
+    },
     "mdast-util-mdx-expression@2.0.1": {
       "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
       "dependencies": [
@@ -1876,6 +2034,78 @@
         "micromark-util-types"
       ]
     },
+    "micromark-extension-gfm-autolink-literal@2.1.0": {
+      "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+      "dependencies": [
+        "micromark-util-character",
+        "micromark-util-sanitize-uri",
+        "micromark-util-symbol",
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm-footnote@2.1.0": {
+      "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+      "dependencies": [
+        "devlop",
+        "micromark-core-commonmark",
+        "micromark-factory-space",
+        "micromark-util-character",
+        "micromark-util-normalize-identifier",
+        "micromark-util-sanitize-uri",
+        "micromark-util-symbol",
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm-strikethrough@2.1.0": {
+      "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+      "dependencies": [
+        "devlop",
+        "micromark-util-chunked",
+        "micromark-util-classify-character",
+        "micromark-util-resolve-all",
+        "micromark-util-symbol",
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm-table@2.1.1": {
+      "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+      "dependencies": [
+        "devlop",
+        "micromark-factory-space",
+        "micromark-util-character",
+        "micromark-util-symbol",
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm-tagfilter@2.0.0": {
+      "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+      "dependencies": [
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm-task-list-item@2.1.0": {
+      "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+      "dependencies": [
+        "devlop",
+        "micromark-factory-space",
+        "micromark-util-character",
+        "micromark-util-symbol",
+        "micromark-util-types"
+      ]
+    },
+    "micromark-extension-gfm@3.0.0": {
+      "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+      "dependencies": [
+        "micromark-extension-gfm-autolink-literal",
+        "micromark-extension-gfm-footnote",
+        "micromark-extension-gfm-strikethrough",
+        "micromark-extension-gfm-table",
+        "micromark-extension-gfm-tagfilter",
+        "micromark-extension-gfm-task-list-item",
+        "micromark-util-combine-extensions",
+        "micromark-util-types"
+      ]
+    },
     "micromark-extension-mdx-expression@3.0.1": {
       "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==",
       "dependencies": [
@@ -2204,6 +2434,17 @@
         "is-hexadecimal"
       ]
     },
+    "parse-latin@7.0.0": {
+      "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==",
+      "dependencies": [
+        "@types/nlcst",
+        "@types/unist@3.0.3",
+        "nlcst-to-string",
+        "unist-util-modify-children",
+        "unist-util-visit-children",
+        "vfile"
+      ]
+    },
     "parse5-htmlparser2-tree-adapter@7.1.0": {
       "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
       "dependencies": [
@@ -2316,6 +2557,14 @@
         "regex-utilities"
       ]
     },
+    "rehype-raw@7.0.0": {
+      "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+      "dependencies": [
+        "@types/hast",
+        "hast-util-raw",
+        "vfile"
+      ]
+    },
     "rehype-recma@1.0.0": {
       "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==",
       "dependencies": [
@@ -2332,6 +2581,17 @@
         "unified"
       ]
     },
+    "remark-gfm@4.0.1": {
+      "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+      "dependencies": [
+        "@types/mdast",
+        "mdast-util-gfm",
+        "micromark-extension-gfm",
+        "remark-parse",
+        "remark-stringify",
+        "unified"
+      ]
+    },
     "remark-mdx@3.1.1": {
       "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==",
       "dependencies": [
@@ -2358,6 +2618,23 @@
         "vfile"
       ]
     },
+    "remark-smartypants@3.0.3": {
+      "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==",
+      "dependencies": [
+        "retext",
+        "retext-smartypants",
+        "unified",
+        "unist-util-visit"
+      ]
+    },
+    "remark-stringify@11.0.0": {
+      "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+      "dependencies": [
+        "@types/mdast",
+        "mdast-util-to-markdown",
+        "unified"
+      ]
+    },
     "resolve-pkg-maps@1.0.0": {
       "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="
     },
@@ -2371,6 +2648,14 @@
       ],
       "bin": true
     },
+    "retext-latin@4.0.0": {
+      "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==",
+      "dependencies": [
+        "@types/nlcst",
+        "parse-latin",
+        "unified"
+      ]
+    },
     "retext-smartypants@6.2.0": {
       "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==",
       "dependencies": [
@@ -2379,6 +2664,23 @@
         "unist-util-visit"
       ]
     },
+    "retext-stringify@4.0.0": {
+      "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==",
+      "dependencies": [
+        "@types/nlcst",
+        "nlcst-to-string",
+        "unified"
+      ]
+    },
+    "retext@9.0.0": {
+      "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==",
+      "dependencies": [
+        "@types/nlcst",
+        "retext-latin",
+        "retext-stringify",
+        "unified"
+      ]
+    },
     "rolldown@1.1.5": {
       "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
       "dependencies": [
@@ -2639,12 +2941,26 @@
         "ohash"
       ]
     },
+    "unist-util-find-after@5.0.0": {
+      "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+      "dependencies": [
+        "@types/unist@3.0.3",
+        "unist-util-is"
+      ]
+    },
     "unist-util-is@6.0.1": {
       "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
       "dependencies": [
         "@types/unist@3.0.3"
       ]
     },
+    "unist-util-modify-children@4.0.0": {
+      "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==",
+      "dependencies": [
+        "@types/unist@3.0.3",
+        "array-iterate"
+      ]
+    },
     "unist-util-position-from-estree@2.0.0": {
       "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==",
       "dependencies": [
@@ -2657,12 +2973,25 @@
         "@types/unist@3.0.3"
       ]
     },
+    "unist-util-remove-position@5.0.0": {
+      "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+      "dependencies": [
+        "@types/unist@3.0.3",
+        "unist-util-visit"
+      ]
+    },
     "unist-util-stringify-position@4.0.0": {
       "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
       "dependencies": [
         "@types/unist@3.0.3"
       ]
     },
+    "unist-util-visit-children@3.0.0": {
+      "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==",
+      "dependencies": [
+        "@types/unist@3.0.3"
+      ]
+    },
     "unist-util-visit-parents@6.0.2": {
       "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
       "dependencies": [
@@ -2773,6 +3102,7 @@
   "workspace": {
     "packageJson": {
       "dependencies": [
+        "npm:@astrojs/markdown-remark@^7.2.2",
         "npm:@myriaddreamin/typst-ts-node-compiler@~0.8.0-rc3",
         "npm:@myriaddreamin/typst-ts-renderer@~0.8.0-rc3",
         "npm:@myriaddreamin/typst.ts@~0.8.0-rc3",
@@ -2780,7 +3110,8 @@
         "npm:astro@^7.2.0",
         "npm:sanitize.css@13",
         "npm:sass@^1.102.0",
-        "npm:typescript@^6.0.3"
+        "npm:typescript@^6.0.3",
+        "npm:unist-util-visit@^5.1.0"
       ],
       "overrides": {
         "@myriaddreamin/typst-ts-node-compiler": "0.8.0-rc3",
package.json
@@ -12,13 +12,15 @@
     "astro": "astro"
   },
   "dependencies": {
+    "@astrojs/markdown-remark": "^7.2.2",
     "@myriaddreamin/typst-ts-node-compiler": "^0.8.0-rc3",
     "@myriaddreamin/typst-ts-renderer": "^0.8.0-rc3",
     "@myriaddreamin/typst.ts": "^0.8.0-rc3",
     "astro": "^7.2.0",
     "astro-typst": "^0.12.4",
     "sanitize.css": "^13.0.0",
-    "sass": "^1.102.0"
+    "sass": "^1.102.0",
+    "unist-util-visit": "^5.1.0"
   },
   "devDependencies": {
     "typescript": "^6.0.3"