第一版

This commit is contained in:
lsy 2025-03-08 18:16:42 +08:00
parent e3b7a06b39
commit 2500865a46
27 changed files with 1920 additions and 1134 deletions

View File

@ -7,15 +7,82 @@ import react from '@astrojs/react';
import node from '@astrojs/node';
import remarkEmoji from 'remark-emoji';
import rehypeExternalLinks from 'rehype-external-links';
import sitemap from '@astrojs/sitemap';
import fs from 'node:fs';
import path from 'node:path';
import { SITE_URL } from './src/consts';
function getArticleDate(articleId) {
try {
const mdPath = path.join(process.cwd(), 'src/content', articleId + '.md');
if (fs.existsSync(mdPath)) {
const content = fs.readFileSync(mdPath, 'utf-8');
const match = content.match(/date:\s*(\d{4}-\d{2}-\d{2})/);
if (match) {
return new Date(match[1]).toISOString();
}
}
} catch (error) {
console.error('Error reading article date:', error);
}
return null;
}
// https://astro.build/config
export default defineConfig({
site: SITE_URL, // 替换为您的实际网站 URL
output: 'server',
trailingSlash: 'ignore',
build: {
format: 'directory'
},
vite: {
plugins: [tailwindcss()]
},
integrations: [react()],
integrations: [
react(),
sitemap({
filter: (page) => !page.includes('/api/'),
serialize(item) {
if (!item) return undefined;
// 文章页面
if (item.url.includes('/articles/')) {
// 从 URL 中提取文章 ID
const articleId = item.url.replace(SITE_URL + '/articles/', '').replace(/\/$/, '');
const publishDate = getArticleDate(articleId);
if (publishDate) {
return {
...item,
priority: 0.8,
lastmod: publishDate
};
}
}
// 其他页面
else {
let priority = 0.7; // 默认优先级
// 首页最高优先级
if (item.url === SITE_URL + '/') {
priority = 1.0;
}
// 文章列表页次高优先级
else if (item.url === SITE_URL + '/articles/') {
priority = 0.9;
}
return {
...item,
priority
};
}
},
// 设置较小的条目限制,这样会自动分割成多个文件
entryLimit: 5
})
],
// 添加 Node.js 适配器配置
adapter: node({

560
package-lock.json generated
View File

@ -9,11 +9,12 @@
"version": "0.0.1",
"dependencies": {
"@astrojs/node": "^9.1.2",
"@astrojs/react": "^4.2.0",
"@astrojs/react": "^4.2.1",
"@astrojs/sitemap": "^3.2.1",
"@tailwindcss/vite": "^4.0.9",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"astro": "^5.3.0",
"astro": "^5.4.2",
"cheerio": "^1.0.0-rc.12",
"echarts": "^5.6.0",
"node-fetch": "^3.3.0",
@ -49,17 +50,18 @@
"license": "MIT"
},
"node_modules/@astrojs/internal-helpers": {
"version": "0.5.1",
"resolved": "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.5.1.tgz",
"integrity": "sha512-M7rAge1n2+aOSxNvKUFa0u/KFn0W+sZy7EW91KOSERotm2Ti8qs+1K0xx3zbOxtAVrmJb5/J98eohVvvEqtNkw==",
"version": "0.6.0",
"resolved": "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.6.0.tgz",
"integrity": "sha512-XgHIJDQaGlFnTr0sDp1PiJrtqsWzbHP2qkTU+JpQ8SnBewKP2IKOe/wqCkl0CyfyRXRu3TSWu4t/cpYMVfuBNA==",
"license": "MIT"
},
"node_modules/@astrojs/markdown-remark": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/@astrojs/markdown-remark/-/markdown-remark-6.1.0.tgz",
"integrity": "sha512-emZNNSTPGgPc3V399Cazpp5+snogjaF04ocOSQn9vy3Kw/eIC4vTQjXOrWDEoSEy+AwPDZX9bQ4wd3bxhpmGgQ==",
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/@astrojs/markdown-remark/-/markdown-remark-6.2.0.tgz",
"integrity": "sha512-LUDjgd9p1yG0qTFSocaj3GOLmZs8Hsw/pNtvqzvNY58Acebxvb/46vDO/e/wxYgsKgIfWS+p+ZI5SfOjoVrbCg==",
"license": "MIT",
"dependencies": {
"@astrojs/internal-helpers": "0.6.0",
"@astrojs/prism": "3.2.0",
"github-slugger": "^2.0.0",
"hast-util-from-html": "^2.0.3",
@ -73,7 +75,7 @@
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"remark-smartypants": "^3.0.2",
"shiki": "^1.29.1",
"shiki": "^1.29.2",
"smol-toml": "^1.3.1",
"unified": "^11.0.5",
"unist-util-remove-position": "^5.0.0",
@ -96,12 +98,6 @@
"astro": "^5.3.0"
}
},
"node_modules/@astrojs/node/node_modules/@astrojs/internal-helpers": {
"version": "0.6.0",
"resolved": "https://registry.npmmirror.com/@astrojs/internal-helpers/-/internal-helpers-0.6.0.tgz",
"integrity": "sha512-XgHIJDQaGlFnTr0sDp1PiJrtqsWzbHP2qkTU+JpQ8SnBewKP2IKOe/wqCkl0CyfyRXRu3TSWu4t/cpYMVfuBNA==",
"license": "MIT"
},
"node_modules/@astrojs/prism": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/@astrojs/prism/-/prism-3.2.0.tgz",
@ -115,14 +111,14 @@
}
},
"node_modules/@astrojs/react": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/@astrojs/react/-/react-4.2.0.tgz",
"integrity": "sha512-2OccnYFK+mLuy9GpJqPM3BQGvvemnXNeww+nBVYFuiH04L7YIdfg4Gq0LT7v/BraiuADV5uTl9VhTDL/ZQPAhw==",
"version": "4.2.1",
"resolved": "https://registry.npmmirror.com/@astrojs/react/-/react-4.2.1.tgz",
"integrity": "sha512-g0P6zxG7RPHNcbmMB15dJJ83+ApBVFBcgnf6BnMz/PVXM150Pa1vYKeuTcWhERqLNgmpI2uXuch5MecIhrUlqQ==",
"license": "MIT",
"dependencies": {
"@vitejs/plugin-react": "^4.3.4",
"ultrahtml": "^1.5.3",
"vite": "^6.0.9"
"vite": "^6.2.0"
},
"engines": {
"node": "^18.17.1 || ^20.3.0 || >=22.0.0"
@ -134,6 +130,17 @@
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@astrojs/sitemap": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/@astrojs/sitemap/-/sitemap-3.2.1.tgz",
"integrity": "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==",
"license": "MIT",
"dependencies": {
"sitemap": "^8.0.0",
"stream-replace-string": "^2.0.0",
"zod": "^3.23.8"
}
},
"node_modules/@astrojs/telemetry": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/@astrojs/telemetry/-/telemetry-3.2.0.tgz",
@ -444,9 +451,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
"cpu": [
"ppc64"
],
@ -460,9 +467,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
"cpu": [
"arm"
],
@ -476,9 +483,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
"cpu": [
"arm64"
],
@ -492,9 +499,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
"cpu": [
"x64"
],
@ -508,9 +515,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
"cpu": [
"arm64"
],
@ -524,9 +531,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
"cpu": [
"x64"
],
@ -540,9 +547,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
"cpu": [
"arm64"
],
@ -556,9 +563,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
"cpu": [
"x64"
],
@ -572,9 +579,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
"cpu": [
"arm"
],
@ -588,9 +595,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
"cpu": [
"arm64"
],
@ -604,9 +611,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
"cpu": [
"ia32"
],
@ -620,9 +627,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
"cpu": [
"loong64"
],
@ -636,9 +643,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
"cpu": [
"mips64el"
],
@ -652,9 +659,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
"cpu": [
"ppc64"
],
@ -668,9 +675,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
"cpu": [
"riscv64"
],
@ -684,9 +691,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
"cpu": [
"s390x"
],
@ -700,9 +707,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
"cpu": [
"x64"
],
@ -716,9 +723,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
"cpu": [
"arm64"
],
@ -732,9 +739,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
"cpu": [
"x64"
],
@ -748,9 +755,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
"cpu": [
"arm64"
],
@ -764,9 +771,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
"cpu": [
"x64"
],
@ -780,9 +787,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
"cpu": [
"x64"
],
@ -796,9 +803,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
"cpu": [
"arm64"
],
@ -812,9 +819,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
"cpu": [
"ia32"
],
@ -828,9 +835,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
"cpu": [
"x64"
],
@ -1252,41 +1259,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@octokit/app": {
"version": "14.1.0",
"resolved": "https://registry.npmmirror.com/@octokit/app/-/app-14.1.0.tgz",
@ -2452,6 +2424,15 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@types/sax": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/@types/sax/-/sax-1.2.7.tgz",
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz",
@ -2607,6 +2588,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
@ -2633,14 +2620,14 @@
}
},
"node_modules/astro": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/astro/-/astro-5.3.0.tgz",
"integrity": "sha512-e88l/Yk/6enR/ZDddLbqtM+oblBFk5mneNSmNesyVYGL/6Dj4UA67GPAZOk79VxT5dbLlclZSyyw/wlxN1aj3A==",
"version": "5.4.2",
"resolved": "https://registry.npmmirror.com/astro/-/astro-5.4.2.tgz",
"integrity": "sha512-9Z3fAniIRJaK/o43OroZA1wHUIU+qHiOR9ovlVT/2XQaN25QRXScIsKWlFp0G/zrx5OuuoJ+QnaoHHW061u26A==",
"license": "MIT",
"dependencies": {
"@astrojs/compiler": "^2.10.3",
"@astrojs/internal-helpers": "0.5.1",
"@astrojs/markdown-remark": "6.1.0",
"@astrojs/compiler": "^2.10.4",
"@astrojs/internal-helpers": "0.6.0",
"@astrojs/markdown-remark": "6.2.0",
"@astrojs/telemetry": "3.2.0",
"@oslojs/encoding": "^1.1.0",
"@rollup/pluginutils": "^5.1.4",
@ -2661,9 +2648,8 @@
"dlv": "^1.1.3",
"dset": "^3.1.4",
"es-module-lexer": "^1.6.0",
"esbuild": "^0.24.2",
"esbuild": "^0.25.0",
"estree-walker": "^3.0.3",
"fast-glob": "^3.3.3",
"flattie": "^1.1.1",
"github-slugger": "^2.0.0",
"html-escaper": "3.0.3",
@ -2672,30 +2658,31 @@
"kleur": "^4.1.5",
"magic-string": "^0.30.17",
"magicast": "^0.3.5",
"micromatch": "^4.0.8",
"mrmime": "^2.0.0",
"mrmime": "^2.0.1",
"neotraverse": "^0.6.18",
"p-limit": "^6.2.0",
"p-queue": "^8.1.0",
"picomatch": "^4.0.2",
"preferred-pm": "^4.1.1",
"prompts": "^2.4.2",
"rehype": "^13.0.2",
"semver": "^7.7.1",
"shiki": "^1.29.2",
"tinyexec": "^0.3.2",
"tsconfck": "^3.1.4",
"tinyglobby": "^0.2.12",
"tsconfck": "^3.1.5",
"ultrahtml": "^1.5.3",
"unist-util-visit": "^5.0.0",
"unstorage": "^1.14.4",
"unstorage": "^1.15.0",
"vfile": "^6.0.3",
"vite": "^6.0.11",
"vitefu": "^1.0.5",
"vite": "^6.2.0",
"vitefu": "^1.0.6",
"which-pm": "^3.0.1",
"xxhash-wasm": "^1.1.0",
"yargs-parser": "^21.1.1",
"yocto-spinner": "^0.2.0",
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.1",
"yocto-spinner": "^0.2.1",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.3",
"zod-to-ts": "^1.2.0"
},
"bin": {
@ -3195,9 +3182,9 @@
}
},
"node_modules/decode-named-character-reference": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
"integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
"license": "MIT",
"dependencies": {
"character-entities": "^2.0.0"
@ -3494,9 +3481,9 @@
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.24.2",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.2.tgz",
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
@ -3506,31 +3493,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.24.2",
"@esbuild/android-arm": "0.24.2",
"@esbuild/android-arm64": "0.24.2",
"@esbuild/android-x64": "0.24.2",
"@esbuild/darwin-arm64": "0.24.2",
"@esbuild/darwin-x64": "0.24.2",
"@esbuild/freebsd-arm64": "0.24.2",
"@esbuild/freebsd-x64": "0.24.2",
"@esbuild/linux-arm": "0.24.2",
"@esbuild/linux-arm64": "0.24.2",
"@esbuild/linux-ia32": "0.24.2",
"@esbuild/linux-loong64": "0.24.2",
"@esbuild/linux-mips64el": "0.24.2",
"@esbuild/linux-ppc64": "0.24.2",
"@esbuild/linux-riscv64": "0.24.2",
"@esbuild/linux-s390x": "0.24.2",
"@esbuild/linux-x64": "0.24.2",
"@esbuild/netbsd-arm64": "0.24.2",
"@esbuild/netbsd-x64": "0.24.2",
"@esbuild/openbsd-arm64": "0.24.2",
"@esbuild/openbsd-x64": "0.24.2",
"@esbuild/sunos-x64": "0.24.2",
"@esbuild/win32-arm64": "0.24.2",
"@esbuild/win32-ia32": "0.24.2",
"@esbuild/win32-x64": "0.24.2"
"@esbuild/aix-ppc64": "0.25.0",
"@esbuild/android-arm": "0.25.0",
"@esbuild/android-arm64": "0.25.0",
"@esbuild/android-x64": "0.25.0",
"@esbuild/darwin-arm64": "0.25.0",
"@esbuild/darwin-x64": "0.25.0",
"@esbuild/freebsd-arm64": "0.25.0",
"@esbuild/freebsd-x64": "0.25.0",
"@esbuild/linux-arm": "0.25.0",
"@esbuild/linux-arm64": "0.25.0",
"@esbuild/linux-ia32": "0.25.0",
"@esbuild/linux-loong64": "0.25.0",
"@esbuild/linux-mips64el": "0.25.0",
"@esbuild/linux-ppc64": "0.25.0",
"@esbuild/linux-riscv64": "0.25.0",
"@esbuild/linux-s390x": "0.25.0",
"@esbuild/linux-x64": "0.25.0",
"@esbuild/netbsd-arm64": "0.25.0",
"@esbuild/netbsd-x64": "0.25.0",
"@esbuild/openbsd-arm64": "0.25.0",
"@esbuild/openbsd-x64": "0.25.0",
"@esbuild/sunos-x64": "0.25.0",
"@esbuild/win32-arm64": "0.25.0",
"@esbuild/win32-ia32": "0.25.0",
"@esbuild/win32-x64": "0.25.0"
}
},
"node_modules/escalade": {
@ -3603,29 +3590,18 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fastq": {
"version": "1.19.0",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.0.tgz",
"integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fetch-blob": {
@ -3769,18 +3745,6 @@
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
"license": "ISC"
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
@ -4138,15 +4102,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -4156,18 +4111,6 @@
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz",
@ -4941,19 +4884,10 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/micromark": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.1.tgz",
"integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz",
"integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
"funding": [
{
"type": "GitHub Sponsors",
@ -4986,9 +4920,9 @@
}
},
"node_modules/micromark-core-commonmark": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz",
"integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==",
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
"integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
"funding": [
{
"type": "GitHub Sponsors",
@ -5460,9 +5394,9 @@
}
},
"node_modules/micromark-util-subtokenize": {
"version": "2.0.4",
"resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz",
"integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==",
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
"integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
"funding": [
{
"type": "GitHub Sponsors",
@ -6049,26 +5983,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/radix3": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/radix3/-/radix3-1.1.2.tgz",
@ -6400,16 +6314,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/rollup": {
"version": "4.34.8",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.34.8.tgz",
@ -6448,29 +6352,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -6497,6 +6378,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.25.0.tgz",
@ -6622,6 +6509,31 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/sitemap": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/sitemap/-/sitemap-8.0.0.tgz",
"integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==",
"license": "MIT",
"dependencies": {
"@types/node": "^17.0.5",
"@types/sax": "^1.2.1",
"arg": "^5.0.0",
"sax": "^1.2.4"
},
"bin": {
"sitemap": "dist/cli.js"
},
"engines": {
"node": ">=14.0.0",
"npm": ">=6.0.0"
}
},
"node_modules/sitemap/node_modules/@types/node": {
"version": "17.0.45",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.45.tgz",
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
"license": "MIT"
},
"node_modules/skin-tone": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/skin-tone/-/skin-tone-2.0.0.tgz",
@ -6681,6 +6593,12 @@
"node": ">= 0.8"
}
},
"node_modules/stream-replace-string": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
"integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
"license": "MIT"
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz",
@ -6757,6 +6675,22 @@
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.12",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.12.tgz",
"integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
"license": "MIT",
"dependencies": {
"fdir": "^6.4.3",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -7224,13 +7158,13 @@
}
},
"node_modules/vite": {
"version": "6.1.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.1.1.tgz",
"integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==",
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.2.1.tgz",
"integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.5.2",
"esbuild": "^0.25.0",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
},
"bin": {
@ -7295,9 +7229,9 @@
}
},
"node_modules/vitefu": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/vitefu/-/vitefu-1.0.5.tgz",
"integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==",
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/vitefu/-/vitefu-1.0.6.tgz",
"integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
"license": "MIT",
"workspaces": [
"tests/deps/*",
@ -7445,9 +7379,9 @@
}
},
"node_modules/yocto-spinner": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/yocto-spinner/-/yocto-spinner-0.2.0.tgz",
"integrity": "sha512-Qu6WAqNLGleB687CCGcmgHIo8l+J19MX/32UrSMfbf/4L8gLoxjpOYoiHT1asiWyqvjRZbgvOhLlvne6E5Tbdw==",
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/yocto-spinner/-/yocto-spinner-0.2.1.tgz",
"integrity": "sha512-lHHxjh0bXaLgdJy3cNnVb/F9myx3CkhrvSOEVTkaUgNMXnYFa2xYPVhtGnqhh3jErY2gParBOHallCbc7NrlZQ==",
"license": "MIT",
"dependencies": {
"yoctocolors": "^2.1.1"

View File

@ -10,11 +10,12 @@
},
"dependencies": {
"@astrojs/node": "^9.1.2",
"@astrojs/react": "^4.2.0",
"@astrojs/react": "^4.2.1",
"@astrojs/sitemap": "^3.2.1",
"@tailwindcss/vite": "^4.0.9",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"astro": "^5.3.0",
"astro": "^5.4.2",
"cheerio": "^1.0.0-rc.12",
"echarts": "^5.6.0",
"node-fetch": "^3.3.0",

BIN
public/images/national.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -13,7 +13,7 @@ const {
<div class="container mx-auto px-4 py-8">
{title && <h1 class="text-3xl font-bold mb-6 text-primary-900 dark:text-primary-100">{title}</h1>}
<div id="article-timeline" class="space-y-6">
<div id="article-timeline" class="relative space-y-8 before:absolute before:inset-0 before:ml-5 before:h-full before:w-0.5 before:-translate-x-px before:bg-gradient-to-b before:from-transparent before:via-primary-300 before:to-transparent md:before:mx-auto md:before:translate-x-0">
<!-- 内容将通过JS动态加载 -->
</div>
@ -78,51 +78,71 @@ const {
return;
}
const articlesHTML = articles.map(article => `
<div class="bg-white dark:bg-dark-card rounded-xl shadow-sm hover:shadow-md overflow-hidden border border-secondary-200 dark:border-dark-border transition-all duration-300">
<a href="/articles/${article.id}" class="block p-6 no-underline text-inherit">
<div class="flex flex-col md:flex-row md:items-center gap-4">
<div class="flex-1">
<h3 class="text-xl font-bold text-primary-800 dark:text-primary-200 mb-2 hover:text-primary-600 dark:hover:text-primary-400 transition-colors">${article.title}</h3>
<div class="flex items-center text-sm text-secondary-500 dark:text-secondary-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<time datetime="${article.date}">${new Date(article.date).toLocaleDateString('zh-CN')}</time>
const articlesHTML = articles.map((article, index) => {
const isEven = index % 2 === 0;
return `
<div class="relative group">
<!-- 时间线节点 -->
<div class="absolute left-5 -translate-x-1/2 md:left-1/2 top-6 flex h-3 w-3 items-center justify-center">
<div class="h-2 w-2 rounded-full bg-primary-500 dark:bg-primary-400 ring-2 ring-white dark:ring-gray-900 ring-offset-2 ring-offset-white dark:ring-offset-gray-900"></div>
</div>
<!-- 文章卡片 -->
<a href="/articles/${article.id}"
class="group/card ml-10 md:ml-0 ${isEven ? 'md:mr-[50%] md:pr-8' : 'md:ml-[50%] md:pl-8'} block">
<article class="relative flex flex-col gap-4 rounded-xl bg-white dark:bg-gray-800 p-6 shadow-lg hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border border-gray-200 dark:border-gray-700">
<!-- 日期标签 -->
<time datetime="${article.date}"
class="absolute top-4 right-4 text-xs font-medium text-secondary-500 dark:text-secondary-400">
${new Date(article.date).toLocaleDateString('zh-CN', {year: 'numeric', month: 'long', day: 'numeric'})}
</time>
<!-- 文章标题 -->
<h3 class="pr-16 text-xl font-bold text-gray-900 dark:text-gray-100 group-hover/card:text-primary-600 dark:group-hover/card:text-primary-400 transition-colors">
${article.title}
</h3>
<!-- 文章摘要 -->
${article.summary ? `
<p class="text-secondary-600 dark:text-secondary-300 line-clamp-2">
${article.summary}
</p>
` : ''}
<!-- 文章元信息 -->
<div class="flex flex-wrap items-center gap-4 text-sm">
${article.section ? `
<span class="mx-2 text-secondary-300 dark:text-secondary-600">•</span>
<span class="flex items-center">
<span class="flex items-center text-secondary-500 dark:text-secondary-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
${article.section}
</span>
` : ''}
${article.tags && article.tags.length > 0 ? `
<div class="flex flex-wrap gap-2">
${article.tags.map(tag => `
<span class="text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 py-1 px-2 rounded-full">
#${tag}
</span>
`).join('')}
</div>
` : ''}
</div>
${article.summary ? `<p class="text-secondary-600 dark:text-secondary-300 line-clamp-2 mb-3">${article.summary}</p>` : ''}
${article.tags && article.tags.length > 0 ? `
<div class="flex flex-wrap gap-2">
${article.tags.map(tag => `
<span class="text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 py-1 px-2 rounded-full">
#${tag}
</span>
`).join('')}
<!-- 阅读更多指示器 -->
<div class="flex items-center text-sm text-primary-600 dark:text-primary-400 group-hover/card:translate-x-1 transition-transform">
<span class="font-medium">阅读全文</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</div>
` : ''}
</div>
<div class="text-primary-500 dark:text-primary-400 hidden md:block">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
</div>
</a>
</div>
`).join('');
</article>
</a>
</div>
`;
}).join('');
if (append) {
articleTimeline.innerHTML += articlesHTML;
@ -134,7 +154,11 @@ const {
function showLoading(show) {
const loading = document.getElementById('loading');
if (loading) {
loading.classList.toggle('hidden', !show);
if (show) {
loading.classList.remove('hidden');
} else {
loading.classList.add('hidden');
}
}
}
@ -146,11 +170,11 @@ const {
}
function setupInfiniteScroll() {
// 直接使用滚动事件
window.addEventListener('scroll', handleScroll);
setTimeout(() => {
handleScroll();
}, 500);
// 初始检查一次,以防内容不足一屏
setTimeout(handleScroll, 500);
}
function handleScroll() {
@ -162,6 +186,7 @@ const {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// 当滚动到距离底部300px时加载更多
if (scrollY + windowHeight >= documentHeight - 300) {
fetchArticles(currentPage + 1, true);
}
@ -170,8 +195,6 @@ const {
document.addEventListener('DOMContentLoaded', () => {
fetchArticles(1, false).then(() => {
setupInfiniteScroll();
}).catch(err => {
// 错误已在fetchArticles中处理
});
});
</script>

View File

@ -0,0 +1,51 @@
import React, { useState, useEffect } from 'react';
interface CountdownProps {
targetDate: string; // 目标日期,格式:'YYYY-MM-DD'
}
export const Countdown: React.FC<CountdownProps> = ({ targetDate }) => {
const [timeLeft, setTimeLeft] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0
});
useEffect(() => {
const timer = setInterval(() => {
const now = new Date().getTime();
const target = new Date(targetDate).getTime();
const difference = target - now;
if (difference > 0) {
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
setTimeLeft({ days, hours, minutes, seconds });
}
}, 1000);
return () => clearInterval(timer);
}, [targetDate]);
const TimeBox = ({ value, label }: { value: number; label: string }) => (
<div className="text-center px-4">
<div className="text-4xl font-light">
{value.toString().padStart(2, '0')}
</div>
<div className="text-sm mt-1 text-gray-500">{label}</div>
</div>
);
return (
<div className="flex items-center justify-center">
<TimeBox value={timeLeft.days} label="天" />
<TimeBox value={timeLeft.hours} label="时" />
<TimeBox value={timeLeft.minutes} label="分" />
<TimeBox value={timeLeft.seconds} label="秒" />
</div>
);
};

View File

@ -55,11 +55,6 @@ const DoubanCollection: React.FC<DoubanCollectionProps> = ({ type }) => {
fetchData();
}, [type]);
const updatePage = (page: number) => {
const start = (page - 1) * 15;
fetchData(start);
};
const handlePageChange = (page: number) => {
const start = (page - 1) * 15;

View File

@ -0,0 +1,49 @@
---
interface Props {
icp?: string;
psbIcp?: string;
psbIcpUrl?: string;
}
const {
icp = "",
psbIcp = "",
psbIcpUrl = "http://www.beian.gov.cn/portal/registerSystemInfo",
} = Astro.props;
const currentYear = new Date().getFullYear();
---
<footer class="w-full py-6 px-4 bg-gray-50 dark:bg-dark-bg border-t border-gray-200 dark:border-gray-800 mt-auto">
<div class="max-w-5xl mx-auto flex flex-col items-center justify-center space-y-4">
<div class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400">
{icp && (
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
>
{icp}
</a>
)}
{psbIcp && (
<a
href={psbIcpUrl}
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
>
<img src="/images/national.png" alt="公安备案" class="h-4 mr-1" />
{psbIcp}
</a>
)}
</div>
<div class="text-sm text-gray-500 dark:text-gray-500 font-light">
© {currentYear} New Echoes. All rights reserved.
</div>
</div>
</footer>

View File

@ -187,14 +187,6 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
700: 1
};
if (loading && projects.length === 0) {
return <div className="flex justify-center p-8">...</div>;
}
if (error) {
return <div className="text-red-500 p-4">: {error}</div>;
}
const getPlatformName = (platform: GitPlatform) => {
return GIT_PLATFORM_CONFIG.platformNames[platform];
};
@ -203,7 +195,7 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
const displayTitle = title || `${getPlatformName(platform)} 项目`;
return (
<div className="git-project-collection">
<div className="git-project-collection max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold mb-6 text-primary-700">
{displayTitle}
{effectiveUsername && <span className="ml-2 text-secondary-500">(@{effectiveUsername})</span>}
@ -227,13 +219,13 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
columnClassName="pl-4 bg-clip-padding"
>
{projects.map((project, index) => (
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-lg border border-secondary-200 bg-white shadow-sm transition-shadow hover:shadow-md">
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-4">
<div className="flex items-center mb-2">
<div className="mr-2 text-secondary-600">
<div key={`${project.platform}-${project.owner}-${project.name}-${index}`} className="mb-4 overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<a href={project.url} target="_blank" rel="noopener noreferrer" className="block p-5">
<div className="flex items-start">
<div className="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
{getPlatformIcon(project.platform as GitPlatform)}
</div>
<div className="flex-1 truncate">
<div className="ml-3 flex-1">
<div className="flex items-center">
<img
src={project.avatarUrl}
@ -245,44 +237,48 @@ const GitProjectCollection: React.FC<GitProjectCollectionProps> = ({
target.src = 'https://via.placeholder.com/40';
}}
/>
<span className="text-sm text-secondary-600 truncate">{project.owner}</span>
<span className="text-sm text-gray-600 dark:text-gray-400 truncate">{project.owner}</span>
</div>
</div>
</div>
<h3 className="text-lg font-semibold mb-1 text-primary-800 line-clamp-1">{project.name}</h3>
<h3 className="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-1 mt-2">{project.name}</h3>
{project.description && (
<p className="text-secondary-700 text-sm mb-3 line-clamp-2">{project.description}</p>
)}
<div className="flex flex-wrap items-center text-xs mt-2">
{project.language && (
<div className="flex items-center mr-4 mb-1">
<span className={`w-3 h-3 rounded-full mr-1 ${getLanguageColor(project.language)}`}></span>
<span className="text-secondary-600">{project.language}</span>
<div className="h-12 mb-3">
{project.description ? (
<p className="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">{project.description}</p>
) : (
<p className="text-sm text-gray-400 dark:text-gray-500 italic"></p>
)}
</div>
)}
<div className="flex items-center mr-4 mb-1">
<svg className="w-4 h-4 mr-1 text-secondary-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 6.477V16h2a1 1 0 110 2H7a1 1 0 110-2h2V6.477L6.237 7.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 4.323V3a1 1 0 011-1zm-5 8.274l-.818 2.552c.25.112.526.174.818.174.292 0 .569-.062.818-.174L5 10.274zm10 0l-.818 2.552c.25.112.526.174.818.174.292 0 .569-.062.818-.174L15 10.274z" clipRule="evenodd" />
</svg>
<span className="text-secondary-600">{project.stars}</span>
</div>
<div className="flex flex-wrap items-center text-xs gap-4">
{project.language && (
<div className="flex items-center">
<span className={`w-3 h-3 rounded-full mr-1.5 ${getLanguageColor(project.language)}`}></span>
<span className="text-gray-600 dark:text-gray-400">{project.language}</span>
</div>
)}
<div className="flex items-center mr-4 mb-1">
<svg className="w-4 h-4 mr-1 text-secondary-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
<span className="text-secondary-600">{project.forks}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
<span className="text-gray-600 dark:text-gray-400">{project.stars}</span>
</div>
<div className="flex items-center mb-1 ml-auto">
<svg className="w-4 h-4 mr-1 text-secondary-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
</svg>
<span className="text-secondary-500">{new Date(project.updatedAt).toLocaleDateString('zh-CN')}</span>
<div className="flex items-center">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
</svg>
<span className="text-gray-600 dark:text-gray-400">{project.forks}</span>
</div>
<div className="flex items-center ml-auto">
<svg className="w-4 h-4 mr-1.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-gray-500 dark:text-gray-400">{new Date(project.updatedAt).toLocaleDateString('zh-CN')}</span>
</div>
</div>
</div>
</div>
</a>

View File

@ -1,16 +1,60 @@
---
import "@/styles/global.css";
import Header from "@/components/header.astro";
import Footer from "@/components/Footer.astro";
import { ICP, PSB_ICP, PSB_ICP_URL, SITE_NAME } from "@/consts";
// 定义Props接口
interface Props {
title?: string;
description?: string;
date?: Date;
author?: string;
tags?: string[];
image?: string;
}
// 获取完整的 URL
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
// 从props中获取页面特定信息
const { title = SITE_NAME, description, date, author, tags, image } = Astro.props;
---
<!doctype html>
<html lang="en" class="m-0 w-full h-full">
<html lang="zh-CN" class="m-0 w-full h-full">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>New Echoes</title>
<!-- 基本元数据 -->
<title>{title}</title>
<meta name="description" content={description || `${SITE_NAME} - 个人博客`} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="article" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description || `${SITE_NAME} - 个人博客`} />
{image && <meta property="og:image" content={new URL(image, Astro.site)} />}
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description || `${SITE_NAME} - 个人博客`} />
{image && <meta property="twitter:image" content={new URL(image, Astro.site)} />}
<!-- 文章特定元数据 -->
{date && <meta property="article:published_time" content={date.toISOString()} />}
{author && <meta name="author" content={author} />}
{tags && tags.map(tag => (
<meta property="article:tag" content={tag} />
))}
<script is:inline>
// 立即执行主题初始化
const theme = (() => {
@ -25,10 +69,11 @@ import Header from "@/components/header.astro";
document.documentElement.dataset.theme = theme;
</script>
</head>
<body class="m-0 w-full h-full bg-white dark:bg-dark-bg transition-colors duration-300">
<body class="m-0 w-full h-full bg-gray-50 dark:bg-dark-bg transition-colors duration-300 flex flex-col min-h-screen">
<Header />
<main class="pt-16">
<main class="pt-16 flex-grow">
<slot />
</main>
<Footer icp={ICP} psbIcp={PSB_ICP} psbIcpUrl={PSB_ICP_URL} />
</body>
</html>

View File

@ -7,7 +7,7 @@ interface Props {
const { type, title } = Astro.props;
---
<div class="container mx-auto px-4 py-8">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl font-bold mb-6">{title}</h1>
<div id="media-list" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
@ -102,12 +102,9 @@ const { type, title } = Astro.props;
const loading = document.getElementById('loading');
if (loading) {
if (show) {
loading.innerHTML = `
<div class="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
<p class="mt-2 text-gray-600">加载更多...</p>
`;
loading.classList.remove('hidden');
} else {
loading.innerHTML = `<div class="h-8"></div>`; // 保持元素可见但内容为空
loading.classList.add('hidden');
}
}
}
@ -124,9 +121,7 @@ const { type, title } = Astro.props;
window.addEventListener('scroll', handleScroll);
// 初始检查一次,以防内容不足一屏
setTimeout(() => {
handleScroll();
}, 500);
setTimeout(handleScroll, 500);
}
function handleScroll() {

View File

@ -1,25 +1,31 @@
import { useEffect, useState } from 'react';
export function ThemeToggle({ height = 20, width = 20, fill = "currentColor" }) {
// document.documentElement.dataset.theme
const [theme, setTheme] = useState(() => {
if (typeof document !== 'undefined') {
return document.documentElement.dataset.theme || 'light';
}
return 'light';
});
export function ThemeToggle({ height = 16, width = 16, fill = "currentColor" }) {
// 使null
const [theme, setTheme] = useState(null);
const [mounted, setMounted] = useState(false);
//
useEffect(() => {
setMounted(true);
// localStorage document.documentElement.dataset.theme
const savedTheme = localStorage.getItem('theme');
const rootTheme = document.documentElement.dataset.theme;
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
// 使
const initialTheme = savedTheme || rootTheme || systemTheme;
setTheme(initialTheme);
//
document.documentElement.dataset.theme = initialTheme;
}, []);
useEffect(() => {
if (!mounted) return;
if (!mounted || theme === null) return;
// DOM localStorage
const root = document.documentElement;
root.dataset.theme = theme;
document.documentElement.dataset.theme = theme;
if (theme === getSystemTheme()) {
localStorage.removeItem('theme');
@ -36,25 +42,39 @@ export function ThemeToggle({ height = 20, width = 20, fill = "currentColor" })
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
}
//
if (!mounted || theme === null) {
return (
<div
className="inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 mt-1"
>
<span className="sr-only">加载主题切换按钮...</span>
</div>
);
}
return (
<div
className="inline-flex items-center justify-center cursor-pointer"
style={{ height: `${height}px`, width: `${width}px` }}
className="inline-flex items-center justify-center h-8 w-8 cursor-pointer rounded-md transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700/50 text-secondary-600 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 mt-1"
onClick={toggleTheme}
role="button"
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
>
{theme === 'dark' ? (
<svg
style={{ height: `${height * 2/3}px`, width: `${width * 2/3}px` }}
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
>
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
) : (
<svg
style={{ height: `${height * 2/3}px`, width: `${width * 2/3}px` }}
style={{ height: `${height}px`, width: `${width}px` }}
fill={fill}
viewBox="0 0 16 16"
className="transition-transform duration-200 hover:scale-110"
>
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>

View File

@ -11,13 +11,14 @@ const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : cu
// 定义导航链接
---
<header class="fixed w-full top-0 z-50 bg-white/80 dark:bg-dark-surface/80 backdrop-blur-md border-b border-secondary-200 dark:border-dark-border">
<nav>
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<header class="fixed w-full top-0 z-50 transition-all duration-300" id="main-header">
<div class="absolute inset-0 bg-gray-50/95 dark:bg-dark-bg/95 transition-all duration-300" id="header-bg"></div>
<nav class="relative">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<!-- Logo 部分 -->
<div class="flex items-center">
<a href="/" class="text-lg font-semibold text-primary-900 dark:text-primary-100 hover:text-primary-600 dark:hover:text-primary-400">
<a href="/" class="text-xl md:text-2xl font-bold tracking-tight transition-all duration-300 ease-in-out bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent hover:from-primary-500 hover:to-primary-300 dark:from-primary-400 dark:to-primary-200 dark:hover:from-primary-300 dark:hover:to-primary-100">
{SITE_NAME}
</a>
</div>
@ -45,14 +46,143 @@ const normalizedPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : cu
<button
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-secondary-400 dark:text-secondary-500 hover:text-secondary-500 dark:hover:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-dark-card focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
id="mobile-menu-button"
aria-expanded="false"
>
<span class="sr-only">打开菜单</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg class="h-6 w-6 block" id="menu-open-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg class="h-6 w-6 hidden" id="menu-close-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- 移动端菜单 -->
<div class="hidden md:hidden fixed inset-x-0 top-16 z-40" id="mobile-menu">
<div id="mobile-menu-bg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
<div class="grid gap-1">
{NAV_LINKS.map(link => (
<a
href={link.href}
class:list={[
'flex items-center px-3 py-3 rounded-lg text-base font-medium transition-all duration-200 ease-in-out',
normalizedPath === (link.href === '/' ? '' : link.href)
? 'text-white bg-primary-600 dark:bg-primary-500 shadow-sm'
: 'text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800/70'
]}
>
{link.text}
</a>
))}
<div class="mt-2 pt-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">切换主题</span>
<ThemeToggle client:load />
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<style>
#header-bg {
opacity: 1;
backdrop-filter: blur(0);
transition: all 0.5s ease;
}
#header-bg.scrolled {
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.04),
0 2px 4px rgba(0, 0, 0, 0.04),
0 4px 8px rgba(0, 0, 0, 0.04);
}
:global([data-theme="dark"]) #header-bg.scrolled {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
/* 移动端菜单样式 */
#mobile-menu {
/* 移除过渡效果,确保菜单内容立即显示 */
opacity: 1;
transform: translateY(0);
max-height: calc(100vh - 4rem);
overflow-y: auto;
/* 确保子元素不受过渡效果影响 */
contain: layout;
}
/* 移动端菜单背景 */
#mobile-menu-bg {
/* 直接应用高斯模糊,不使用过渡效果 */
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
background: rgba(249, 250, 251, 0.8);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.04);
/* 确保高斯模糊立即应用 */
will-change: backdrop-filter;
}
:global([data-theme="dark"]) #mobile-menu-bg {
background: rgba(15, 23, 42, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
</style>
<script>
const header = document.getElementById('header-bg');
const mobileMenuBg = document.getElementById('mobile-menu-bg');
const scrollThreshold = 50;
function updateHeaderBackground() {
if (window.scrollY > scrollThreshold) {
header?.classList.add('scrolled');
} else {
header?.classList.remove('scrolled');
}
}
// 初始检查
updateHeaderBackground();
// 添加滚动事件监听
window.addEventListener('scroll', updateHeaderBackground);
// 移动端菜单逻辑
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const menuOpenIcon = document.getElementById('menu-open-icon');
const menuCloseIcon = document.getElementById('menu-close-icon');
if (mobileMenuButton && mobileMenu && menuOpenIcon && menuCloseIcon) {
mobileMenuButton.addEventListener('click', () => {
const expanded = mobileMenuButton.getAttribute('aria-expanded') === 'true';
// 切换菜单状态
mobileMenuButton.setAttribute('aria-expanded', (!expanded).toString());
if (expanded) {
// 直接隐藏菜单,不使用过渡效果
mobileMenu.classList.add('hidden');
} else {
// 直接显示菜单,不使用过渡效果
mobileMenu.classList.remove('hidden');
}
// 切换图标
menuOpenIcon.classList.toggle('hidden');
menuCloseIcon.classList.toggle('hidden');
});
}
</script>

View File

@ -1,13 +1,19 @@
export const SITE_NAME = "Blog Name";
export const SITE_URL = 'https://lsy22.com';
export const SITE_NAME = "echoes";
export const NAV_LINKS = [
{ href: '/', text: '首页' },
{ href: '/articles', text: '文章' },
{ href: '/movies', text: '观影' },
{ href: '/books', text: '读书' },
{ href: '/about', text: '关于' },
{ href: '/projects', text: '项目' }
{ href: '/projects', text: '项目' },
{ href: '/about', text: '关于' }
];
export const ICP = '渝ICP备2022009272号';
export const PSB_ICP = '渝公网安备50011902000520号';
export const PSB_ICP_URL = 'http://www.beian.gov.cn/portal/registerSystemInfo';
export const VISITED_PLACES = [ '黑龙江', '吉林', '辽宁', '北京', '天津', '广东', '西藏', '河北', '山东', '湖南','重庆','四川' ];
export const DOUBAN_ID = 'lsy22';

View File

@ -1,19 +0,0 @@
---
title: "测试文章"
date: 2023-03-03
tags: ["测试"]
category: "测试分类"
summary: "这是一篇测试文章,用于测试目录结构"
---
# 测试文章
这是一篇测试文章,用于测试目录结构功能。
## 二级标题
这是二级标题下的内容。
### 三级标题
这是三级标题下的内容。

View File

@ -1,335 +0,0 @@
---
title: "多级目录测试文章"
date: 2023-03-03
tags: ["测试", "多级目录"]
category: "测试分类"
summary: "这是一篇用于测试多级目录功能的文章"
---
## 1. 基础语法
### 1.1 粗体文本
```markdown
**这是粗体文本**
```
**这是粗体文本**
### 1.2 斜体文本
```markdown
*这是斜体文本*
```
*这是斜体文本*
### 1.3 粗斜体文本
```markdown
***这是粗斜体文本***
```
***这是粗斜体文本***
### 1.4 删除线文本
```markdown
~~这是删除线文本~~
```
~~这是删除线文本~~
### 1.5 无序列表
```markdown
- 第一项
- 子项 1
- 子项 2
- 第二项
- 第三项
```
- 第一项
- 子项 1
- 子项 2
- 第二项
- 第三项
### 1.6 有序列表
```markdown
1. 第一步
1. 子步骤 1
2. 子步骤 2
2. 第二步
3. 第三步
```
1. 第一步
1. 子步骤 1
2. 子步骤 2
2. 第二步
3. 第三步
### 1.7 任务列表
```markdown
- [x] 已完成任务
- [ ] 未完成任务
- [x] 又一个已完成任务
```
- [x] 已完成任务
- [ ] 未完成任务
- [x] 又一个已完成任务
### 1.8 行内代码
```markdown
这是一段包含`const greeting = "Hello World";`的行内代码
```
这是一段包含`const greeting = "Hello World";`的行内代码
### 1.9 代码块
````markdown
```typescript
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, \${user.name}!`;
}
```
````
```typescript
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, \${user.name}!`;
}
```
### 1.10 表格
```markdown
| 功能 | 基础版 | 高级版 |
|:-----|:------:|-------:|
| 文本编辑 | ✓ | ✓ |
| 实时预览 | ✗ | ✓ |
| 导出格式 | 2种 | 5种 |
```
| 功能 | 基础版 | 高级版 |
|:-----|:------:|-------:|
| 文本编辑 | ✓ | ✓ |
| 实时预览 | ✗ | ✓ |
| 导出格式 | 2种 | 5种 |
### 1.11 引用
```markdown
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。
```
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。
### 1.12 脚注
```markdown
这里有一个脚注[^1]。
[^1]: 这是脚注的内容。
```
这里有一个脚注[^1]。
[^1]: 这是脚注的内容。
### 1.13 表情符号
```markdown
:smile: :heart: :star: :rocket:
```
:smile: :heart: :star: :rocket:
### 1.14 可折叠内容
```markdown
<details>
<summary >
🎯 如何选择合适的写作工具?
</summary>
选择写作工具时需要考虑以下几点:
1. **跨平台支持** - 确保在不同设备上都能访问
2. **实时预览** - Markdown 实时渲染很重要
3. **版本控制** - 最好能支持文章的版本管理
4. **导出功能** - 支持导出为多种格式
</details>
```
<details>
<summary>
🎯 如何选择合适的写作工具?
</summary>
选择写作工具时需要考虑以下几点:
1. **跨平台支持** - 确保在不同设备上都能访问
2. **实时预览** - Markdown 实时渲染很重要
3. **版本控制** - 最好能支持文章的版本管理
4. **导出功能** - 支持导出为多种格式
</details>
### 1.15 引用式
```markdown
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。以下是一些建议:
>
> 1. 开门见山,直入主题
> 2. 层次分明,逻辑清晰
> 3. 语言简洁,表达准确
>
> *— 写作指南*
```
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。以下是一些建议:
>
> 1. 开门见山,直入主题
> 2. 层次分明,逻辑清晰
> 3. 语言简洁,表达准确
>
> *— 写作指南*
## 2. HTML排版
### 2.1 图文混排布局
```markdown
<div class="flex items-center gap-6 my-8">
<img src="https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=400&h=400"
alt="写作工具"
class="w-1/3 rounded-lg shadow-lg" />
<div class="flex-1">
<h4 class="text-xl font-bold mb-2">高效写作工具</h4>
<p>使用合适的写作工具可以极大提升写作效率。推荐使用支持即时预览的编辑器,这样可以实时查看排版效果。</p>
</div>
</div>
```
<div class="flex items-center gap-6 my-8">
<img src="https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=400&h=400"
alt="写作工具"
class="w-1/3 rounded-lg shadow-lg" />
<div class="flex-1">
<h4 class="text-xl font-bold mb-2">高效写作工具</h4>
<p>使用合适的写作工具可以极大提升写作效率。推荐使用支持即时预览的编辑器,这样可以实时查看排版效果。</p>
</div>
</div>
### 2.2 并排卡片
```markdown
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-8">
<div class="p-6 bg-gray-100 rounded-lg">
<h4 class="text-lg font-bold mb-2">🚀 快速上手</h4>
<p>通过简单的标记语法,快速创建格式化的文档,无需复杂的排版工具。</p>
</div>
<div class="p-6 bg-gray-100 rounded-lg">
<h4 class="text-lg font-bold mb-2">⚡ 高效输出</h4>
<p>专注于内容创作,让工具自动处理排版,提高写作效率。</p>
</div>
</div>
```
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-8">
<div class="p-6 bg-gray-100 rounded-lg">
<h4 class="text-lg font-bold mb-2">🚀 快速上手</h4>
<p>通过简单的标记语法,快速创建格式化的文档,无需复杂的排版工具。</p>
</div>
<div class="p-6 bg-gray-100 rounded-lg">
<h4 class="text-lg font-bold mb-2">⚡ 高效输出</h4>
<p>专注于内容创作,让工具自动处理排版,提高写作效率。</p>
</div>
</div>
### 2.4 高亮提示框
```markdown
<div class="p-6 bg-blue-50 border-l-4 border-blue-500 rounded-lg my-8">
<h4 class="text-lg font-bold text-blue-700 mb-2">💡 小贴士</h4>
<p class="text-blue-600">在写作时,可以先列出文章大纲,再逐步充实内容。这可以保证文章结构清晰,内容完整。</p>
</div>
```
<div class="p-6 bg-blue-50 border-l-4 border-blue-500 rounded-lg my-8">
<h4 class="text-lg font-bold text-blue-700 mb-2">小贴士</h4>
<p class="text-blue-600">在写作时,可以先列出文章大纲,再逐步充实内容。这样可以保证文章结构清晰,内容完整。</p>
</div>
### 2.5 时间线
```markdown
<div class="relative pl-8 my-8 border-l-2 border-gray-200">
<div class="mb-8 relative">
<div class="absolute -left-10 w-4 h-4 bg-blue-500 rounded-full"></div>
<div class="font-bold mb-2">1. 确定主题</div>
<p>根据目标受众和写作目的,确定文章主题。</p>
</div>
<div class="mb-8 relative">
<div class="absolute -left-10 w-4 h-4 bg-blue-500 rounded-full"></div>
<div class="font-bold mb-2">2. 收集资料</div>
<p>广泛搜集相关资料,为写作做充实准备。</p>
</div>
</div>
```
<div class="relative pl-8 my-8 border-l-2 border-gray-200">
<div class="mb-8 relative">
<div class="absolute -left-10 w-4 h-4 bg-blue-500 rounded-full"></div>
<div class="font-bold mb-2">1. 确定主题</div>
<p>根据目标受众和写作目的,确定文章主题。</p>
</div>
<div class="mb-8 relative">
<div class="absolute -left-10 w-4 h-4 bg-blue-500 rounded-full"></div>
<div class="font-bold mb-2">2. 收集资料</div>
<p>广泛搜集相关资料,为写作做充实准备。</p>
</div>
</div>
## 3. 总结
本文展示了 Markdown 从基础到高级的各种用法:
1. 基础语法:文本格式化、列表、代码、表格等
2. 高级排版:图文混排、叠面板、卡片布局等
3. 特殊语法:数学公式、脚注、表情符号等
> 💡 **提示**:部分高级排版功能可能需要特定的 Markdown 编辑器或渲染支持,请确认是否支持这些功能。

View File

@ -1,108 +0,0 @@
---
title: Astro 框架介绍
date: 2023-07-20
tags: [Astro, 前端, Web开发]
category: 技术
summary: Astro 是一个现代的静态站点生成器,专注于内容驱动的网站,本文将介绍其基本特性和使用方法。
---
# Astro 框架介绍
[Astro](https://astro.build/) 是一个现代的静态站点生成器,专注于内容驱动的网站。它允许开发者使用他们喜欢的 UI 组件框架(如 React、Vue、Svelte 等),同时生成快速、优化的静态 HTML。
## Astro 的主要特点
### 1. 零 JavaScript 默认
Astro 默认不会向客户端发送任何 JavaScript。这意味着您的网站将以闪电般的速度加载因为浏览器不需要下载、解析和执行 JavaScript 代码。
### 2. 组件岛屿架构
Astro 引入了"组件岛屿"的概念,允许您在需要交互性的地方选择性地使用 JavaScript。这种方法确保了最佳的性能同时仍然提供了丰富的用户体验。
```astro
---
import ReactCounter from '../components/ReactCounter.jsx';
import VueCounter from '../components/VueCounter.vue';
import SvelteCounter from '../components/SvelteCounter.svelte';
---
<!-- 这些组件将在客户端激活(水合) -->
<ReactCounter client:load />
<VueCounter client:visible />
<SvelteCounter client:idle />
```
### 3. 多框架支持
Astro 允许您在同一个项目中使用多种 UI 框架,如 React、Vue、Svelte、Solid 等。这意味着您可以使用最适合特定任务的工具,而不必被单一框架所限制。
### 4. 内置优化
Astro 自动优化您的网站,包括:
- 代码分割
- CSS 优化
- 图像优化
- 预渲染
- 延迟加载
## 基本使用
### 安装
使用以下命令创建一个新的 Astro 项目:
```bash
# 使用 npm
npm create astro@latest
# 使用 yarn
yarn create astro
# 使用 pnpm
pnpm create astro@latest
```
### 项目结构
一个基本的 Astro 项目结构如下:
```
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
### 创建页面
在 Astro 中,`src/pages/` 目录中的每个 `.astro` 文件都会生成一个对应的 HTML 页面:
```astro
---
// src/pages/index.astro
---
<html>
<head>
<title>我的 Astro 网站</title>
</head>
<body>
<h1>欢迎来到我的网站!</h1>
</body>
</html>
```
## 结语
Astro 是一个强大而灵活的框架,特别适合内容驱动的网站,如博客、文档站点、营销网站等。它的零 JavaScript 默认策略和组件岛屿架构使其成为构建高性能网站的绝佳选择。
如果您正在寻找一个能够提供出色开发体验和最终用户体验的框架Astro 绝对值得一试!

View File

@ -1,19 +0,0 @@
---
title: "测试文章"
date: 2023-03-03
tags: ["测试"]
category: "测试分类"
summary: "这是一篇测试文章,用于测试目录结构"
---
# 测试文章
这是一篇测试文章,用于测试目录结构功能。
## 二级标题
这是二级标题下的内容。
### 三级标题
这是三级标题下的内容。

View File

@ -1,79 +0,0 @@
---
title: Hello World
date: 2023-06-15
tags: [示例, Markdown, 入门]
category: 教程
summary: 这是一篇示例文章,展示了 Markdown 的基本语法和使用方法。
---
# Hello World
这是一篇示例文章,用于展示 Markdown 的基本语法和使用方法。
## Markdown 简介
Markdown 是一种轻量级标记语言,创建于 2004 年其设计目标是易读易写成为一种适用于网络的书写语言。Markdown 的语法简洁明了,易于掌握,目前被广泛用于撰写博客、文档、笔记等。
## 基本语法
### 标题
使用 `#` 号可以表示不同级别的标题,例如:
```markdown
# 一级标题
## 二级标题
### 三级标题
```
### 强调
使用星号或下划线可以强调文本:
*斜体* 或 _斜体_
**粗体** 或 __粗体__
***粗斜体*** 或 ___粗斜体___
### 列表
无序列表使用星号、加号或减号作为列表标记:
* 项目一
* 项目二
* 项目三
有序列表使用数字加点:
1. 第一项
2. 第二项
3. 第三项
### 链接
[链接文本](https://www.example.com)
### 图片
![图片描述](https://via.placeholder.com/150)
### 引用
> 这是一段引用文本。
>
> 引用可以有多个段落。
### 代码
行内代码使用反引号:`console.log('Hello World')`
代码块使用三个反引号:
```javascript
function sayHello() {
console.log('Hello World');
}
```
## 结语
Markdown 的语法非常简单,但功能强大,希望这篇文章能帮助您快速入门 Markdown。更多高级用法请参考 [Markdown 官方文档](https://daringfireball.net/projects/markdown/)。

View File

@ -0,0 +1,257 @@
---
title: "markdown使用教程"
date: 2023-03-03
tags: ["测试", "多级目录"]
---
### 1.1 标题语法
```markdown
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
```
### 1.2 文本格式化
#### 1.2.1 粗体文本
```markdown
**这是粗体文本**
```
**这是粗体文本**
#### 1.2.2 斜体文本
```markdown
*这是斜体文本*
```
*这是斜体文本*
#### 1.2.3 粗斜体文本
```markdown
***这是粗斜体文本***
```
***这是粗斜体文本***
#### 1.2.4 删除线文本
```markdown
~~这是删除线文本~~
```
~~这是删除线文本~~
#### 1.2.5 下划线文本
```markdown
<u>这是下划线文本</u>
```
<u>这是下划线文本</u>
### 1.3 列表
#### 1.3.1 无序列表
```markdown
- 第一项
- 子项 1
- 子项 2
- 第二项
- 第三项
```
- 第一项
- 子项 1
- 子项 2
- 第二项
- 第三项
#### 1.3.2 有序列表
```markdown
1. 第一步
1. 子步骤 1
2. 子步骤 2
2. 第二步
3. 第三步
```
1. 第一步
1. 子步骤 1
2. 子步骤 2
2. 第二步
3. 第三步
#### 1.3.3 任务列表
```markdown
- [x] 已完成任务
- [ ] 未完成任务
- [x] 又一个已完成任务
```
- [x] 已完成任务
- [ ] 未完成任务
- [x] 又一个已完成任务
### 1.4 代码
#### 1.4.1 行内代码
```markdown
这是一段包含`const greeting = "Hello World";`的行内代码
```
这是一段包含`const greeting = "Hello World";`的行内代码
#### 1.4.2 代码块
````markdown
```typescript
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
```
````
```typescript
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
```
### 1.5 表格
```markdown
| 功能 | 基础版 | 高级版 |
|:-----|:------:|-------:|
| 文本编辑 | ✓ | ✓ |
| 实时预览 | ✗ | ✓ |
| 导出格式 | 2种 | 5种 |
```
| 功能 | 基础版 | 高级版 |
|:-----|:------:|-------:|
| 文本编辑 | ✓ | ✓ |
| 实时预览 | ✗ | ✓ |
| 导出格式 | 2种 | 5种 |
### 1.6 引用
```markdown
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。
>
> 可以包含多个段落
```
> 📌 **最佳实践**
>
> 好的文章需要有清晰的结构和流畅的表达。
>
> 可以包含多个段落
### 1.7 链接和图片
#### 1.7.1 链接
```markdown
[Markdown 官方文档](https://www.markdownguide.org)
[相对路径链接](../path/to/file.md)
```
[Markdown 官方文档](https://www.markdownguide.org)
[相对路径链接](../path/to/file.md)
#### 1.7.2 图片
```markdown
![图片描述](https://example.com/image.jpg "图片标题")
```
### 1.8 水平分割线
```markdown
---
***
___
```
---
### 1.9 脚注
```markdown
这里有一个脚注[^1],这里有另一个脚注[^2]。
[^1]: 这是第一个脚注的内容。
[^2]: 这是第二个脚注的内容。
```
这里有一个脚注[^1],这里有另一个脚注[^2]。
[^1]: 这是第一个脚注的内容。
[^2]: 这是第二个脚注的内容。
### 1.10 转义字符
```markdown
\* 这不是斜体 \*
\` 这不是代码 \`
\[ 这不是链接 \]
```
\* 这不是斜体 \*
\` 这不是代码 \`
\[ 这不是链接 \]
### 1.11 收纳语法
```markdown
<details>
<summary>点击展开</summary>
> 这里是被收纳的内容。
> 可以包含任何 Markdown 格式的内容。
>
> - 列表项1
> - 列表项2
> - 列表项3
</details>
```
<details>
<summary>点击展开</summary>
> 这里是被收纳的内容。
> 可以包含任何 Markdown 格式的内容。
>
> - 列表项1
> - 列表项2
> - 列表项3
</details>

23
src/pages/404.astro Normal file
View File

@ -0,0 +1,23 @@
---
import Layout from '@/components/Layout.astro';
import { SITE_NAME } from '@/consts';
---
<Layout title={`404 - 页面未找到 | ${SITE_NAME}`}>
<div class="min-h-[calc(100vh-4rem)] flex items-center justify-center">
<div class="text-center px-4">
<h1 class="text-6xl md:text-8xl font-bold bg-gradient-to-r from-primary-600 to-primary-400 dark:from-primary-400 dark:to-primary-200 text-transparent bg-clip-text mb-6">
404
</h1>
<p class="text-2xl md:text-3xl font-semibold text-secondary-800 dark:text-secondary-200 mb-4">
页面未找到
</p>
<p class="text-lg md:text-xl text-secondary-600 dark:text-secondary-400 max-w-2xl mx-auto leading-relaxed mb-8">
您访问的页面不存在或已被移动到其他位置
</p>
<a href="/" class="inline-block px-6 py-3 bg-primary-500 hover:bg-primary-600 text-white font-medium rounded-lg transition-colors duration-300">
返回首页
</a>
</div>
</div>
</Layout>

15
src/pages/about.astro Normal file
View File

@ -0,0 +1,15 @@
---
import Layout from "@/components/Layout.astro";
import { Countdown } from "@/components/Countdown";
const targetDate = "2070-04-06";
---
<Layout title="退休倒计时">
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold text-center mb-8">距离退休还有</h1>
<div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<Countdown client:load targetDate={targetDate} />
</div>
</div>
</Layout>

View File

@ -1,9 +1,9 @@
---
import { getCollection, render } from 'astro:content';
import { contentStructure, getRelativePath, getBasename, getDirPath } from '../../content.config';
import type { SectionStructure } from '../../content.config';
import Layout from '../../components/Layout.astro';
import Breadcrumb from '../../components/Breadcrumb.astro';
import { contentStructure, getRelativePath, getBasename, getDirPath } from '@/content.config';
import type { SectionStructure } from '@/content.config';
import Layout from '@/components/Layout.astro';
import Breadcrumb from '@/components/Breadcrumb.astro';
// 添加这一行告诉Astro预渲染这个页面
export const prerender = true;
@ -91,27 +91,37 @@ const relatedArticles = allArticles
))
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime())
.slice(0, 3);
// 准备文章描述
const description = article.data.summary || `${article.data.title} - 发布于 ${article.data.date.toLocaleDateString('zh-CN')}`;
---
<Layout>
<div class="bg-gradient-to-b from-primary-50 dark:from-dark-surface to-white dark:to-dark-bg min-h-screen">
<div class="max-w-4xl mx-auto px-4 py-8">
<!-- 阅读进度条 -->
<div class="fixed top-0 left-0 w-full h-1 bg-transparent z-50" id="progress-container">
<div class="h-full w-0 bg-primary-500 transition-width duration-100" id="progress-bar"></div>
</div>
<Layout
title={article.data.title}
description={description}
date={article.data.date}
author={article.data.author}
tags={article.data.tags}
image={article.data.image}
>
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 阅读进度条 -->
<div class="fixed top-0 left-0 w-full h-1 bg-transparent z-50" id="progress-container 9">
<div class="h-full w-0 bg-primary-500 transition-width duration-100" id="progress-bar"></div>
</div>
<!-- 文章头部 -->
<header class="mb-8">
<!-- 导航区域 -->
<div class="bg-white dark:bg-dark-card rounded-xl p-4 mb-6">
<div class="flex items-center justify-between">
<Breadcrumb
pageType="article"
pathSegments={breadcrumbs}
articleTitle={article.data.title}
/>
<!-- 文章头部 -->
<header class="mb-8">
<!-- 导航区域 -->
<div class="bg-white dark:bg-dark-card rounded-xl p-4 mb-6 shadow-lg border border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<Breadcrumb
pageType="article"
pathSegments={breadcrumbs}
articleTitle={article.data.title}
/>
<div class="flex items-center gap-3">
{/* 返回按钮 */}
<a href="/articles" class="text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors flex items-center text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -121,77 +131,86 @@ const relatedArticles = allArticles
</a>
</div>
</div>
</div>
<h1 class="text-3xl font-bold mb-4 text-gray-900 dark:text-gray-100">{article.data.title}</h1>
<h1 class="text-3xl font-bold mb-4 text-gray-900 dark:text-gray-100">{article.data.title}</h1>
<div class="flex flex-wrap items-center gap-4 text-sm text-secondary-600 dark:text-secondary-400 mb-4">
<time datetime={article.data.date.toISOString()} class="flex items-center">
<div class="flex flex-wrap items-center gap-4 text-sm text-secondary-600 dark:text-secondary-400 mb-4">
<time datetime={article.data.date.toISOString()} class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{article.data.date.toLocaleDateString('zh-CN')}
</time>
{/* 显示文章所在目录 */}
{section && (
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
{article.data.date.toLocaleDateString('zh-CN')}
</time>
<a href={`/articles?path=${encodeURIComponent(section)}`} class="hover:text-indigo-600 transition-colors">
{section}
</a>
</span>
)}
</div>
{/* 显示文章所在目录 */}
{section && (
<span class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
<a href={`/articles?path=${encodeURIComponent(section)}`} class="hover:text-indigo-600 transition-colors">
{section}
</a>
</span>
)}
{article.data.tags && article.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mb-6">
{article.data.tags.map(tag => (
<a href={`/articles?tag=${tag}`} class="text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 py-1 px-2 rounded hover:bg-primary-100 dark:hover:bg-primary-800/30 transition-colors">
#{tag}
</a>
))}
</div>
)}
</header>
{article.data.tags && article.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mb-6">
{article.data.tags.map(tag => (
<a href={`/articles?tag=${tag}`} class="text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 py-1 px-2 rounded hover:bg-primary-100 dark:hover:bg-primary-800/30 transition-colors">
#{tag}
</a>
))}
</div>
)}
{article.data.summary && (
<div class="bg-primary-50 dark:bg-primary-900/30 p-4 rounded-lg mb-6 text-secondary-700 dark:text-secondary-300 italic">
{article.data.summary}
</div>
)}
</header>
<!-- 文章内容区域 -->
<div class="relative">
<!-- 文章内容 -->
<article class="prose prose-lg dark:prose-invert prose-primary prose-table:rounded-lg prose-thead:bg-secondary-50 dark:prose-thead:bg-dark-card prose-ul:list-disc prose-ol:list-decimal prose-li:my-1 max-w-none mb-12 bg-white dark:bg-dark-card p-8 rounded-xl">
<article class="prose prose-lg dark:prose-invert prose-primary prose-table:rounded-lg prose-table:border-separate prose-table:border-2 prose-thead:bg-primary-50 dark:prose-thead:bg-dark-surface prose-ul:list-disc prose-ol:list-decimal prose-li:my-1 prose-blockquote:border-l-4 prose-blockquote:border-primary-500 prose-blockquote:bg-gray-100 prose-blockquote:dark:bg-dark-surface prose-a:text-primary-600 prose-a:dark:text-primary-400 prose-a:no-underline prose-a:border-b prose-a:border-primary-300 prose-a:hover:border-primary-600 max-w-none mb-12">
<Content />
</article>
<!-- 相关文章 -->
{relatedArticles.length > 0 && (
<div class="mt-12 pt-8 border-t border-secondary-200 dark:border-dark-border">
<h2 class="text-2xl font-bold mb-6 text-primary-900 dark:text-primary-100">相关文章</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
{relatedArticles.map(relatedArticle => (
<a href={`/articles/${relatedArticle.id}`} class="block p-4 border border-primary-100 dark:border-dark-border rounded-lg bg-white dark:bg-dark-card hover:shadow-md transition-shadow">
<h3 class="font-bold text-lg mb-2 line-clamp-2 text-gray-800 dark:text-gray-200 hover:text-primary-700 dark:hover:text-primary-400">{relatedArticle.data.title}</h3>
<p class="text-sm text-secondary-600 dark:text-secondary-400 mb-2">{relatedArticle.data.date.toLocaleDateString('zh-CN')}</p>
{relatedArticle.data.summary && (
<p class="text-sm text-secondary-700 dark:text-secondary-300 line-clamp-3">{relatedArticle.data.summary}</p>
)}
</a>
))}
<!-- 固定目录面板 - 脱离文档流 -->
<div class="hidden 2xl:block fixed right-[calc(50%-48rem)] top-20 w-64 z-30">
<div class="bg-white dark:bg-dark-card rounded-lg shadow-lg p-4 max-h-[calc(100vh-8rem)] overflow-y-auto border border-gray-200 dark:border-gray-700">
<div class="border-b border-secondary-100 dark:border-dark-border pb-2 mb-3 sticky top-0 bg-white dark:bg-dark-card">
<h3 class="font-bold text-primary-700 dark:text-primary-400">文章目录</h3>
</div>
<div id="toc-content" class="text-sm">
<!-- 目录内容将通过JavaScript动态生成 -->
</div>
</div>
)}
<!-- 返回顶部按钮 -->
<button id="back-to-top" class="fixed bottom-8 right-8 w-12 h-12 rounded-full bg-primary-500 dark:bg-primary-600 text-white shadow-md flex items-center justify-center opacity-0 invisible translate-y-5 transition-all duration-300 hover:bg-primary-600 dark:hover:bg-primary-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
</button>
</div>
</div>
<!-- 相关文章 -->
{relatedArticles.length > 0 && (
<div class="mt-12 pt-8 border-t border-secondary-200 dark:border-dark-border">
<h2 class="text-2xl font-bold mb-6 text-primary-900 dark:text-primary-100">相关文章</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
{relatedArticles.map(relatedArticle => (
<a href={`/articles/${relatedArticle.id}`} class="block p-5 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-dark-card hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<h3 class="font-bold text-lg mb-2 line-clamp-2 text-gray-800 dark:text-gray-200 hover:text-primary-700 dark:hover:text-primary-400">{relatedArticle.data.title}</h3>
<p class="text-sm text-secondary-600 dark:text-secondary-400 mb-2">{relatedArticle.data.date.toLocaleDateString('zh-CN')}</p>
{relatedArticle.data.summary && (
<p class="text-sm text-secondary-700 dark:text-secondary-300 line-clamp-3">{relatedArticle.data.summary}</p>
)}
</a>
))}
</div>
</div>
)}
<!-- 返回顶部按钮 -->
<button id="back-to-top" class="fixed bottom-8 right-8 w-12 h-12 rounded-full bg-primary-500 dark:bg-primary-600 text-white shadow-md flex items-center justify-center opacity-0 invisible translate-y-5 transition-all duration-300 hover:bg-primary-600 dark:hover:bg-primary-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
</button>
</div>
</Layout>
@ -237,6 +256,176 @@ const relatedArticles = allArticles
// 初始化
updateReadingProgress();
// 目录功能
document.addEventListener('DOMContentLoaded', () => {
const tocContent = document.getElementById('toc-content');
const tocPanel = document.querySelector('[class*="2xl:block"][class*="fixed"]');
// 检查是否有足够空间显示目录
function checkTocVisibility() {
if (!tocPanel) return;
// 如果窗口宽度小于1536px (2xl breakpoint),隐藏目录
if (window.innerWidth < 1536) {
tocPanel.classList.add('hidden');
tocPanel.classList.remove('2xl:block');
} else {
tocPanel.classList.remove('hidden');
tocPanel.classList.add('2xl:block');
}
}
// 监听窗口大小变化
window.addEventListener('resize', checkTocVisibility);
// 初始检查
checkTocVisibility();
// 生成目录内容
function generateTableOfContents() {
// 获取文章中的所有标题元素
const article = document.querySelector('article');
if (!article || !tocContent) {
console.error('找不到文章内容或目录容器');
if (tocContent) {
tocContent.innerHTML = '<p class="text-secondary-500 dark:text-secondary-400 italic">无法生成目录</p>';
}
return;
}
const headings = article.querySelectorAll('h1, h2, h3, h4, h5, h6');
if (headings.length === 0) {
tocContent.innerHTML = '<p class="text-secondary-500 dark:text-secondary-400 italic">此文章没有目录</p>';
return;
}
// 创建目录列表
const tocList = document.createElement('ul');
tocList.className = 'space-y-2';
// 为每个标题创建目录项
headings.forEach((heading, index) => {
// 为每个标题添加ID如果没有的话
if (!heading.id) {
heading.id = `heading-${index}`;
}
// 创建目录项
const listItem = document.createElement('li');
// 根据标题级别设置缩进
const headingLevel = parseInt(heading.tagName.substring(1));
const indent = (headingLevel - 1) * 0.75; // 每级缩进0.75rem
// 创建链接
const link = document.createElement('a');
link.href = `#${heading.id}`;
link.className = `block hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-50 ${headingLevel > 2 ? 'text-secondary-600 dark:text-secondary-400' : 'text-secondary-800 dark:text-secondary-200 font-medium'}`;
link.style.paddingLeft = `${indent}rem`;
link.textContent = heading.textContent;
// 点击链接时滚动到目标位置
link.addEventListener('click', (e) => {
// 平滑滚动到目标位置
e.preventDefault();
const targetId = link.getAttribute('href')?.substring(1);
if (!targetId) return;
const targetElement = document.getElementById(targetId);
if (targetElement) {
// 滚动到目标位置,并添加一些偏移以避免被固定导航遮挡
const offset = 100; // 可以根据需要调整
const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
// 高亮目标标题
targetElement.classList.add('bg-primary-50', 'dark:bg-primary-900/20');
setTimeout(() => {
targetElement.classList.remove('bg-primary-50', 'dark:bg-primary-900/20');
}, 2000);
}
});
listItem.appendChild(link);
tocList.appendChild(listItem);
});
// 将目录添加到面板
tocContent.innerHTML = '';
tocContent.appendChild(tocList);
}
// 页面加载时生成目录
try {
generateTableOfContents();
// 添加滚动监听,更新目录高亮
function updateActiveHeading() {
const article = document.querySelector('article');
if (!article || !tocContent) return;
const headings = Array.from(article.querySelectorAll('h1, h2, h3, h4, h5, h6'));
if (headings.length === 0) return;
// 获取所有目录链接
const tocLinks = Array.from(tocContent.querySelectorAll('a'));
if (tocLinks.length === 0) return;
// 移除所有活跃状态
tocLinks.forEach(link => {
link.classList.remove('text-primary-600', 'dark:text-primary-400', 'font-medium');
});
// 找到当前视口中最靠近顶部的标题
let currentHeading = null;
const scrollPosition = window.scrollY + 150; // 添加一些偏移量
for (const heading of headings) {
const headingTop = heading.getBoundingClientRect().top + window.scrollY;
if (headingTop <= scrollPosition) {
currentHeading = heading;
} else {
break;
}
}
// 如果找到当前标题,高亮对应的目录项
if (currentHeading) {
const activeLink = tocLinks.find(link => link.getAttribute('href') === `#${currentHeading.id}`);
if (activeLink) {
activeLink.classList.add('text-primary-600', 'dark:text-primary-400', 'font-medium');
}
}
}
// 监听滚动事件,使用节流函数优化性能
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
updateActiveHeading();
ticking = false;
});
ticking = true;
}
});
// 初始化高亮
updateActiveHeading();
} catch (error) {
console.error('生成目录时发生错误:', error);
if (tocContent) {
tocContent.innerHTML = '<p class="text-secondary-500 dark:text-secondary-400 italic">生成目录时发生错误</p>';
}
}
});
// 代码块增强功能
document.addEventListener('DOMContentLoaded', () => {
// 处理所有代码块

View File

@ -5,6 +5,20 @@ import { contentStructure, getRelativePath, getBasename, getDirPath } from '../.
import Layout from '@/components/Layout.astro';
import Breadcrumb from '@/components/Breadcrumb.astro';
import ArticleTimeline from '@/components/ArticleTimeline.astro';
export function extractSummary(content: string, length = 150) {
// 移除 Markdown 标记
const plainText = content
.replace(/---[\s\S]*?---/, '') // 移除 frontmatter
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 将链接转换为纯文本
.replace(/[#*`~>]/g, '') // 移除特殊字符
.replace(/\n+/g, ' ') // 将换行转换为空格
.trim();
// 提取指定长度的文本
return plainText.length > length
? plainText.slice(0, length).trim() + '...'
: plainText;
}
// 预渲染页面,但允许客户端导航
export const prerender = false;
@ -100,10 +114,10 @@ interface Breadcrumb {
---
<Layout>
<div class="bg-gradient-to-b from-primary-50 to-white dark:from-gray-900 dark:to-gray-800 min-h-screen">
<main class={`mx-auto px-4 py-6 ${viewMode === 'grid' ? 'max-w-6xl' : 'max-w-4xl'}`}>
<div class="bg-gray-50 dark:bg-dark-bg min-h-screen">
<main class={`mx-auto px-4 sm:px-6 lg:px-8 py-6 ${viewMode === 'grid' ? 'max-w-7xl' : 'max-w-5xl'}`}>
<!-- 导航栏 -->
<div class="bg-white dark:bg-gray-800 rounded-xl mb-4">
<div class="bg-white dark:bg-gray-800 rounded-xl mb-4 shadow-lg border border-gray-200 dark:border-gray-700">
<div class="px-4 py-3">
<div class="flex items-center justify-between !h-10">
<Breadcrumb
@ -146,7 +160,7 @@ interface Breadcrumb {
{/* 上一级目录卡片 - 仅在浏览目录时显示 */}
{!tagFilter && pathSegments.length > 0 && (
<a href={`/articles${pathSegments.length > 1 ? `?path=${encodeURIComponent(pathSegments.slice(0, -1).join('/'))}` : ''}`}
class="group flex flex-col h-full p-4 border border-primary-100 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl transition-all duration-300 hover:border-primary-300 dark:hover:border-primary-600">
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div class="flex items-center">
<div class="w-10 h-10 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -173,7 +187,7 @@ interface Breadcrumb {
return (
<a href={`/articles?path=${encodeURIComponent(dirLink)}`}
class="group flex flex-col h-full p-4 border border-primary-100 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl transition-all duration-300 hover:border-primary-300 dark:hover:border-primary-600">
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div class="flex items-center">
<div class="w-10 h-10 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -216,7 +230,7 @@ interface Breadcrumb {
// 显示标签过滤后的文章
filteredArticles.map(article => (
<a href={`/articles/${article.id}`}
class="group flex flex-col h-full p-4 border border-primary-100 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl transition-all duration-300 hover:border-primary-300 dark:hover:border-primary-600">
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div class="flex items-start">
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -225,8 +239,10 @@ interface Breadcrumb {
</div>
<div class="ml-3 flex-1">
<h3 class="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-2">{article.data.title}</h3>
{article.data.summary && (
<p class="text-xs text-gray-600 mt-1 line-clamp-2">{article.data.summary}</p>
{article.body && (
<p class="text-xs text-gray-600 mt-1 line-clamp-2">
{extractSummary(article.body)}
</p>
)}
<div class="text-xs text-gray-500 mt-2 flex items-center justify-between">
<time datetime={article.data.date.toISOString()}>
@ -292,7 +308,7 @@ interface Breadcrumb {
if (!article) {
return (
<div class="flex flex-col h-full p-4 border border-red-200 rounded-xl bg-red-50">
<div class="flex flex-col h-full p-5 border border-red-200 rounded-xl bg-red-50 shadow-lg">
<div class="flex items-start">
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-red-100 text-red-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -313,7 +329,7 @@ interface Breadcrumb {
return (
<a href={`/articles/${article.id}`}
class="group flex flex-col h-full p-4 border border-primary-100 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl transition-all duration-300 hover:border-primary-300 dark:hover:border-primary-600">
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
<div class="flex items-start">
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -322,8 +338,10 @@ interface Breadcrumb {
</div>
<div class="ml-3 flex-1">
<h3 class="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-2">{article.data.title}</h3>
{article.data.summary && (
<p class="text-xs text-gray-600 mt-1 line-clamp-2">{article.data.summary}</p>
{article.body && (
<p class="text-xs text-gray-600 mt-1 line-clamp-2">
{extractSummary(article.body)}
</p>
)}
<div class="text-xs text-gray-500 mt-2 flex items-center justify-between">
<time datetime={article.data.date.toISOString()}>
@ -341,7 +359,7 @@ interface Breadcrumb {
{/* 空内容提示 */}
{((tagFilter && filteredArticles.length === 0) || (!tagFilter && currentSections.length === 0 && currentArticles.length === 0)) && (
<div class="text-center py-16 bg-white rounded-xl shadow-sm mb-12">
<div class="text-center py-16 bg-white rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 mb-12">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-primary-200 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
@ -355,7 +373,7 @@ interface Breadcrumb {
)}
<!-- 标签过滤器 -->
<div class="bg-white dark:bg-gray-800 p-8 rounded-xl shadow-sm">
<div class="bg-white dark:bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700">
<h2 class="text-2xl font-bold mb-6 text-primary-900 dark:text-primary-100 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
@ -368,10 +386,10 @@ interface Breadcrumb {
const isActive = tag === tagFilter;
return (
<a href={`/articles?tag=${tag}`}
class={`py-2 px-4 rounded-full text-sm font-medium transition-all duration-200 ${
class={`py-2 px-4 rounded-full text-sm font-medium transition-all duration-300 ${
isActive
? 'bg-primary-600 text-white dark:bg-primary-500 dark:text-gray-900 hover:bg-primary-700 dark:hover:bg-primary-400'
: 'bg-primary-50 dark:bg-gray-700 text-primary-700 dark:text-primary-200 hover:bg-primary-100 dark:hover:bg-gray-600'
? 'bg-primary-600 text-white dark:bg-primary-500 dark:text-gray-100 hover:bg-primary-700 dark:hover:bg-primary-600 shadow-md hover:shadow-lg'
: 'bg-primary-50 dark:bg-gray-700/50 text-primary-600 dark:text-gray-300 hover:bg-primary-100 dark:hover:bg-gray-700 hover:text-primary-700 dark:hover:text-primary-400'
}`}>
{tag}
</a>

View File

@ -1,10 +1,17 @@
---
import Layout from '@/components/Layout.astro';
import { ThemeToggle } from '@/components/ThemeToggle';
import { SITE_NAME } from '@/consts';
---
<Layout>
<div class="min-h-[calc(100vh-4rem)] flex items-center justify-center">
<div class="text-center px-4">
<h1 class="text-6xl md:text-8xl font-bold bg-gradient-to-r from-primary-600 to-primary-400 dark:from-primary-400 dark:to-primary-200 text-transparent bg-clip-text mb-6">
{SITE_NAME}
</h1>
<p class="text-xl md:text-2xl text-secondary-600 dark:text-secondary-400 max-w-2xl mx-auto leading-relaxed">
探索技术与思维的边界
</p>
</div>
</div>
</Layout>

View File

@ -0,0 +1,22 @@
---
import Layout from '@/components/Layout.astro';
import { GitPlatform } from '@/components/GitProjectCollection';
import GitProjectCollection from '@/components/GitProjectCollection';
---
<Layout title="项目 | echoes">
<main class="container mx-auto px-4 py-8">
<div class="space-y-12">
<!-- Gitea 项目集合 -->
<GitProjectCollection
platform={GitPlatform.GITEA}
username="lsy"
title="Gitea 个人项目"
client:load
/>
</div>
</main>
</Layout>

View File

@ -1,42 +1,186 @@
/* 增强表格样式 */
.prose table {
border-collapse: collapse;
border-collapse: separate;
border-spacing: 0;
width: 100%;
margin-top: 1.5em;
margin-bottom: 1.5em;
border-radius: 0.5rem;
margin: 1.5em 0;
border-radius: 0.75rem;
overflow: hidden;
box-shadow: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border: 2px solid var(--color-secondary-500);
}
/* 确保表格边框在所有浏览器中都能正确显示 */
.prose table * {
box-sizing: border-box;
}
.prose thead {
background-color: var(--color-secondary-50);
background-color: var(--color-primary-50);
}
.prose thead th {
padding: 0.75rem 1rem;
padding: 1rem 1.25rem;
font-weight: 600;
text-align: left;
border-bottom: 2px solid var(--color-secondary-200);
color: var(--color-primary-900);
font-size: 0.95rem;
line-height: 1.5rem;
white-space: nowrap;
position: relative;
vertical-align: middle;
border-bottom: 2px solid var(--color-secondary-500);
border-right: 2px solid var(--color-secondary-500);
}
.prose thead th:last-child {
border-right: none;
}
.prose tbody tr {
border-bottom: 1px solid var(--color-secondary-200);
border-bottom: 2px solid var(--color-secondary-500);
}
.prose tbody tr:last-child {
border-bottom: none;
}
.prose tbody tr:hover {
background-color: var(--color-secondary-50);
}
.prose tbody td {
padding: 0.75rem 1rem;
vertical-align: top;
padding: 0.875rem 1.25rem;
color: var(--color-secondary-800);
font-size: 0.95rem;
line-height: 1.5rem;
vertical-align: middle;
position: relative;
border-right: 2px solid var(--color-secondary-500);
border-bottom: 2px solid var(--color-secondary-500);
}
.prose tbody td:last-child {
border-right: none;
}
.prose tbody tr:last-child td {
border-bottom: none;
}
.prose tbody tr:nth-child(even) {
background-color: var(--color-secondary-50/80);
}
.prose tbody tr:nth-child(even):hover {
background-color: var(--color-secondary-50);
}
/* 表格内容对齐样式 */
.prose th[align="center"],
.prose td[align="center"] {
text-align: center;
}
.prose th[align="right"],
.prose td[align="right"] {
text-align: right;
}
.prose th[align="left"],
.prose td[align="left"] {
text-align: left;
}
/* 表格内容样式增强 */
.prose td code {
background-color: var(--color-secondary-100);
padding: 0.2em 0.4em;
border-radius: 0.25em;
font-size: 0.875em;
}
[data-theme="dark"] .prose td code {
background-color: var(--color-secondary-800);
}
/* 表格内的链接样式 */
.prose td a {
color: var(--color-primary-600);
text-decoration: none;
border-bottom: 1px solid var(--color-primary-300);
transition: all 0.2s ease;
}
.prose td a:hover {
color: var(--color-primary-700);
border-bottom-color: var(--color-primary-500);
}
[data-theme="dark"] .prose td a {
color: var(--color-primary-400);
border-bottom-color: var(--color-primary-700);
}
[data-theme="dark"] .prose td a:hover {
color: var(--color-primary-300);
border-bottom-color: var(--color-primary-500);
}
/* 表格内复选框样式 */
.prose td input[type="checkbox"] {
width: 1.25em;
height: 1.25em;
margin: 0;
vertical-align: middle;
position: relative;
top: -1px;
accent-color: var(--color-primary-500);
}
/* 表格内图标样式 */
.prose td svg,
.prose td img:not(.emoji) {
vertical-align: middle;
display: inline-block;
width: auto;
height: 1.25em;
margin: 0 0.25em;
}
/* 表格内的状态标记 */
.prose td .status {
display: inline-flex;
align-items: center;
padding: 0.25em 0.75em;
border-radius: 9999px;
font-size: 0.75em;
font-weight: 500;
line-height: 1.5;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.prose td .status-success {
background-color: rgba(16, 185, 129, 0.1);
color: rgb(6, 95, 70);
}
.prose td .status-warning {
background-color: rgba(245, 158, 11, 0.1);
color: rgb(146, 64, 14);
}
.prose td .status-error {
background-color: rgba(239, 68, 68, 0.1);
color: rgb(153, 27, 27);
}
.prose td .status-info {
background-color: rgba(59, 130, 246, 0.1);
color: rgb(30, 64, 175);
}
/* 增强列表样式 */
.prose ul {
list-style-type: disc;
@ -239,15 +383,374 @@
color: var(--color-secondary-300);
}
/* 引用样式 */
.prose blockquote {
margin: 1.5em 0;
padding: 1em 1.5em;
border-left: 4px solid var(--color-primary-500);
background-color: var(--color-gray-100);
border-radius: 0.5rem;
font-style: italic;
color: var(--color-secondary-700);
}
.prose blockquote p {
margin: 0;
}
.prose blockquote p + p {
margin-top: 1em;
}
/* 链接样式 */
.prose a {
color: var(--color-primary-600);
text-decoration: none;
border-bottom: 1px solid var(--color-primary-300);
transition: all 0.2s ease;
}
.prose a:hover {
color: var(--color-primary-800);
border-bottom-color: var(--color-primary-600);
}
/* 暗色模式适配 */
[data-theme="dark"] .prose blockquote {
background-color: var(--color-dark-surface);
border-left-color: var(--color-primary-400);
color: var(--color-secondary-300);
}
[data-theme="dark"] .prose a {
color: var(--color-primary-400);
border-bottom-color: var(--color-primary-600);
}
[data-theme="dark"] .prose a:hover {
color: var(--color-primary-300);
border-bottom-color: var(--color-primary-400);
}
/* 响应式表格样式 */
@media (max-width: 640px) {
.prose table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.prose thead th {
white-space: nowrap;
padding: 0.75rem 1rem;
}
.prose tbody td {
padding: 0.75rem 1rem;
}
}
/* 表格内容溢出处理 */
.prose td p {
margin: 0;
}
.prose td > *:first-child {
margin-top: 0;
}
.prose td > *:last-child {
margin-bottom: 0;
}
.prose td.truncate {
max-width: 20rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 确保表格边框在所有情况下都能正确显示 */
.prose table tr td:last-child {
border-right: none !important;
}
.prose table tr th:last-child {
border-right: none !important;
}
.prose table tr:last-child td {
border-bottom: none !important;
}
/* 暗色模式下的边框颜色 */
[data-theme="dark"] .prose table {
--table-border-color: var(--color-dark-border);
}
/* 增强列表样式 */
.prose ul {
list-style-type: disc;
margin-top: 1.25em;
margin-bottom: 1.25em;
padding-left: 1.625em;
}
.prose ul li {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-left: 0.375em;
}
.prose ul li::marker {
color: #6b7280;
}
.prose ul li ul {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.prose ol {
list-style-type: decimal;
margin-top: 1.25em;
margin-bottom: 1.25em;
padding-left: 1.625em;
}
.prose ol li {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-left: 0.375em;
}
.prose ol li::marker {
color: #6b7280;
}
/* 添加明确的表格边框样式 */
.prose table {
border: 2px solid var(--color-secondary-500) !important;
}
.prose th {
border-right: 2px solid var(--color-secondary-500) !important;
border-bottom: 2px solid var(--color-secondary-500) !important;
}
.prose td {
border-right: 2px solid var(--color-secondary-500) !important;
border-bottom: 2px solid var(--color-secondary-500) !important;
}
.prose th:last-child,
.prose td:last-child {
border-right: none !important;
}
.prose tr:last-child td {
border-bottom: none !important;
}
[data-theme="dark"] .prose th,
[data-theme="dark"] .prose td {
border-color: var(--color-dark-border) !important;
}
[data-theme="dark"] .prose table {
border-color: var(--color-dark-border) !important;
}
/* 暗色模式表格样式 */
[data-theme="dark"] .prose table {
border-color: var(--color-dark-border);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
border-radius: 0.75rem;
border-width: 2px;
}
[data-theme="dark"] .prose thead {
background-color: var(--color-dark-card);
background-color: var(--color-dark-surface);
}
[data-theme="dark"] .prose thead th {
color: var(--color-primary-300);
border-bottom: 2px solid var(--color-dark-border);
border-right: 2px solid var(--color-dark-border);
font-weight: 600;
}
[data-theme="dark"] .prose thead th:last-child {
border-right: none;
}
[data-theme="dark"] .prose tbody tr {
border-bottom: 2px solid var(--color-dark-border);
}
[data-theme="dark"] .prose tbody tr:last-child {
border-bottom: none;
}
[data-theme="dark"] .prose tbody td {
color: var(--color-secondary-200);
border-right: 2px solid var(--color-dark-border);
border-bottom: 2px solid var(--color-dark-border);
}
[data-theme="dark"] .prose tbody td:last-child {
border-right: none;
}
[data-theme="dark"] .prose tbody tr:last-child td {
border-bottom: none;
}
[data-theme="dark"] .prose tbody tr:hover {
background-color: var(--color-dark-surface/60);
}
[data-theme="dark"] .prose tbody tr:nth-child(even) {
background-color: var(--color-dark-card);
background-color: var(--color-dark-surface/40);
}
[data-theme="dark"] .prose thead th,
[data-theme="dark"] .prose tbody tr {
border-color: var(--color-dark-border);
[data-theme="dark"] .prose tbody tr:nth-child(even):hover {
background-color: var(--color-dark-surface/60);
}
/* 暗色模式下的状态标记样式 */
[data-theme="dark"] .prose td .status-success {
background-color: rgba(16, 185, 129, 0.2);
color: rgb(110, 231, 183);
}
[data-theme="dark"] .prose td .status-warning {
background-color: rgba(245, 158, 11, 0.2);
color: rgb(253, 186, 116);
}
[data-theme="dark"] .prose td .status-error {
background-color: rgba(239, 68, 68, 0.2);
color: rgb(252, 165, 165);
}
/* 收纳内容样式 */
.details-content {
margin-left: 1.5em;
padding: 1em;
background-color: var(--color-gray-100);
border-left: 4px solid var(--color-primary-500);
margin-bottom: 1em;
border-radius: 0.5rem;
}
[data-theme="dark"] .details-content {
background-color: var(--color-dark-surface);
border-left-color: var(--color-primary-400);
}
/* 收纳标题样式 */
.prose details {
margin: 1.5em 0;
border-radius: 0.5rem;
border: 1px solid var(--color-secondary-200);
background-color: var(--color-gray-50);
transition: all 0.2s ease;
overflow: hidden;
}
.prose details summary {
padding: 1em;
cursor: pointer;
position: relative;
font-weight: 500;
color: var(--color-secondary-900);
list-style: none;
display: flex;
align-items: center;
gap: 0.75em;
transition: all 0.2s ease;
background: linear-gradient(to right, var(--color-primary-50), var(--color-gray-50));
border-left: 4px solid var(--color-primary-100);
}
.prose details summary::-webkit-details-marker {
display: none;
}
.prose details summary::before {
content: "";
width: 20px;
height: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%234b6bff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5l7 7-7 7'%3E%3C/path%3E%3C/svg%3E");
background-size: contain;
background-repeat: no-repeat;
transition: transform 0.3s ease;
flex-shrink: 0;
}
.prose details[open] summary {
border-left: 4px solid var(--color-primary-500);
background: linear-gradient(to right, var(--color-primary-100), var(--color-gray-50));
border-bottom: 1px solid var(--color-secondary-200);
}
.prose details[open] summary::before {
transform: rotate(90deg);
}
.prose details summary:hover {
background: linear-gradient(to right, var(--color-primary-100), var(--color-gray-100));
color: var(--color-primary-700);
}
.prose details > blockquote {
margin: 0;
padding: 1.5em;
border-radius: 0;
border-left: 4px solid var(--color-primary-500);
background: linear-gradient(to right, var(--color-primary-50/50), var(--color-gray-50));
}
.prose details > blockquote p:first-child {
margin-top: 0;
}
.prose details > blockquote p:last-child {
margin-bottom: 0;
}
/* 暗色模式适配 */
[data-theme="dark"] .prose details {
border-color: var(--color-dark-border);
background-color: var(--color-dark-surface);
}
[data-theme="dark"] .prose details summary {
color: var(--color-secondary-100);
background: linear-gradient(to right, var(--color-dark-surface), var(--color-dark-card));
border-left-color: var(--color-primary-800);
}
[data-theme="dark"] .prose details[open] summary {
border-bottom-color: var(--color-dark-border);
}
[data-theme="dark"] .prose details summary::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23839dff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5l7 7-7 7'%3E%3C/path%3E%3C/svg%3E");
}
[data-theme="dark"] .prose details[open] summary {
background: linear-gradient(to right, var(--color-dark-card), var(--color-dark-surface));
border-left-color: var(--color-primary-400);
}
[data-theme="dark"] .prose details summary:hover {
background: linear-gradient(to right, var(--color-primary-900/30), var(--color-dark-card));
color: var(--color-primary-400);
}
[data-theme="dark"] .prose details > blockquote {
background: linear-gradient(to right, var(--color-primary-900/10), var(--color-dark-surface));
border-left-color: var(--color-primary-400);
}