Compare commits

...

2 Commits

Author SHA1 Message Date
eb0be5ebca 添加 Markdown 渲染
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-27 15:11:41 +08:00
b4298602bf 添加字体支持 2026-03-27 12:18:30 +08:00
9 changed files with 408 additions and 22 deletions

123
package-lock.json generated
View File

@ -9,12 +9,17 @@
"dependencies": {
"@astrojs/node": "^10.0.4",
"@astrojs/svelte": "^8.0.4",
"@traptitech/markdown-it-katex": "^3.6.0",
"astro": "^6.1.0",
"axios": "^1.13.6",
"katex": "^0.16.43",
"markdown-it": "^14.1.1",
"shiki": "^4.0.2",
"svelte": "^5.55.0",
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2",
"@typescript-eslint/parser": "^8.57.2",
"eslint": "^10.1.0",
"eslint-plugin-astro": "^1.6.0",
@ -1855,6 +1860,15 @@
"vite": "^6.3.0 || ^7.0.0"
}
},
"node_modules/@traptitech/markdown-it-katex": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@traptitech/markdown-it-katex/-/markdown-it-katex-3.6.0.tgz",
"integrity": "sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==",
"license": "MIT",
"dependencies": {
"katex": "^0.16.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
@ -1893,6 +1907,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@ -1902,6 +1934,13 @@
"@types/unist": "*"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@ -4109,6 +4148,31 @@
"dev": true,
"license": "MIT"
},
"node_modules/katex": {
"version": "0.16.43",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.43.tgz",
"integrity": "sha512-K7NL5JtGrFEglipOAjY4UYA69CnTuNmjArxeXF6+bw7h2OGySUPv6QWRjfb1gmutJ4Mw/qLeBqiROOEDULp4nA==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"license": "MIT",
"dependencies": {
"commander": "^8.3.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/katex/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -4133,6 +4197,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@ -4194,6 +4267,35 @@
"source-map-js": "^1.2.1"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/markdown-table": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
@ -4444,6 +4546,12 @@
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -5547,6 +5655,15 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -6380,6 +6497,12 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",

View File

@ -14,12 +14,17 @@
"dependencies": {
"@astrojs/node": "^10.0.4",
"@astrojs/svelte": "^8.0.4",
"@traptitech/markdown-it-katex": "^3.6.0",
"astro": "^6.1.0",
"axios": "^1.13.6",
"katex": "^0.16.43",
"markdown-it": "^14.1.1",
"shiki": "^4.0.2",
"svelte": "^5.55.0",
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2",
"@typescript-eslint/parser": "^8.57.2",
"eslint": "^10.1.0",
"eslint-plugin-astro": "^1.6.0",

138
src/assets/fonts.css Normal file
View File

@ -0,0 +1,138 @@
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-BoldItalic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBold.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBoldItalic.woff2') format('woff2');
font-weight: 800;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLight.woff2') format('woff2');
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLightItalic.woff2') format('woff2');
font-weight: 200;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-LightItalic.woff2') format('woff2');
font-weight: 300;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Medium.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-MediumItalic.woff2') format('woff2');
font-weight: 500;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBoldItalic.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Thin.woff2') format('woff2');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Maple Mono';
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ThinItalic.woff2') format('woff2');
font-weight: 100;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "ZLabsRoundPix 16px M CN";
src: url('https://cdn.passthem.top/fonts/ZLabsRoundPix_16px_M_CN.ttf.woff2') format('woff2');
font-display: swap;
}
:root {
--font-mono: 'Maple Mono NF CN', 'Maple Mono', monospace, var(--font-sans);
--font-sans: 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', sans-serif;
}

View File

@ -5,20 +5,32 @@
/* == 主题色 == */
:root {
color-scheme: light dark;
color-scheme: light dark;
--color-bg-0: light-dark(oklch(95% 0 0), oklch(30% 0.02 270));
--color-fg-0: light-dark(oklch(25% 0.02 270), oklch(90% 0.02 270));
--color-bg-0: light-dark(oklch(95% 0 0), oklch(30% 0.02 270));
--color-fg-0: light-dark(oklch(25% 0.02 270), oklch(90% 0.02 270));
--color-link: light-dark(oklch(40% 0.2 270), oklch(80% 0.2 270));
--color-link: light-dark(oklch(40% 0.2 270), oklch(80% 0.2 270));
}
/* == 页面设置 == */
html,
body {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
background-color: var(--color-bg-0);
color: var(--color-fg-0);
background-color: var(--color-bg-0);
color: var(--color-fg-0);
}
/* == 支持 Shiki 的高亮 == */
pre.shiki code {
font-family: var(--font-mono);
font-size: .9rem;
}
pre.shiki,
pre.shiki span {
color: light-dark(var(--shiki-light), var(--shiki-dark));
background-color: light-dark(var(--shiki-light-bg), var(--shiki-dark-bg));
}

View File

@ -1,5 +1,7 @@
---
import '../assets/style.css'
import '../assets/fonts.css'
import 'katex/dist/katex.min.css'
interface Props {
title?: string

View File

@ -18,20 +18,7 @@ const { title = '小帕的小窝' } = Astro.props
<style>
.main {
font-family:
'HarmonyOS Sans SC',
'Noto Sans SC',
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
font-family: var(--font-sans);
display: flex;
justify-content: center;
align-items: center;

View File

@ -0,0 +1,35 @@
import { legacyClient } from '../clients'
export const listBlogs = async ({
page = 1,
limit = 20,
}: {
page?: number
limit?: number
}) => {
const resp = await legacyClient.post('/v1/blog/list', { page, limit })
return resp.data.data as {
id: number
title: string
featured_image: null | {
image_url: string
}
}[]
}
export const getBlog: (
blog_id: number,
) => Promise<null | { title: string; content: string }> = async (
blog_id: number,
) => {
const resp = await legacyClient.get(`/v1/blog/${blog_id}`, {
validateStatus: (status) => status == 200 || status == 404,
})
if (resp.status == 404) {
return null
}
return resp.data as {
title: string
content: string
}
}

61
src/lib/markdown.ts Normal file
View File

@ -0,0 +1,61 @@
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import { createHighlighter, type Highlighter } from 'shiki'
let highlighter: Highlighter | null = null
/**
* Markdown 渲染函数。
*
* 为了让客户端和服务端表现相同,所以单独拿出来了一个 Markdown 渲染器。
*/
export async function renderMarkdown(content: string): Promise<string> {
if (highlighter === null) {
highlighter = await createHighlighter({
themes: ['one-light', 'one-dark-pro'],
/* 额...要我自己定义需要的所有语言吗 */
langs: [
'python',
'javascript',
'typescript',
'typst',
'markdown',
'json',
'toml',
'yaml',
'bash',
'c',
'c++',
'rust',
'go',
'zig',
'makefile',
'make',
'nim',
'nix',
'kdl',
'md',
'sh',
],
})
}
const md = new MarkdownIt({
html: true,
linkify: true,
highlight: (code, lang) => {
return highlighter!.codeToHtml(code, {
lang: lang || 'text',
themes: {
light: 'one-light',
dark: 'one-dark-pro',
},
defaultColor: false,
})
},
})
md.use(mdKatex)
return md.render(content)
}

View File

@ -0,0 +1,23 @@
---
import { getBlog } from '../../lib/apis/legacy/blog'
import BaseLayout from '../../layout/BaseLayout.astro'
import { renderMarkdown } from '../../lib/markdown'
export const prerender = false
const { blog_id } = Astro.params
const blog_id_num = parseInt(blog_id)
if (isNaN(blog_id_num) || blog_id_num < 0) {
return Astro.redirect('/404')
}
const blogData = await getBlog(blog_id_num)
if (blogData === null) {
return Astro.redirect('/404')
}
const blogRendered = await renderMarkdown(blogData.content)
---
<BaseLayout set:html={blogRendered} />