Compare commits
11 Commits
5e53a213e6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
6c525bcf98
|
|||
|
7a3059f486
|
|||
|
1b8bd19370
|
|||
|
ca1cf10aa8
|
|||
|
880afe34ab
|
|||
|
c3973e779a
|
|||
|
d605131bda
|
|||
|
2a71d714e8
|
|||
|
a47a537a64
|
|||
|
e36492a692
|
|||
|
9c019351a3
|
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
|||||||
|
npx lint-staged
|
||||||
@ -12,6 +12,7 @@ const ICON2_URL =
|
|||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
output: 'server',
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: 'standalone',
|
mode: 'standalone',
|
||||||
}),
|
}),
|
||||||
@ -21,4 +22,11 @@ export default defineConfig({
|
|||||||
redirects: {
|
redirects: {
|
||||||
'/assets/icon-qq-BThBBmjV.jpg': ICON2_URL,
|
'/assets/icon-qq-BThBBmjV.jpg': ICON2_URL,
|
||||||
},
|
},
|
||||||
|
vite: {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['node:crypto', 'buffer', 'keyv'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
43
eslint.config.mjs
Normal file
43
eslint.config.mjs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import eslintPluginAstro from 'eslint-plugin-astro'
|
||||||
|
import tseslint from '@typescript-eslint/eslint-plugin'
|
||||||
|
import tsparser from '@typescript-eslint/parser'
|
||||||
|
import svelteParser from 'svelte-eslint-parser'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: ['dist/**', 'node_modules/**', '.astro/**'],
|
||||||
|
plugins: {
|
||||||
|
astro: eslintPluginAstro,
|
||||||
|
'@typescript-eslint': tseslint,
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...eslintPluginAstro.configs.recommended.rules,
|
||||||
|
...tseslint.configs.recommended.rules,
|
||||||
|
'no-console': 'warn',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: svelteParser,
|
||||||
|
parserOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
761
package-lock.json
generated
761
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@ -9,7 +9,20 @@
|
|||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"prepare": "husky"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,ts,jsx,tsx,svelte,vue}": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.{astro,css,md,mdx,json}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^10.0.4",
|
"@astrojs/node": "^10.0.4",
|
||||||
@ -29,6 +42,7 @@
|
|||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"katex": "^0.16.43",
|
"katex": "^0.16.43",
|
||||||
|
"keyv": "^5.6.0",
|
||||||
"markdown-it": "^14.1.1",
|
"markdown-it": "^14.1.1",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
@ -39,10 +53,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
||||||
"@typescript-eslint/parser": "^8.57.2",
|
"@typescript-eslint/parser": "^8.57.2",
|
||||||
"eslint": "^10.1.0",
|
"eslint": "^10.1.0",
|
||||||
"eslint-plugin-astro": "^1.6.0",
|
"eslint-plugin-astro": "^1.6.0",
|
||||||
|
"eslint-plugin-vue": "^10.8.0",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^16.4.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-astro": "^0.14.1"
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
"svelte-eslint-parser": "^1.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,12 @@ const config = {
|
|||||||
parser: 'astro',
|
parser: 'astro',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: '*.css',
|
||||||
|
options: {
|
||||||
|
tabWidth: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Bold.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Bold.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -8,7 +9,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-BoldItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-BoldItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -16,7 +18,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBold.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBold.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -24,7 +27,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBoldItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraBoldItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -32,7 +36,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLight.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLight.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -40,7 +45,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLightItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ExtraLightItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -48,7 +54,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Italic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Italic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -56,7 +63,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Light.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Light.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -64,7 +72,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-LightItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-LightItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -72,7 +81,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Medium.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Medium.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -80,7 +90,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-MediumItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-MediumItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -88,7 +99,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Regular.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Regular.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -96,7 +108,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBold.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBold.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -104,7 +117,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBoldItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-SemiBoldItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -112,7 +126,8 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Thin.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-Thin.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
@ -120,19 +135,23 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Maple Mono';
|
font-family: 'Maple Mono';
|
||||||
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ThinItalic.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/MapleMonoNormal-NF-CN-ThinItalic.woff2')
|
||||||
|
format('woff2');
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "ZLabsRoundPix 16px M CN";
|
font-family: 'ZLabsRoundPix 16px M CN';
|
||||||
src: url('https://cdn.passthem.top/fonts/ZLabsRoundPix_16px_M_CN.ttf.woff2') format('woff2');
|
src: url('https://cdn.passthem.top/fonts/ZLabsRoundPix_16px_M_CN.ttf.woff2')
|
||||||
|
format('woff2');
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-mono: 'Maple Mono NF CN', 'Maple Mono', monospace, var(--font-sans);
|
--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;
|
--font-sans:
|
||||||
|
'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC',
|
||||||
|
sans-serif;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
.prose {
|
.prose {
|
||||||
line-height: 1.6em;
|
& p,
|
||||||
|
& h1,
|
||||||
|
& h2,
|
||||||
|
& h3,
|
||||||
|
& h4,
|
||||||
|
& h5,
|
||||||
|
& h6 {
|
||||||
|
line-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
& p {
|
& p {
|
||||||
margin-block: .6em;
|
margin-block: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
& h1,
|
& h1,
|
||||||
@ -44,16 +52,16 @@
|
|||||||
background-color: var(--color-bg-1);
|
background-color: var(--color-bg-1);
|
||||||
color: var(--color-fg-1);
|
color: var(--color-fg-1);
|
||||||
|
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
padding-block: .5rem;
|
padding-block: 0.5rem;
|
||||||
padding-inline: 1.25rem;
|
padding-inline: 1.25rem;
|
||||||
margin-block: 1.25rem;
|
margin-block: 1.25rem;
|
||||||
|
|
||||||
border-inline-start: solid .25rem;
|
border-inline-start: solid 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
& pre.shiki {
|
& pre.shiki {
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
padding-block: 1rem;
|
padding-block: 1rem;
|
||||||
padding-inline: 1.25rem;
|
padding-inline: 1.25rem;
|
||||||
margin-block: 1.25rem;
|
margin-block: 1.25rem;
|
||||||
@ -69,7 +77,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
& img {
|
& img {
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
margin-block: 1.25rem;
|
margin-block: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& p > code {
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& pre,
|
||||||
|
& code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ body {
|
|||||||
/* == 配置默认字体 == */
|
/* == 配置默认字体 == */
|
||||||
code {
|
code {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: .9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* == 支持 Shiki 的高亮 == */
|
/* == 支持 Shiki 的高亮 == */
|
||||||
|
|||||||
@ -16,8 +16,8 @@ interface Props {
|
|||||||
|
|
||||||
const { link, title, featureImage, author, createdAt, updatedAt } = Astro.props
|
const { link, title, featureImage, author, createdAt, updatedAt } = Astro.props
|
||||||
|
|
||||||
const formatDate = (date: Date) => `${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
const formatDate = (date: Date) =>
|
||||||
|
`${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
||||||
---
|
---
|
||||||
|
|
||||||
<a class="card" href={link}>
|
<a class="card" href={link}>
|
||||||
@ -35,25 +35,38 @@ const formatDate = (date: Date) => `${date.getFullYear()} 年 ${date.getMonth()
|
|||||||
}
|
}
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h1 class="title">{title}</h1>
|
<h1 class="title">{title}</h1>
|
||||||
{author && <div class="author">
|
{
|
||||||
<Image
|
author && (
|
||||||
class="image"
|
<div class="author">
|
||||||
src={author.avatar ?? ""}
|
<Image
|
||||||
alt=`用户 ${author.name} 的头像`
|
class="image"
|
||||||
width={24}
|
src={author.avatar ?? ''}
|
||||||
height={24}
|
alt={`用户 ${author.name} 的头像`}
|
||||||
densities={[1.5, 2]}
|
width={24}
|
||||||
/>
|
height={24}
|
||||||
<span class="content">{author.name}</span>
|
densities={[1.5, 2]}
|
||||||
</div>}
|
/>
|
||||||
{createdAt && <div class="iconinfo">
|
<span class="content">{author.name}</span>
|
||||||
<Icon class="icon" name="material-symbols:calendar-add-on-rounded" />
|
</div>
|
||||||
<span class="content">{formatDate(createdAt)}</span>
|
)
|
||||||
</div>}
|
}
|
||||||
{updatedAt && (!createdAt || formatDate(createdAt) != formatDate(updatedAt)) && <div class="iconinfo">
|
{
|
||||||
<Icon class="icon" name="material-symbols:calendar-check-rounded" />
|
createdAt && (
|
||||||
<span class="content">{formatDate(updatedAt)}</span>
|
<div class="iconinfo">
|
||||||
</div>}
|
<Icon class="icon" name="material-symbols:calendar-add-on-rounded" />
|
||||||
|
<span class="content">{formatDate(createdAt)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
updatedAt &&
|
||||||
|
(!createdAt || formatDate(createdAt) != formatDate(updatedAt)) && (
|
||||||
|
<div class="iconinfo">
|
||||||
|
<Icon class="icon" name="material-symbols:calendar-check-rounded" />
|
||||||
|
<span class="content">{formatDate(updatedAt)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -105,41 +118,46 @@ const formatDate = (date: Date) => `${date.getFullYear()} 年 ${date.getMonth()
|
|||||||
|
|
||||||
& > .info {
|
& > .info {
|
||||||
& > h1 {
|
& > h1 {
|
||||||
margin-inline: 0.75rem;
|
margin-inline: 0.5rem;
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-block-start: 0.25rem;
|
margin-block-start: 0.25rem;
|
||||||
margin-block-end: 0.75rem;
|
margin-block-end: 0.75rem;
|
||||||
|
|
||||||
|
@media (width < 768px) {
|
||||||
|
margin-inline: 0.75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.author, &>.iconinfo {
|
& > .author,
|
||||||
|
& > .iconinfo {
|
||||||
margin-inline: 0.5rem;
|
margin-inline: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .5rem;
|
gap: 0.5rem;
|
||||||
margin-block: 0.1rem;
|
margin-block: 0.1rem;
|
||||||
|
|
||||||
&>.image {
|
& > .image {
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
width: var(--icon-size);
|
width: var(--icon-size);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.icon {
|
& > .icon {
|
||||||
width: var(--icon-size);
|
width: var(--icon-size);
|
||||||
height: var(--icon-size);
|
height: var(--icon-size);
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
opacity: .5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.content {
|
& > .content {
|
||||||
opacity: .5;
|
opacity: 0.5;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.author {
|
& > .author {
|
||||||
margin-block: 0.25rem;
|
margin-block: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,10 @@ export type ListBlogItemType = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BlogContentType = ListBlogItemType & {
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
export const listBlogs = async ({
|
export const listBlogs = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
@ -58,17 +62,30 @@ export const listBlogs = async ({
|
|||||||
|
|
||||||
export const getBlog: (
|
export const getBlog: (
|
||||||
blog_id: number,
|
blog_id: number,
|
||||||
) => Promise<null | { title: string; content: string }> = async (
|
) => Promise<null | BlogContentType> = async (blog_id: number) => {
|
||||||
blog_id: number,
|
|
||||||
) => {
|
|
||||||
const resp = await legacyClient.get(`/v1/blog/${blog_id}`, {
|
const resp = await legacyClient.get(`/v1/blog/${blog_id}`, {
|
||||||
validateStatus: (status) => status == 200 || status == 404,
|
validateStatus: (status) => status == 200 || status == 404,
|
||||||
})
|
})
|
||||||
if (resp.status == 404) {
|
if (resp.status == 404) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return resp.data as {
|
let _blog = resp.data
|
||||||
title: string
|
|
||||||
content: string
|
if (_blog.featured_image) {
|
||||||
|
_blog = {
|
||||||
|
..._blog,
|
||||||
|
featured_image: {
|
||||||
|
image_url:
|
||||||
|
'https://legacy.passthem.top' + _blog.featured_image.image_url,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_blog.author.avatar = {
|
||||||
|
image_url: 'https://legacy.passthem.top' + _blog.author.avatar.image_url,
|
||||||
|
}
|
||||||
|
_blog.created_at = new Date(_blog.created_at)
|
||||||
|
_blog.updated_at = new Date(_blog.updated_at)
|
||||||
|
|
||||||
|
return _blog
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/lib/markdoc-cache.ts
Normal file
16
src/lib/markdoc-cache.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { RenderableTreeNode } from '@markdoc/markdoc'
|
||||||
|
import KeyV from 'keyv'
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
|
||||||
|
const markdocVersion = '1'
|
||||||
|
|
||||||
|
export const markdocCache = new KeyV<RenderableTreeNode>({
|
||||||
|
namespace: `markdoc-cache-${markdocVersion}`,
|
||||||
|
ttl: 1000 * 60 * 60 * 24 * 7,
|
||||||
|
})
|
||||||
|
export const generateCacheKey = (content: string) => {
|
||||||
|
return createHash('sha256')
|
||||||
|
.update(content)
|
||||||
|
.update(markdocVersion)
|
||||||
|
.digest('hex')
|
||||||
|
}
|
||||||
@ -12,6 +12,15 @@ export const markdocConfig: Config = {
|
|||||||
highlightedHtml: { type: String },
|
highlightedHtml: { type: String },
|
||||||
},
|
},
|
||||||
transform(node, config) {
|
transform(node, config) {
|
||||||
|
/*
|
||||||
|
* 注意力!!!!!!!!!!!!!!
|
||||||
|
* 注意!!!!!!!!!!!!
|
||||||
|
*
|
||||||
|
* 如果改了这里的逻辑,记得更改 `markdoc-cache.ts` 里的版本号
|
||||||
|
* 以让对应的缓存能够被更新
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
const attributes = node.transformAttributes(config)
|
const attributes = node.transformAttributes(config)
|
||||||
|
|
||||||
const highlightedHtml = shikiRender(
|
const highlightedHtml = shikiRender(
|
||||||
@ -29,8 +38,28 @@ export const markdocConfig: Config = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toMarkdocTree = async (content: string) => {
|
const _toMarkdocTree = async (content: string) => {
|
||||||
const ast = Markdoc.parse(content)
|
const ast = Markdoc.parse(content)
|
||||||
await ensureShikiEngine()
|
await ensureShikiEngine()
|
||||||
return Markdoc.transform(ast, markdocConfig)
|
const tree = Markdoc.transform(ast, markdocConfig)
|
||||||
|
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toMarkdocTree = async (content: string) => {
|
||||||
|
if (!import.meta.env.SSR) {
|
||||||
|
return await _toMarkdocTree(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheLib = await import('./markdoc-cache.ts')
|
||||||
|
const key = cacheLib.generateCacheKey(content)
|
||||||
|
const cached = await cacheLib.markdocCache.get(key)
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
const tree = await _toMarkdocTree(content)
|
||||||
|
cacheLib.markdocCache.set(key, tree)
|
||||||
|
return tree
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import FullLayoutV1 from '../layout/FullLayoutV1.astro'
|
import FullLayoutV1 from '../layout/FullLayoutV1.astro'
|
||||||
import { listBlogs } from '../lib/apis/legacy/blog'
|
import { listBlogs, type ListBlogItemType } from '../lib/apis/legacy/blog'
|
||||||
import BlogCard from '../components/BlogCard.astro'
|
import BlogCard from '../components/BlogCard.astro'
|
||||||
import BlogHeaderImage from '../assets/blogs-header.webp'
|
import BlogHeaderImage from '../assets/blogs-header.webp'
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
@ -10,7 +10,13 @@ export const prerender = false
|
|||||||
const _page = parseInt(Astro.url.searchParams.get('page') || '1')
|
const _page = parseInt(Astro.url.searchParams.get('page') || '1')
|
||||||
const page = isNaN(_page) ? 1 : Math.max(1, _page)
|
const page = isNaN(_page) ? 1 : Math.max(1, _page)
|
||||||
|
|
||||||
const blogs = await listBlogs({ page, limit: 100 })
|
let blogs: ListBlogItemType[] = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
blogs = await listBlogs({ page, limit: 100 })
|
||||||
|
} catch {
|
||||||
|
return Astro.redirect('/500')
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<FullLayoutV1 withGap title="博客列表 - 小帕的小窝">
|
<FullLayoutV1 withGap title="博客列表 - 小帕的小窝">
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { getBlog } from '../../lib/apis/legacy/blog'
|
|||||||
import FullLayoutV1 from '../../layout/FullLayoutV1.astro'
|
import FullLayoutV1 from '../../layout/FullLayoutV1.astro'
|
||||||
import { MarkdocTreeRender } from '../../components/MarkdocRenderer.tsx'
|
import { MarkdocTreeRender } from '../../components/MarkdocRenderer.tsx'
|
||||||
import { toMarkdocTree } from '../../lib/markdoc'
|
import { toMarkdocTree } from '../../lib/markdoc'
|
||||||
|
import { Image } from 'astro:assets'
|
||||||
|
import { Icon } from 'astro-icon/components'
|
||||||
|
|
||||||
import Artalk from '../../components/Artalk.svelte'
|
import Artalk from '../../components/Artalk.svelte'
|
||||||
|
|
||||||
@ -15,21 +17,68 @@ if (isNaN(blog_id_num) || blog_id_num < 0) {
|
|||||||
return Astro.redirect('/404')
|
return Astro.redirect('/404')
|
||||||
}
|
}
|
||||||
|
|
||||||
const blogData = await getBlog(blog_id_num)
|
let blogData = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
blogData = await getBlog(blog_id_num)
|
||||||
|
} catch (e) {
|
||||||
|
return Astro.redirect('/500')
|
||||||
|
}
|
||||||
|
|
||||||
if (blogData === null) {
|
if (blogData === null) {
|
||||||
return Astro.redirect('/404')
|
return Astro.redirect('/404')
|
||||||
}
|
}
|
||||||
|
|
||||||
const tree = await toMarkdocTree(blogData.content)
|
const tree = await toMarkdocTree(blogData.content)
|
||||||
|
const formatDate = (date: Date) =>
|
||||||
|
`${date.getFullYear()} 年 ${date.getMonth() + 1} 月 ${date.getDate()} 日`
|
||||||
---
|
---
|
||||||
|
|
||||||
<FullLayoutV1 title={blogData.title} withGap>
|
<FullLayoutV1 title={blogData.title} withGap>
|
||||||
|
{
|
||||||
|
blogData.featured_image && (
|
||||||
|
<div class="featured-image">
|
||||||
|
<Image
|
||||||
|
class="image"
|
||||||
|
src={blogData.featured_image.image_url}
|
||||||
|
width={960}
|
||||||
|
height={540}
|
||||||
|
alt="博客的头图"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<main>
|
<main>
|
||||||
<h1>{blogData.title}</h1>
|
<h1>{blogData.title}</h1>
|
||||||
|
{
|
||||||
|
blogData.author && (
|
||||||
|
<div class="blog-info">
|
||||||
|
<a
|
||||||
|
class="author"
|
||||||
|
href={`https://legacy.passthem.top/user/${blogData.author.username}`}
|
||||||
|
>
|
||||||
|
<div class="avatar">
|
||||||
|
<Image
|
||||||
|
class="image"
|
||||||
|
src={blogData.author.avatar.image_url}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt={`用户 ${blogData.author.nickname} 的头像`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="username">{blogData.author.nickname}</div>
|
||||||
|
</a>
|
||||||
|
<span class="split">・</span>
|
||||||
|
<span>{formatDate(blogData.updated_at)} 更新</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
<article>
|
<article>
|
||||||
<MarkdocTreeRender tree={tree} client:load />
|
<MarkdocTreeRender tree={tree} client:load />
|
||||||
</article>
|
</article>
|
||||||
<h1>评论区</h1>
|
<h1 class="comment-title">
|
||||||
|
<Icon name="material-symbols:chat-rounded" /> 评论区
|
||||||
|
</h1>
|
||||||
<Artalk client:idle />
|
<Artalk client:idle />
|
||||||
</main>
|
</main>
|
||||||
</FullLayoutV1>
|
</FullLayoutV1>
|
||||||
@ -47,15 +96,85 @@ const tree = await toMarkdocTree(blogData.content)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.featured-image {
|
||||||
|
margin-inline: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 960px;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
margin-block-end: 2rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .image {
|
||||||
|
object-fit: cover;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to bottom, transparent, #00000044);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width < 768px) and (width >= 540px) {
|
||||||
|
aspect-ratio: 2.5 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= 768px) {
|
||||||
|
aspect-ratio: 4 / 1;
|
||||||
|
margin-block-end: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-fg-1);
|
||||||
|
|
||||||
|
& > .author {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
& > .avatar {
|
||||||
|
& > .image {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-inline-size: 840px;
|
max-inline-size: 840px;
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
padding-block-end: 2rem;
|
padding-block-end: 4rem;
|
||||||
|
padding-inline: 20px;
|
||||||
|
|
||||||
& > h1 {
|
& > h1 {
|
||||||
font-size: 2.4rem;
|
font-size: 2rem;
|
||||||
font-weight: 700;
|
font-weight: 900;
|
||||||
margin-block: 1.2rem;
|
margin-block-end: 1.2rem;
|
||||||
|
margin-block-start: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
&.comment-title {
|
||||||
|
margin-block-start: 3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -96,7 +96,12 @@ try {
|
|||||||
<Icon name="mdi:email" />
|
<Icon name="mdi:email" />
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-container">
|
<div class="contact-container">
|
||||||
<h1><div class="text">联系我<div class="animation-before"></div></div><div class="animation"></div></h1>
|
<h1>
|
||||||
|
<div class="text">联系我<div class="animation-before"></div></div><div
|
||||||
|
class="animation"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -188,22 +193,26 @@ try {
|
|||||||
[
|
[
|
||||||
'https://legacy.passthem.top/assets/ydt-DIeb2Djx.png',
|
'https://legacy.passthem.top/assets/ydt-DIeb2Djx.png',
|
||||||
'https://qm.qq.com/q/nDnHUy9KQo',
|
'https://qm.qq.com/q/nDnHUy9KQo',
|
||||||
'有顶天变电站 (QQ)'
|
'有顶天变电站 (QQ)',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'https://legacy.passthem.top/assets/lfxdxy-BogfTZvz.png',
|
'https://legacy.passthem.top/assets/lfxdxy-BogfTZvz.png',
|
||||||
'https://qm.qq.com/q/QOpCVZcvyS',
|
'https://qm.qq.com/q/QOpCVZcvyS',
|
||||||
'六方相的新月 (QQ)'
|
'六方相的新月 (QQ)',
|
||||||
],
|
],
|
||||||
].map(b => <a href={b[1]} class="friend">
|
].map((b) => (
|
||||||
<div class="avatar">
|
<a href={b[1]} class="friend">
|
||||||
<Image src={b[0]} width={60} height={60} alt=`网站「${b[2]}」的图标` />
|
<div class="avatar">
|
||||||
</div>
|
<Image
|
||||||
<div class="description">
|
src={b[0]}
|
||||||
{b[2]}
|
width={60}
|
||||||
</div>
|
height={60}
|
||||||
</a>
|
alt={`网站「${b[2]}」的图标`}
|
||||||
)
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="description">{b[2]}</div>
|
||||||
|
</a>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -567,7 +576,6 @@ try {
|
|||||||
& > .text {
|
& > .text {
|
||||||
background-color: var(--color-fg-0);
|
background-color: var(--color-fg-0);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .animation-before {
|
& .animation-before {
|
||||||
@ -603,8 +611,7 @@ try {
|
|||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: var(--bg-width) 100%;
|
background-size: var(--bg-width) 100%;
|
||||||
animation: bg-contact-roll infinite linear 2s;
|
animation: bg-contact-roll infinite linear 2s;
|
||||||
transform: skewX(var(--skew-angle))
|
transform: skewX(var(--skew-angle)) translateX(calc(var(--unit) - 1px));
|
||||||
translateX(calc(var(--unit) - 1px));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,7 +685,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 友链页 */
|
/* 友链页 */
|
||||||
.slide:has(>.friends) {
|
.slide:has(> .friends) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
@ -695,7 +702,7 @@ try {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-inline: 1rem;
|
margin-inline: 1rem;
|
||||||
/* background-color: rgb(from var(--color-bg-0) r g b / .3); */
|
/* background-color: rgb(from var(--color-bg-0) r g b / .3); */
|
||||||
background-color: rgb(from var(--color-bg-2) r g b / .5);
|
background-color: rgb(from var(--color-bg-2) r g b / 0.5);
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
|
|
||||||
@ -751,7 +758,8 @@ try {
|
|||||||
outline-offset: 3px;
|
outline-offset: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,45 @@ import {
|
|||||||
import { ensureShikiEngine } from '../lib/markdown'
|
import { ensureShikiEngine } from '../lib/markdown'
|
||||||
// import { toMarkdocTree } from '../lib/markdoc'
|
// import { toMarkdocTree } from '../lib/markdoc'
|
||||||
import Markdoc from '@markdoc/markdoc'
|
import Markdoc from '@markdoc/markdoc'
|
||||||
|
import KeyV from 'keyv'
|
||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
|
||||||
export const prerender = false
|
export const prerender = false
|
||||||
|
|
||||||
|
const renderCache = new KeyV<string>({
|
||||||
|
namespace: 'markdoc-rss',
|
||||||
|
})
|
||||||
|
|
||||||
|
const _render = async (content: string) => {
|
||||||
|
const key = 'html:' + createHash('sha256').update(content).digest('hex')
|
||||||
|
const cached = await renderCache.get(key)
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
const blogTree = Markdoc.transform(Markdoc.parse(content))
|
||||||
|
const html = Markdoc.renderers.html(blogTree)
|
||||||
|
|
||||||
|
await renderCache.set(key, html, 1000 * 60 * 60 * 24)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getBlog = async (blogId: number) => {
|
||||||
|
const key = `raw:blog-${blogId}`
|
||||||
|
const cached = await renderCache.get(key)
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = (await getBlog(blogId))?.content
|
||||||
|
if (content) {
|
||||||
|
await renderCache.set(key, content, 1000 * 60 * 60 * 12)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
export const GET = (async (context) => {
|
export const GET = (async (context) => {
|
||||||
let blogs: ListBlogItemType[] = []
|
let blogs: ListBlogItemType[] = []
|
||||||
let pid = 0
|
let pid = 0
|
||||||
@ -34,18 +70,13 @@ export const GET = (async (context) => {
|
|||||||
site,
|
site,
|
||||||
items: await Promise.all(
|
items: await Promise.all(
|
||||||
blogs.map(async (blog) => {
|
blogs.map(async (blog) => {
|
||||||
const blogContent =
|
const blogContent = (await _getBlog(blog.id)) || '博客内容暂不可用'
|
||||||
(await getBlog(blog.id))?.content || '博客内容暂不可用'
|
|
||||||
// const blogTree = await toMarkdocTree(blogContent)
|
|
||||||
const blogTree = Markdoc.transform(Markdoc.parse(blogContent))
|
|
||||||
const html = Markdoc.renderers.html(blogTree)
|
|
||||||
|
|
||||||
const rssItem: RSSFeedItem = {
|
const rssItem: RSSFeedItem = {
|
||||||
title: blog.title,
|
title: blog.title,
|
||||||
description: `一篇由 ${blog.author.nickname} 写的博客`,
|
description: `一篇由 ${blog.author.nickname} 写的博客`,
|
||||||
link: `${site}/blogs/${blog.id}`,
|
link: `${site}/blogs/${blog.id}`,
|
||||||
pubDate: new Date(blog.created_at),
|
pubDate: new Date(blog.created_at),
|
||||||
content: html,
|
content: await _render(blogContent),
|
||||||
author: blog.author.nickname,
|
author: blog.author.nickname,
|
||||||
enclosure: blog.featured_image
|
enclosure: blog.featured_image
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
".astro/types.d.ts",
|
"exclude": ["dist"],
|
||||||
"**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "react"
|
"jsxImportSource": "react"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user