Compare commits

..

4 Commits

Author SHA1 Message Date
joyzhao
21539772ef feat(blog): 添加博客工具函数以处理文章数据和分类标签 2026-01-12 14:43:05 +08:00
joyzhao
b8a8cc474b feat(comments): 添加 Waline 自定义样式文件以适配网站主题
fix(blog): 删除不再使用的博客工具函数文件
2026-01-12 14:36:11 +08:00
joyzhao
78c468a7d2 feat(comments): 添加页面浏览和评论功能支持 2026-01-12 11:04:30 +08:00
joyzhao
ca9e083399 feat(comments): 添加 Waline 评论组件,替换 Giscus 评论组件 2026-01-12 09:46:29 +08:00
7 changed files with 651 additions and 56 deletions

View File

@@ -39,6 +39,7 @@
"tw-animate-css": "^1.2.4" "tw-animate-css": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.16" "@tailwindcss/typography": "^0.5.16",
"@waline/client": "^3.8.0"
} }
} }

240
pnpm-lock.yaml generated
View File

@@ -75,6 +75,9 @@ importers:
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.16 specifier: ^0.5.16
version: 0.5.16(tailwindcss@4.1.10) version: 0.5.16(tailwindcss@4.1.10)
'@waline/client':
specifier: ^3.8.0
version: 3.8.0(typescript@5.8.3)
packages: packages:
@@ -159,6 +162,10 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-option@7.27.1': '@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -172,6 +179,11 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
'@babel/parser@7.28.5':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/plugin-transform-react-jsx-self@7.27.1': '@babel/plugin-transform-react-jsx-self@7.27.1':
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -200,6 +212,10 @@ packages:
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
'@capsizecss/unpack@2.4.0': '@capsizecss/unpack@2.4.0':
resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==} resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==}
@@ -510,6 +526,9 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0': '@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -946,6 +965,9 @@ packages:
'@types/unist@3.0.3': '@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@ungap/structured-clone@1.3.0': '@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
@@ -955,6 +977,56 @@ packages:
peerDependencies: peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
'@vue/compiler-core@3.5.26':
resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==}
'@vue/compiler-dom@3.5.26':
resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==}
'@vue/compiler-sfc@3.5.26':
resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==}
'@vue/compiler-ssr@3.5.26':
resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==}
'@vue/reactivity@3.5.26':
resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==}
'@vue/runtime-core@3.5.26':
resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==}
'@vue/runtime-dom@3.5.26':
resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==}
'@vue/server-renderer@3.5.26':
resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==}
peerDependencies:
vue: 3.5.26
'@vue/shared@3.5.26':
resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==}
'@vueuse/core@14.1.0':
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
peerDependencies:
vue: ^3.5.0
'@vueuse/metadata@14.1.0':
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
'@vueuse/shared@14.1.0':
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
peerDependencies:
vue: ^3.5.0
'@waline/api@1.0.0':
resolution: {integrity: sha512-o0lWjt+/oZH1/4q3DJxTf5kdkgNbSmoLRXIiGznW225A6hq9/9IkOO1DiAijIsxGYJS6psFC+58+IzkD1EerBA==}
engines: {node: '>=18'}
'@waline/client@3.8.0':
resolution: {integrity: sha512-UAbqLmqy2Pns/CXFlrLYEDG4F1peRPo+wp2FmpfFL9BO0ceQO6KvvAJXw3EhX3wWcMiKlX29G0gJVtG4UlCzGw==}
engines: {node: '>=18'}
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
@@ -1015,6 +1087,9 @@ packages:
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'}
hasBin: true hasBin: true
autosize@6.0.1:
resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==}
axobject-query@4.1.0: axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1153,6 +1228,9 @@ packages:
csstype@3.1.3: csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
debug@4.4.1: debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
@@ -1224,6 +1302,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
entities@7.0.0:
resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==}
engines: {node: '>=0.12'}
es-module-lexer@1.7.0: es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
@@ -1713,6 +1795,9 @@ packages:
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
magicast@0.3.5: magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
@@ -1723,6 +1808,16 @@ packages:
markdown-table@3.0.4: markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
marked-highlight@2.2.3:
resolution: {integrity: sha512-FCfZRxW/msZAiasCML4isYpxyQWKEEx44vOgdn5Kloae+Qc3q4XR7WjpKKf8oMLk7JP9ZCRd2vhtclJFdwxlWQ==}
peerDependencies:
marked: '>=4 <18'
marked@16.4.2:
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
engines: {node: '>= 20'}
hasBin: true
mdast-util-definitions@6.0.0: mdast-util-definitions@6.0.0:
resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==}
@@ -2003,6 +2098,10 @@ packages:
resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
prismjs@1.30.0: prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -2037,6 +2136,9 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'} engines: {node: '>= 14.18.0'}
recaptcha-v3@1.11.3:
resolution: {integrity: sha512-sEE6J0RzUkS+sKEBpgCD/AqCU0ffrAVOADGjvAx9vcttN+VLK42SWMkj/J/I6vHu3Kew+xcfbBqDVb65N0QGDw==}
recma-build-jsx@1.0.0: recma-build-jsx@1.0.0:
resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==}
@@ -2430,6 +2532,14 @@ packages:
vite: vite:
optional: true optional: true
vue@3.5.26:
resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
web-namespaces@2.0.1: web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
@@ -2661,6 +2771,8 @@ snapshots:
'@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
'@babel/helper-validator-option@7.27.1': {} '@babel/helper-validator-option@7.27.1': {}
'@babel/helpers@7.27.6': '@babel/helpers@7.27.6':
@@ -2672,6 +2784,10 @@ snapshots:
dependencies: dependencies:
'@babel/types': 7.27.6 '@babel/types': 7.27.6
'@babel/parser@7.28.5':
dependencies:
'@babel/types': 7.28.5
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)': '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.4)':
dependencies: dependencies:
'@babel/core': 7.27.4 '@babel/core': 7.27.4
@@ -2707,6 +2823,11 @@ snapshots:
'@babel/helper-string-parser': 7.27.1 '@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1 '@babel/helper-validator-identifier': 7.27.1
'@babel/types@7.28.5':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@capsizecss/unpack@2.4.0': '@capsizecss/unpack@2.4.0':
dependencies: dependencies:
blob-to-buffer: 1.2.9 blob-to-buffer: 1.2.9
@@ -2898,6 +3019,8 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
dependencies: dependencies:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
@@ -3291,6 +3414,8 @@ snapshots:
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
'@types/web-bluetooth@0.0.21': {}
'@ungap/structured-clone@1.3.0': {} '@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1))': '@vitejs/plugin-react@4.5.2(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1))':
@@ -3305,6 +3430,87 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vue/compiler-core@3.5.26':
dependencies:
'@babel/parser': 7.28.5
'@vue/shared': 3.5.26
entities: 7.0.0
estree-walker: 2.0.2
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.26':
dependencies:
'@vue/compiler-core': 3.5.26
'@vue/shared': 3.5.26
'@vue/compiler-sfc@3.5.26':
dependencies:
'@babel/parser': 7.28.5
'@vue/compiler-core': 3.5.26
'@vue/compiler-dom': 3.5.26
'@vue/compiler-ssr': 3.5.26
'@vue/shared': 3.5.26
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.6
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.26':
dependencies:
'@vue/compiler-dom': 3.5.26
'@vue/shared': 3.5.26
'@vue/reactivity@3.5.26':
dependencies:
'@vue/shared': 3.5.26
'@vue/runtime-core@3.5.26':
dependencies:
'@vue/reactivity': 3.5.26
'@vue/shared': 3.5.26
'@vue/runtime-dom@3.5.26':
dependencies:
'@vue/reactivity': 3.5.26
'@vue/runtime-core': 3.5.26
'@vue/shared': 3.5.26
csstype: 3.2.3
'@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.8.3))':
dependencies:
'@vue/compiler-ssr': 3.5.26
'@vue/shared': 3.5.26
vue: 3.5.26(typescript@5.8.3)
'@vue/shared@3.5.26': {}
'@vueuse/core@14.1.0(vue@3.5.26(typescript@5.8.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.1.0
'@vueuse/shared': 14.1.0(vue@3.5.26(typescript@5.8.3))
vue: 3.5.26(typescript@5.8.3)
'@vueuse/metadata@14.1.0': {}
'@vueuse/shared@14.1.0(vue@3.5.26(typescript@5.8.3))':
dependencies:
vue: 3.5.26(typescript@5.8.3)
'@waline/api@1.0.0': {}
'@waline/client@3.8.0(typescript@5.8.3)':
dependencies:
'@vueuse/core': 14.1.0(vue@3.5.26(typescript@5.8.3))
'@waline/api': 1.0.0
autosize: 6.0.1
marked: 16.4.2
marked-highlight: 2.2.3(marked@16.4.2)
recaptcha-v3: 1.11.3
vue: 3.5.26(typescript@5.8.3)
transitivePeerDependencies:
- typescript
acorn-jsx@5.3.2(acorn@8.15.0): acorn-jsx@5.3.2(acorn@8.15.0):
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
@@ -3457,6 +3663,8 @@ snapshots:
- uploadthing - uploadthing
- yaml - yaml
autosize@6.0.1: {}
axobject-query@4.1.0: {} axobject-query@4.1.0: {}
bail@2.0.2: {} bail@2.0.2: {}
@@ -3582,6 +3790,8 @@ snapshots:
csstype@3.1.3: {} csstype@3.1.3: {}
csstype@3.2.3: {}
debug@4.4.1: debug@4.4.1:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
@@ -3631,6 +3841,8 @@ snapshots:
entities@6.0.1: {} entities@6.0.1: {}
entities@7.0.0: {}
es-module-lexer@1.7.0: {} es-module-lexer@1.7.0: {}
esast-util-from-estree@2.0.0: esast-util-from-estree@2.0.0:
@@ -4157,6 +4369,10 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
magicast@0.3.5: magicast@0.3.5:
dependencies: dependencies:
'@babel/parser': 7.27.5 '@babel/parser': 7.27.5
@@ -4167,6 +4383,12 @@ snapshots:
markdown-table@3.0.4: {} markdown-table@3.0.4: {}
marked-highlight@2.2.3(marked@16.4.2):
dependencies:
marked: 16.4.2
marked@16.4.2: {}
mdast-util-definitions@6.0.0: mdast-util-definitions@6.0.0:
dependencies: dependencies:
'@types/mdast': 4.0.4 '@types/mdast': 4.0.4
@@ -4713,6 +4935,12 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
prismjs@1.30.0: {} prismjs@1.30.0: {}
prompts@2.4.2: prompts@2.4.2:
@@ -4737,6 +4965,8 @@ snapshots:
readdirp@4.1.2: {} readdirp@4.1.2: {}
recaptcha-v3@1.11.3: {}
recma-build-jsx@1.0.0: recma-build-jsx@1.0.0:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8
@@ -5191,6 +5421,16 @@ snapshots:
optionalDependencies: optionalDependencies:
vite: 6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1) vite: 6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)
vue@3.5.26(typescript@5.8.3):
dependencies:
'@vue/compiler-dom': 3.5.26
'@vue/compiler-sfc': 3.5.26
'@vue/runtime-dom': 3.5.26
'@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.8.3))
'@vue/shared': 3.5.26
optionalDependencies:
typescript: 5.8.3
web-namespaces@2.0.1: {} web-namespaces@2.0.1: {}
webidl-conversions@3.0.1: {} webidl-conversions@3.0.1: {}

View File

@@ -0,0 +1,64 @@
import { useEffect, useRef } from 'react';
import type { WalineInstance, WalineInitOptions } from '@waline/client';
import { init } from '@waline/client';
import '@waline/client/style';
import '@/styles/waline-custom.css';
import type { Lang } from '@/types/i18n';
export type CommentsProps = Partial<Omit<WalineInitOptions, 'el' | 'serverURL'>> & { lang?: Lang };
export default function Comments({ lang = 'en', ...props }: CommentsProps) {
const walineInstanceRef = useRef<WalineInstance | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const walineLang = lang === 'zh' ? 'zh-CN' : 'en';
useEffect(() => {
if (!containerRef.current) return;
const getTheme = () => document.documentElement.classList.contains('dark') ? 'html.dark' : false;
const pathname = window.location.pathname;
const postsMatch = pathname.match(/\/posts\/([^/]+)/);
const path = postsMatch ? postsMatch[1] : pathname;
walineInstanceRef.current = init({
...props,
el: containerRef.current,
serverURL: import.meta.env.PUBLIC_WALINE_SERVER_URL,
lang: walineLang,
dark: getTheme(),
pageview: true,
comment: true,
path,
});
return () => walineInstanceRef.current?.destroy();
}, []);
useEffect(() => {
const getTheme = () => document.documentElement.classList.contains('dark') ? 'html.dark' : false;
walineInstanceRef.current?.update({
...props,
lang: walineLang,
dark: getTheme(),
});
const handleThemeChange = () => {
walineInstanceRef.current?.update({
...props,
lang: walineLang,
dark: getTheme(),
});
};
const observer = new MutationObserver(handleThemeChange);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}, [lang, props, walineLang]);
return <div ref={containerRef} className="waline-container" />;
}

View File

@@ -1,52 +0,0 @@
import Giscus from '@giscus/react';
import type { Lang } from '@/types/i18n';
import { useEffect, useState } from 'react';
export interface GiscusCommentsProps {
lang?: Lang;
}
export default function GiscusComments({ lang = 'en' }: GiscusCommentsProps) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const [term, setTerm] = useState<string>('');
const giscusLang = lang === 'zh' ? 'zh-CN' : lang;
useEffect(() => {
const getTheme = () => document.documentElement.classList.contains('dark') ? 'dark' : 'light';
const pathname = window.location.pathname;
const postsMatch = pathname.match(/\/posts\/([^/]+)/);
const discussionTerm = postsMatch ? postsMatch[1] : pathname;
setTheme(getTheme());
setTerm(discussionTerm);
const observer = new MutationObserver(() => {
setTheme(getTheme());
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}, []);
return (
<Giscus
repo="zguiyang/blog-giscus"
repoId="R_kgDOQ2Wnxw"
category="Announcements"
categoryId="DIC_kwDOQ2Wnx84C0vSJ"
mapping="specific"
term={term}
strict="0"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme={theme}
lang={giscusLang}
loading="lazy"
/>
);
}

View File

@@ -12,7 +12,7 @@ import TableOfContents from '@/components/layout/TableOfContents.astro';
import BlogNavigation from '@/components/layout/BlogNavigation.astro'; import BlogNavigation from '@/components/layout/BlogNavigation.astro';
import PostMeta from '@/components/blog/PostMeta.astro'; import PostMeta from '@/components/blog/PostMeta.astro';
import Container from '../components/ui/Container'; import Container from '../components/ui/Container';
import GiscusComments from '@/components/blog/GiscusComments'; import Comments from '@/components/blog/Comments';
// Use Astro's MarkdownLayoutProps for proper type safety // Use Astro's MarkdownLayoutProps for proper type safety
export type Props = MarkdownLayoutProps<FrontmatterProps>; export type Props = MarkdownLayoutProps<FrontmatterProps>;
@@ -107,7 +107,7 @@ const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : unde
<!-- Comments Section --> <!-- Comments Section -->
<div class="mt-10 sm:mt-16 border-t border-border pt-10"> <div class="mt-10 sm:mt-16 border-t border-border pt-10">
<GiscusComments client:only="react" lang={lang} /> <Comments client:only="react" lang={lang} />
</div> </div>
<!-- Author Card moved to bottom with enhanced styling --> <!-- Author Card moved to bottom with enhanced styling -->

View File

@@ -55,7 +55,7 @@ const tags = extractTags(allPostsArray);
--- ---
<BlogLayout title="博客 - 赵桂阳" description="深入我对编程、技术趋势和开发者生活的思考。探索我的最新文章。"> <BlogLayout title="博客 - Joy Zhao" description="深入我对编程、技术趋势和开发者生活的思考。探索我的最新文章。">
<main class="min-h-screen"> <main class="min-h-screen">
<!-- 头部区域 --> <!-- 头部区域 -->
<Container client:load className="pt-24 pb-12"> <Container client:load className="pt-24 pb-12">

View File

@@ -0,0 +1,342 @@
/**
* Waline Custom Styles
* 适配网站主题风格 - 紫色渐变主题
*/
/* ========== 基础变量覆盖 ========== */
:root {
/* 主题色 - 使用网站的紫色渐变accent */
--waline-theme-color: #8B5CF6;
--waline-active-color: #EC4899;
/* 背景色 */
--waline-bg-color: oklch(1 0 0);
--waline-bg-color-light: oklch(0.97 0 0);
--waline-bg-color-hover: oklch(0.93 0 0);
/* 文字颜色 */
--waline-color: oklch(0.4 0 0);
--waline-light-grey: oklch(0.65 0 0);
--waline-dark-grey: oklch(0.3 0 0);
/* 边框颜色 */
--waline-border-color: oklch(0.87 0 0);
/* 其他颜色 */
--waline-badge-color: #8B5CF6;
--waline-info-bg-color: oklch(0.97 0 0);
--waline-info-color: oklch(0.55 0 0);
--waline-bq-color: oklch(0.93 0 0);
/* 代码块背景 */
--waline-code-bg-color: oklch(0.25 0 0);
/* 圆角 - 与网站一致 */
--waline-border-radius: var(--radius, 0.75rem);
/* 阴影 */
--waline-box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.08));
/* 头像圆角 - 使用网站风格 */
--waline-avatar-radius: var(--radius-md, 0.5rem);
/* 按钮样式 */
--waline-btn-radius: var(--radius, 0.75rem);
}
/* ========== 暗黑模式 ========== */
.dark {
--waline-bg-color: oklch(0.205 0 0);
--waline-bg-color-light: oklch(0.269 0 0);
--waline-bg-color-hover: oklch(0.32 0 0);
--waline-color: oklch(0.9 0 0);
--waline-light-grey: oklch(0.6 0 0);
--waline-dark-grey: oklch(0.75 0 0);
--waline-border-color: oklch(1 0 0 / 15%);
--waline-info-bg-color: oklch(0.269 0 0);
--waline-info-color: oklch(0.6 0 0);
--waline-bq-color: oklch(0.269 0 0);
--waline-code-bg-color: oklch(0.12 0 0);
--waline-box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.25));
}
/* ========== 评论面板样式 ========== */
.wl-panel {
border-radius: var(--waline-border-radius) !important;
border: 1px solid var(--waline-border-color) !important;
box-shadow: var(--waline-box-shadow) !important;
background: var(--waline-bg-color) !important;
}
/* ========== 输入框样式 ========== */
.wl-header {
border-bottom-color: var(--waline-border-color) !important;
}
.wl-header label {
color: var(--waline-color) !important;
}
.wl-header input {
background: transparent !important;
}
.wl-editor {
background: var(--waline-bg-color-light) !important;
border-radius: var(--waline-btn-radius) !important;
}
.wl-editor:focus {
background: var(--waline-bg-color-hover) !important;
}
/* ========== 按钮样式 ========== */
.wl-btn {
border-radius: var(--waline-btn-radius) !important;
font-size: 0.875em !important;
transition: all 0.2s ease !important;
}
.wl-btn:hover {
border-color: var(--waline-theme-color) !important;
color: var(--waline-theme-color) !important;
background: var(--waline-bg-color-light) !important;
}
.wl-btn.primary {
background: linear-gradient(135deg, #8B5CF6, #EC4899) !important;
border: none !important;
color: white !important;
font-weight: 500 !important;
}
.wl-btn.primary:hover {
background: linear-gradient(135deg, #7C3AED, #DB2777) !important;
border: none !important;
color: white !important;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
/* ========== 头部标签页 ========== */
.wl-header label {
transition: all 0.2s ease !important;
}
.wl-header label:hover {
background: var(--waline-bg-color-light) !important;
}
/* ========== 评论卡片样式 ========== */
.wl-card {
border-bottom-color: var(--waline-border-color) !important;
}
.wl-card:first-child {
margin-inline-start: 0 !important;
}
.wl-nick {
color: var(--waline-color) !important;
}
.wl-time {
color: var(--waline-info-color) !important;
}
.wl-content {
color: var(--waline-color) !important;
line-height: 1.8 !important;
}
/* ========== 徽章样式 ========== */
.wl-badge {
background: linear-gradient(135deg, #8B5CF6, #EC4899) !important;
border: none !important;
color: white !important;
font-size: 0.7em !important;
padding: 2px 8px !important;
border-radius: 9999px !important;
}
/* ========== 操作按钮 ========== */
.wl-action {
color: var(--waline-light-grey) !important;
transition: all 0.2s ease !important;
}
.wl-action:hover {
color: var(--waline-theme-color) !important;
transform: scale(1.1);
}
.wl-action.active {
color: var(--waline-active-color) !important;
}
/* ========== 评论操作 ========== */
.wl-delete:hover,
.wl-like:hover,
.wl-reply:hover,
.wl-edit:hover {
color: var(--waline-theme-color) !important;
}
.wl-delete.active,
.wl-like.active,
.wl-reply.active,
.wl-edit.active {
color: var(--waline-active-color) !important;
}
/* ========== 排序按钮 ========== */
.wl-sort li {
color: var(--waline-info-color) !important;
transition: all 0.2s ease !important;
}
.wl-sort li:hover {
color: var(--waline-theme-color) !important;
}
.wl-sort li.active {
color: var(--waline-theme-color) !important;
font-weight: 600;
}
/* ========== 表情弹窗 ========== */
.wl-emoji-popup {
border-radius: var(--waline-border-radius) !important;
box-shadow: var(--waline-box-shadow) !important;
background: var(--waline-bg-color) !important;
border: 1px solid var(--waline-border-color) !important;
}
.wl-emoji-popup .wl-tab.active {
background: var(--waline-bg-color-light) !important;
}
.wl-emoji-popup button:hover {
background: var(--waline-bg-color-hover) !important;
}
/* ========== GIF 弹窗 ========== */
.wl-gif-popup {
border-radius: var(--waline-border-radius) !important;
box-shadow: var(--waline-box-shadow) !important;
background: var(--waline-bg-color) !important;
border: 1px solid var(--waline-border-color) !important;
}
/* ========== 评论统计 ========== */
.wl-count {
color: var(--waline-theme-color) !important;
font-size: 1.5em !important;
font-weight: 700 !important;
}
/* ========== 空状态 ========== */
.wl-empty {
color: var(--waline-info-color) !important;
}
/* ========== 头像样式 ========== */
.wl-avatar {
border-radius: var(--waline-avatar-radius) !important;
border: 2px solid var(--waline-border-color) !important;
box-shadow: var(--waline-box-shadow) !important;
}
/* ========== 回复引用框 ========== */
.wl-quote {
border-left-color: var(--waline-border-color) !important;
}
/* ========== 预览区域 ========== */
.wl-preview .wl-content {
background: var(--waline-bg-color-light) !important;
border-radius: var(--waline-btn-radius) !important;
}
/* ========== 反应区域 ========== */
.wl-reaction-title {
color: var(--waline-color) !important;
}
.wl-reaction-item.active .wl-reaction-text {
color: var(--waline-theme-color) !important;
}
.wl-reaction-votes {
border-color: var(--waline-theme-color) !important;
color: var(--waline-theme-color) !important;
}
.wl-reaction-item.active .wl-reaction-votes {
background: var(--waline-theme-color) !important;
color: white !important;
}
/* ========== 加载动画 ========== */
.wl-loading svg {
color: var(--waline-theme-color) !important;
}
/* ========== 滚动条样式 ========== */
[data-waline] ::-webkit-scrollbar {
width: 6px;
height: 6px;
}
[data-waline] ::-webkit-scrollbar-track-piece {
background: var(--waline-bg-color-light);
border-radius: 3px;
}
[data-waline] ::-webkit-scrollbar-thumb {
background: var(--waline-theme-color);
border-radius: 3px;
}
[data-waline] ::-webkit-scrollbar-thumb:hover {
background: var(--waline-active-color);
}
/* ========== 链接样式 ========== */
[data-waline] a {
color: var(--waline-theme-color) !important;
text-decoration: none !important;
transition: all 0.2s ease !important;
}
[data-waline] a:hover {
color: var(--waline-active-color) !important;
}
/* ========== 代码块样式 ========== */
.wl-content pre,
.wl-content pre[class*="language-"] {
background: var(--waline-code-bg-color) !important;
border-radius: var(--waline-btn-radius) !important;
}
/* ========== 移动端适配 ========== */
@media (max-width: 640px) {
.wl-panel {
border-radius: var(--radius-md, 0.5rem) !important;
}
.wl-btn {
font-size: 0.8em !important;
padding: 0.4em 0.8em !important;
}
.wl-editor {
min-height: 6em !important;
}
}