├── .github └── dependabot.yml ├── .gitignore ├── .husky └── _ │ ├── pre-commit │ └── prepare-commit-msg ├── .prettierignore ├── .prettierrc.mjs ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── astro.config.mjs ├── bun.lock ├── eslint.config.cjs ├── lefthook.yml ├── package.json ├── public ├── avatar.png ├── fonts │ └── hwmc.otf ├── images │ └── page-meta │ │ ├── en │ │ ├── about.png │ │ ├── archive.png │ │ └── links.png │ │ └── zh │ │ ├── about.png │ │ ├── archive.png │ │ └── links.png ├── links │ └── astro.svg ├── noise.png └── preview.png ├── src ├── components │ ├── astro │ │ ├── comments.astro │ │ ├── footer.astro │ │ ├── header.astro │ │ ├── intro.astro │ │ ├── link-card.astro │ │ ├── nav.astro │ │ ├── post-list.astro │ │ ├── recent-blogs.astro │ │ ├── tag.astro │ │ └── twikoo.astro │ └── react │ │ ├── language-toggle.tsx │ │ ├── noise-background.tsx │ │ └── theme-toggle.tsx ├── config │ ├── en │ │ ├── about.mdx │ │ ├── intro.mdx │ │ └── links.mdx │ ├── index.ts │ ├── links.ts │ └── zh │ │ ├── about.mdx │ │ ├── intro.mdx │ │ └── links.mdx ├── content.config.ts ├── content │ └── posts │ │ ├── en │ │ ├── post-1.md │ │ ├── post-2.md │ │ ├── post-3.md │ │ ├── post-4.md │ │ ├── post-5.md │ │ ├── post-6.md │ │ ├── post-7.md │ │ └── post-8.md │ │ └── zh │ │ ├── post-1.mdx │ │ ├── post-2.md │ │ ├── post-3.md │ │ ├── post-4.md │ │ ├── post-5.md │ │ ├── post-6.md │ │ ├── post-7.md │ │ └── post-8.md ├── i18n │ ├── ui.ts │ └── utils.ts ├── layouts │ ├── base.astro │ └── main.astro ├── pages │ ├── 404.astro │ ├── [lang] │ │ ├── about │ │ │ └── index.astro │ │ ├── archive │ │ │ └── index.astro │ │ ├── index.astro │ │ ├── links │ │ │ └── index.astro │ │ ├── posts │ │ │ └── [...slug].astro │ │ ├── rss.xml.ts │ │ └── tags │ │ │ └── [tag].astro │ ├── index.astro │ ├── og │ │ └── [...route].ts │ └── rss.xml.ts ├── styles │ ├── post.css │ ├── post.scss │ ├── tailwind.css │ ├── twikoo.css │ └── view-transition.css ├── types │ └── twikoo.d.ts └── utils │ ├── index.ts │ └── langs.ts ├── tailwind.config.mjs └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | # astro tmp pages 27 | astro_tmp_pages_* 28 | -------------------------------------------------------------------------------- /.husky/_/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif bundle exec lefthook -h >/dev/null 2>&1 37 | then 38 | bundle exec lefthook "$@" 39 | elif yarn lefthook -h >/dev/null 2>&1 40 | then 41 | yarn lefthook "$@" 42 | elif pnpm lefthook -h >/dev/null 2>&1 43 | then 44 | pnpm lefthook "$@" 45 | elif swift package plugin lefthook >/dev/null 2>&1 46 | then 47 | swift package --disable-sandbox plugin lefthook "$@" 48 | elif command -v mint >/dev/null 2>&1 49 | then 50 | mint run csjones/lefthook-plugin "$@" 51 | else 52 | echo "Can't find lefthook in PATH" 53 | fi 54 | fi 55 | } 56 | 57 | call_lefthook run "pre-commit" "$@" 58 | -------------------------------------------------------------------------------- /.husky/_/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif bundle exec lefthook -h >/dev/null 2>&1 37 | then 38 | bundle exec lefthook "$@" 39 | elif yarn lefthook -h >/dev/null 2>&1 40 | then 41 | yarn lefthook "$@" 42 | elif pnpm lefthook -h >/dev/null 2>&1 43 | then 44 | pnpm lefthook "$@" 45 | elif swift package plugin lefthook >/dev/null 2>&1 46 | then 47 | swift package --disable-sandbox plugin lefthook "$@" 48 | elif command -v mint >/dev/null 2>&1 49 | then 50 | mint run csjones/lefthook-plugin "$@" 51 | else 52 | echo "Can't find lefthook in PATH" 53 | fi 54 | fi 55 | } 56 | 57 | call_lefthook run "prepare-commit-msg" "$@" 58 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # lockfiles 2 | package-lock.json 3 | pnpm-lock.yaml 4 | 5 | # dependencies 6 | node_modules -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | export default { 3 | semi: false, 4 | singleQuote: false, 5 | trailingComma: "all", 6 | endOfLine: "lf", 7 | plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"], 8 | overrides: [ 9 | { 10 | files: "*.astro", 11 | options: { 12 | parser: "astro", 13 | }, 14 | }, 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "astro-build.astro-vscode", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "unifiedjs.vscode-mdx" 7 | ], 8 | "unwantedRecommendations": [] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "biome.enabled": false, 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "[astro]": { 6 | "editor.defaultFormatter": "astro-build.astro-vscode" 7 | }, 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": "explicit", 10 | "source.organizeImports": "always" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) sun0225SUN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Air 2 | 3 | A minimalism, personal blog theme for Astro. 4 | 5 | > If you find this project helpful, please consider giving it a star ⭐️ 6 | 7 | [![Built with Astro](https://astro.badg.es/v1/built-with-astro/tiny.svg)](https://astro.build) [![Netlify Status](https://api.netlify.com/api/v1/badges/3e2c71b9-071f-4846-9321-41c949134ebf/deploy-status)](https://app.netlify.com/sites/astro-air/deploys) 8 | 9 | Astro Air 10 | 11 | ## Showcase 12 | 13 | - [Astro Air](https://astro-air.guoqi.dev) 14 | - [Guoqi's blog](https://blog.sunguoqi.com) 15 | - ... 16 | 17 | > welcome to add your own blog to the list ❤️ 18 | 19 | ## Features 20 | 21 | - [x] 🌓 Dark mode support 22 | - [x] 📱 Fully device responsive 23 | - [x] 🎨 Clean and minimalist design 24 | - [x] 📝 Markdown/MDX for content authoring 25 | - [x] 🏄‍♂️ SSG static rendering, SEO friendly 26 | - [x] 🌐 i18n support (EN/ZH) 27 | - [x] 🔗 Social media integration 28 | - [x] 📰 RSS feed & sitemap support 29 | - [x] 🛠️ Google analysis integration 30 | - [x] 💬 Commenting Integration (Twikoo) 31 | - [x] 🎨 Enhance Transition and Animation 32 | - [ ] 🔍 Local search functionality 33 | - [ ] ...and more 34 | 35 | ## Quick Start 36 | 37 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/sun0225SUN/astro-air) 38 | 39 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/sun0225SUN/astro-air) 40 | 41 | ## Configuration 42 | 43 | - Open `src/config/index.ts` and customize your site settings 44 | 45 | - Open `src/config/links.ts` and customize your site links 46 | 47 | - Open `src/config/zh(en)/about.mdx(intro.mdx、links.mdx)` and customize your pages content 48 | 49 | ## Writing Content 50 | 51 | 1. Create new blog posts in the `src/content/posts/` directory 52 | 2. Use the following frontmatter template: 53 | 54 | ```markdown 55 | --- 56 | title: "Your Post Title" 57 | description: "A brief description of your post" 58 | pubDate: YYYY-MM-DD 59 | updatedDate(optional): YYYY-MM-DD 60 | tags(optional): ["tag1", "tag2"] 61 | ogImage(optional): "cover image URL" 62 | --- 63 | 64 | Your content here... 65 | ``` 66 | 67 | ## Update Theme 68 | 69 | ```bash 70 | git remote add upstream https://github.com/sun0225SUN/astro-air 71 | 72 | git fetch upstream 73 | 74 | git merge upstream/main --allow-unrelated-histories 75 | 76 | ``` 77 | 78 | ## Contributing 79 | 80 | Contributions are welcome! Feel free to: 81 | 82 | 1. Fork the repository 83 | 2. Create your feature branch 84 | 3. Submit a pull request 85 | 86 | ```bash 87 | git clone https://github.com/sun0225SUN/astro-air 88 | 89 | cd astro-air 90 | 91 | pnpm install 92 | 93 | pnpm dev 94 | ``` 95 | 96 | ## License 97 | 98 | This project is licensed under the MIT License - see the LICENSE file for details. 99 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import mdx from "@astrojs/mdx" 2 | import react from "@astrojs/react" 3 | import sitemap from "@astrojs/sitemap" 4 | import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections" 5 | import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers" 6 | import tailwindcss from "@tailwindcss/vite" 7 | import expressiveCode from "astro-expressive-code" 8 | import { defineConfig } from "astro/config" 9 | 10 | import robotsTxt from "astro-robots-txt" 11 | 12 | // https://astro.build/config 13 | export default defineConfig({ 14 | output: "static", 15 | prefetch: true, 16 | site: "https://astro-air.guoqi.dev", 17 | vite: { 18 | plugins: [tailwindcss()], 19 | }, 20 | integrations: [ 21 | react(), 22 | sitemap(), 23 | expressiveCode({ 24 | plugins: [pluginCollapsibleSections(), pluginLineNumbers()], 25 | themes: ["material-theme-lighter", "material-theme-darker"], 26 | defaultProps: { 27 | showLineNumbers: true, 28 | }, 29 | }), 30 | mdx(), 31 | robotsTxt(), 32 | ], 33 | }) 34 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const eslintPluginAstro = require("eslint-plugin-astro") 2 | module.exports = [ 3 | // add more generic rule sets here, such as: 4 | // js.configs.recommended, 5 | ...eslintPluginAstro.configs["flat/recommended"], // In CommonJS, the `flat/` prefix is required. 6 | { 7 | rules: { 8 | // override/add rules settings here, such as: 9 | // "astro/no-set-html-directive": "error" 10 | }, 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | prettier-js: 4 | glob: "*.{js,jsx,ts,tsx,astro}" 5 | run: npx prettier --write {staged_files} 6 | eslint-fix: 7 | glob: "*.{js,jsx,ts,tsx,astro}" 8 | run: npx eslint --fix {staged_files} 9 | eslint: 10 | glob: "*.{js,jsx,ts,tsx,astro}" 11 | run: npx eslint {staged_files} 12 | prettier-other: 13 | glob: "*.{json,css,md}" 14 | run: npx prettier --write {staged_files} 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-air", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "build": "astro check && astro build", 8 | "preview": "astro preview", 9 | "astro": "astro", 10 | "format": "prettier --write ." 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.9.4", 14 | "@astrojs/mdx": "^4.0.7", 15 | "@astrojs/react": "^4.2.0", 16 | "@astrojs/rss": "^4.0.11", 17 | "@astrojs/sitemap": "^3.2.1", 18 | "@expressive-code/plugin-collapsible-sections": "^0.40.1", 19 | "@expressive-code/plugin-line-numbers": "^0.40.1", 20 | "@tailwindcss/vite": "^4.0.3", 21 | "@types/react": "^19.0.8", 22 | "@types/react-dom": "^19.0.3", 23 | "astro": "^5.2.3", 24 | "astro-expressive-code": "^0.40.1", 25 | "astro-google-analytics": "^1.0.3", 26 | "astro-og-canvas": "^0.5.6", 27 | "astro-robots-txt": "^1.0.0", 28 | "canvaskit-wasm": "^0.39.1", 29 | "lefthook": "^1.10.10", 30 | "lucide-react": "^0.479.0", 31 | "react": "^19.0.0", 32 | "react-dom": "^19.0.0", 33 | "tailwindcss": "^4.0.3", 34 | "twikoo": "^1.6.41", 35 | "typescript": "^5.7.3" 36 | }, 37 | "devDependencies": { 38 | "@tailwindcss/typography": "^0.5.16", 39 | "@types/dom-view-transitions": "^1.0.5", 40 | "@types/node": "^22.13.1", 41 | "@types/sanitize-html": "^2.13.0", 42 | "@typescript-eslint/parser": "^8.23.0", 43 | "eslint": "^9.19.0", 44 | "eslint-plugin-astro": "^1.3.1", 45 | "eslint-plugin-jsx-a11y": "^6.10.2", 46 | "prettier": "^3.4.2", 47 | "prettier-plugin-astro": "^0.14.1", 48 | "prettier-plugin-tailwindcss": "^0.6.11", 49 | "sass": "^1.83.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/avatar.png -------------------------------------------------------------------------------- /public/fonts/hwmc.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/fonts/hwmc.otf -------------------------------------------------------------------------------- /public/images/page-meta/en/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/en/about.png -------------------------------------------------------------------------------- /public/images/page-meta/en/archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/en/archive.png -------------------------------------------------------------------------------- /public/images/page-meta/en/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/en/links.png -------------------------------------------------------------------------------- /public/images/page-meta/zh/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/zh/about.png -------------------------------------------------------------------------------- /public/images/page-meta/zh/archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/zh/archive.png -------------------------------------------------------------------------------- /public/images/page-meta/zh/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/images/page-meta/zh/links.png -------------------------------------------------------------------------------- /public/links/astro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/noise.png -------------------------------------------------------------------------------- /public/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sun0225SUN/astro-air/1b4d159f996b155042ceb6dcd45030920f781538/public/preview.png -------------------------------------------------------------------------------- /src/components/astro/comments.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Twikoo from "~/components/astro/twikoo.astro" 3 | import { common } from "~/config" 4 | --- 5 | 6 | {common.comments.enabled && common.comments.twikoo.enabled && } 7 | -------------------------------------------------------------------------------- /src/components/astro/footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const currentYear = new Date().getFullYear() 3 | --- 4 | 5 | 34 | -------------------------------------------------------------------------------- /src/components/astro/header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Rss } from "lucide-react" 3 | import { LanguageToggle } from "~/components/react/language-toggle" 4 | import { ThemeToggle } from "~/components/react/theme-toggle" 5 | import { en, zh } from "~/config" 6 | import { getLangFromUrl } from "~/i18n/utils" 7 | 8 | const lang = getLangFromUrl(Astro.url) 9 | const config = lang === "zh" ? zh : en 10 | --- 11 | 12 |
13 | 14 |
{config.siteName}
15 |
16 | 17 |
18 | { 19 | config.rss && ( 20 | 26 | 27 | 28 | ) 29 | } 30 | { 31 | config.social.map((social) => ( 32 | 41 | )) 42 | } 43 | 44 | 45 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /src/components/astro/intro.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import IntroContentEn from "~/config/en/intro.mdx" 3 | import IntroContentZh from "~/config/zh/intro.mdx" 4 | import { getLangFromUrl } from "~/i18n/utils" 5 | 6 | const lang = getLangFromUrl(Astro.url) 7 | const IntroContent = lang === "zh" ? IntroContentZh : IntroContentEn 8 | --- 9 | 10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /src/components/astro/link-card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | name: string 4 | link: string 5 | description: string 6 | avatar: string 7 | } 8 | 9 | const { name, link, description, avatar } = Astro.props 10 | --- 11 | 12 | 17 | {name} 22 |
23 | 24 | {name} 25 | 26 | 29 | {description} 30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/components/astro/nav.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { en, zh } from "~/config" 3 | import { getLangFromUrl, useTranslations } from "~/i18n/utils" 4 | 5 | const lang = getLangFromUrl(Astro.url) 6 | const t = useTranslations(lang) 7 | 8 | const { home, archive, custom, links, about } = 9 | lang === "zh" ? zh.navigation : en.navigation 10 | --- 11 | 12 |
15 | { 16 | home && ( 17 | 24 |

{t("nav.home")}

25 |
26 | ) 27 | } 28 | { 29 | archive && ( 30 | 37 |

{t("nav.archive")}

38 |
39 | ) 40 | } 41 | { 42 | custom?.map((tab) => ( 43 | 51 |

{tab.label}

52 |
53 | )) 54 | } 55 | { 56 | links && ( 57 | 64 |

{t("nav.links")}

65 |
66 | ) 67 | } 68 | { 69 | about && ( 70 | 77 |

{t("nav.about")}

78 |
79 | ) 80 | } 81 |
82 | -------------------------------------------------------------------------------- /src/components/astro/post-list.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | post: any 4 | lang: string 5 | dateFormat?: string 6 | dateWidth?: string 7 | } 8 | 9 | const { post, lang, dateFormat = "default", dateWidth = "w-36" } = Astro.props 10 | import { formatDate } from "~/utils" 11 | --- 12 | 13 | 17 |

20 | 21 |

22 |

{post.data.title}

23 |
24 | -------------------------------------------------------------------------------- /src/components/astro/recent-blogs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { common } from "~/config" 3 | import { getLangFromUrl, useTranslations } from "~/i18n/utils" 4 | import { getPostsByLocale } from "~/utils" 5 | import PostList from "./post-list.astro" 6 | 7 | const lang = getLangFromUrl(Astro.url) 8 | const t = useTranslations(lang) 9 | 10 | const allPosts = await getPostsByLocale(lang) 11 | const posts = allPosts.slice(0, common.latestPosts) 12 | --- 13 | 14 |
{t("blog.latest")}
15 | { 16 | posts.map((post: any) => ( 17 | 23 | )) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/astro/tag.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getLangFromUrl, useTranslations } from "~/i18n/utils" 3 | import MainLayout from "~/layouts/main.astro" 4 | import PostList from "./post-list.astro" 5 | 6 | interface Props { 7 | posts: any[] 8 | tag: string 9 | } 10 | 11 | const { posts, tag } = Astro.props 12 | 13 | const lang = getLangFromUrl(Astro.url) 14 | const t = useTranslations(lang) 15 | 16 | const filteredPosts = posts.filter((post: any) => post.data.tags?.includes(tag)) 17 | --- 18 | 19 | 20 |
#{tag}
21 | 22 | { 23 | filteredPosts.length > 0 ? ( 24 |
    25 | {filteredPosts.map((post: any) => ( 26 | 32 | ))} 33 |
34 | ) : ( 35 |

{t("tag.no_posts")}

36 | ) 37 | } 38 |
39 | -------------------------------------------------------------------------------- /src/components/astro/twikoo.astro: -------------------------------------------------------------------------------- 1 |
2 | 3 | 24 | -------------------------------------------------------------------------------- /src/components/react/language-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Languages } from "lucide-react" 2 | 3 | export function LanguageToggle() { 4 | const handleLanguageToggle = () => { 5 | const currentPath = window.location.pathname 6 | 7 | const newPath = currentPath.includes("/en") 8 | ? currentPath.replace("/en", "/zh") 9 | : currentPath.replace("/zh", "/en") 10 | 11 | window.location.href = newPath 12 | } 13 | 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /src/components/react/noise-background.tsx: -------------------------------------------------------------------------------- 1 | export function NoiseBackground() { 2 | return ( 3 |
4 | ) 5 | } 6 | -------------------------------------------------------------------------------- /src/components/react/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Moon, Sun } from "lucide-react" 2 | 3 | export function ThemeToggle() { 4 | const updateTheme = () => { 5 | const isDark = document.documentElement.classList.contains("dark") 6 | localStorage.setItem("theme", isDark ? "dark" : "light") 7 | document.documentElement.setAttribute( 8 | "data-theme", 9 | isDark ? "material-theme-darker" : "material-theme-lighter", 10 | ) 11 | } 12 | 13 | const handleToggleClick = () => { 14 | const element = document.documentElement 15 | 16 | // if not supported, just toggle the theme 17 | if (!document.startViewTransition) { 18 | element.classList.toggle("dark") 19 | updateTheme() 20 | return 21 | } 22 | 23 | document.startViewTransition(() => { 24 | element.classList.toggle("dark") 25 | updateTheme() 26 | }) 27 | } 28 | 29 | return ( 30 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/config/en/about.mdx: -------------------------------------------------------------------------------- 1 | export const title = "Hello, I'm Guoqi Sun ~" 2 | 3 |

{title}

4 | 5 | 9 | 13 | -------------------------------------------------------------------------------- /src/config/en/intro.mdx: -------------------------------------------------------------------------------- 1 |
2 |

3 | 🖥️ Front-end developer (React)|📸 Photography enthusiast (Nikon)|🛸 Travel 4 | explorer (experiencer)|🚴 Cycling freestyler (Java SILURO6-TOP 6)|🍎 5 | Technology product enthusiast (Apple & Xiaomi) 6 |

7 |

💬 Try, fail, retry. That's the rhythm of growth.

8 |
9 | -------------------------------------------------------------------------------- /src/config/en/links.mdx: -------------------------------------------------------------------------------- 1 | export const title = "My Link" 2 | 3 |

{title}

4 | 5 | ```js 6 | name: "Guoqi Sun", 7 | description: "Try, fail, retry. That's the rhythm of growth.", 8 | link: "https://blog.sunguoqi.com", 9 | avatar: "https://assets.guoqi.dev/images/avatar.png", 10 | ``` 11 | 12 |

13 | Want to add a friend link? Please leave a comment in the comment area below, 14 | and I will add it as soon as possible. 15 |

16 | 17 | ```js 18 | name: "your site name", 19 | description: "your site description", 20 | link: "your site link", 21 | avatar: "your site avatar", 22 | ``` 23 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { Github, Twitter } from "lucide-react" 2 | 3 | export const defaultLanguage: string = "en" 4 | 5 | export const common = { 6 | domain: "https://astro-air.guoqi.dev", 7 | meta: { 8 | favicon: "/avatar.png", 9 | url: "https://blog.sunguoqi.com", 10 | }, 11 | googleAnalyticsId: "", 12 | social: [ 13 | { 14 | icon: Twitter, 15 | label: "X", 16 | link: "https://x.com/sun0225SUN", 17 | }, 18 | { 19 | icon: Github, 20 | label: "GitHub", 21 | link: "https://github.com/sun0225SUN", 22 | }, 23 | ], 24 | rss: true, 25 | navigation: { 26 | home: true, 27 | archive: true, 28 | custom: [ 29 | { 30 | label: "CamLife", 31 | link: "https://camlife.cn", 32 | }, 33 | ], 34 | links: true, 35 | about: true, 36 | }, 37 | latestPosts: 8, 38 | comments: { 39 | enabled: true, 40 | twikoo: { 41 | enabled: true, 42 | // replace with your own envId 43 | envId: import.meta.env.PUBLIC_TWIKOO_ENV_ID ?? "", 44 | }, 45 | }, 46 | } 47 | 48 | export const zh = { 49 | ...common, 50 | siteName: "小孙同学", 51 | meta: { 52 | ...common.meta, 53 | title: "小孙同学", 54 | slogan: "一个浪漫的理性主义者", 55 | description: "读书、摄影、编程、旅行", 56 | }, 57 | navigation: { 58 | ...common.navigation, 59 | custom: [ 60 | { 61 | label: "影集", 62 | link: "https://camlife.cn", 63 | }, 64 | ], 65 | }, 66 | pageMeta: { 67 | archive: { 68 | title: "归档", 69 | description: "小孙同学的所有文章", 70 | ogImage: "/images/page-meta/zh/archive.png", 71 | }, 72 | links: { 73 | title: "朋友们", 74 | description: "小孙同学的和他朋友们", 75 | ogImage: "/images/page-meta/zh/links.png", 76 | }, 77 | about: { 78 | title: "关于我", 79 | description: "小孙同学的自我介绍", 80 | ogImage: "/images/page-meta/zh/about.png", 81 | }, 82 | }, 83 | } 84 | 85 | export const en = { 86 | ...common, 87 | siteName: "Guoqi Sun", 88 | meta: { 89 | ...common.meta, 90 | title: "Guoqi Sun", 91 | slogan: "A Romantic Rationalist", 92 | description: "Reading, Photography, Programming, Traveling", 93 | }, 94 | navigation: { 95 | ...common.navigation, 96 | custom: [ 97 | { 98 | label: "CamLife", 99 | link: "https://camlife.cn", 100 | }, 101 | ], 102 | }, 103 | pageMeta: { 104 | archive: { 105 | title: "All Posts", 106 | description: "Here are Guoqi Sun's all posts", 107 | ogImage: "/images/page-meta/en/archive.png", 108 | }, 109 | links: { 110 | title: "My Friends", 111 | description: "Here are Guoqi Sun's friends", 112 | ogImage: "/images/page-meta/en/links.png", 113 | }, 114 | about: { 115 | title: "About Me", 116 | description: "Here is Guoqi Sun's self-introduction", 117 | ogImage: "/images/page-meta/en/about.png", 118 | }, 119 | }, 120 | } 121 | -------------------------------------------------------------------------------- /src/config/links.ts: -------------------------------------------------------------------------------- 1 | export const links = [ 2 | { 3 | name: "Astro", 4 | link: "https://astro.build", 5 | description: "The web framework for content-driven websites", 6 | avatar: "/links/astro.svg", 7 | }, 8 | { 9 | name: "Guoqi Sun", 10 | link: "https://blog.sunguoqi.com", 11 | description: "Try, fail, retry. That's the rhythm of growth.", 12 | avatar: "https://assets.guoqi.dev/images/avatar.png", 13 | }, 14 | ] 15 | -------------------------------------------------------------------------------- /src/config/zh/about.mdx: -------------------------------------------------------------------------------- 1 | export const title = "你好,我是小孙同学~" 2 | 3 |

{title}

4 | 5 | 9 | 13 | -------------------------------------------------------------------------------- /src/config/zh/intro.mdx: -------------------------------------------------------------------------------- 1 |
2 |

3 | 🖥️ 前端小学生|📸 摄影爱好者|🛸 旅行探索家|🚴 骑行蹭风选手|🍎 4 | 科技产品发烧友 5 |

6 |

💬 路虽远行则将至,事虽难做则必成!

7 |
8 | -------------------------------------------------------------------------------- /src/config/zh/links.mdx: -------------------------------------------------------------------------------- 1 | export const title = "我的链接" 2 | 3 |

{title}

4 | 5 | ```json 6 | name: "Guoqi Sun" 7 | description: "Try, fail, retry. That's the rhythm of growth." 8 | link: "https://blog.sunguoqi.com" 9 | avatar: "https://assets.guoqi.dev/images/avatar.png" 10 | ``` 11 | 12 |

13 | 想要添加友情链接?请按照如下格式在评论区留言,我会及时添加。 14 |

15 | 16 | ```json 17 | name: "your site name" 18 | description: "your site description" 19 | link: "your site link" 20 | avatar: "your site avatar" 21 | ``` 22 | -------------------------------------------------------------------------------- /src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { glob } from "astro/loaders" 2 | import { defineCollection, z } from "astro:content" 3 | 4 | const postSchema = z.object({ 5 | title: z.string(), 6 | description: z.string(), 7 | pubDate: z.coerce.date(), 8 | updatedDate: z.coerce.date().optional(), 9 | heroImage: z.string().optional(), 10 | ogImage: z.string().optional(), 11 | tags: z.array(z.string()).optional(), 12 | }) 13 | 14 | const enPostsCollection = defineCollection({ 15 | loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/posts/en" }), 16 | schema: postSchema, 17 | }) 18 | 19 | const zhPostsCollection = defineCollection({ 20 | loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/posts/zh" }), 21 | schema: postSchema, 22 | }) 23 | 24 | export const collections = { 25 | enPosts: enPostsCollection, 26 | zhPosts: zhPostsCollection, 27 | } 28 | -------------------------------------------------------------------------------- /src/content/posts/en/post-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "My First Blog Post" 3 | pubDate: 2020-07-01 4 | description: "This is the first post of my new Astro blog." 5 | ogImage: "https://sunguoqi.com/me.png" 6 | author: "Astro Learner" 7 | image: 8 | url: "https://docs.astro.build/assets/rose.webp" 9 | alt: "The Astro logo on a dark background with a pink glow." 10 | tags: ["astro", "blogging", "learning-in-public"] 11 | --- 12 | 13 | Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website. 14 | 15 | ## What I've accomplished 16 | 17 | 1. **Installing Astro**: First, I created a new Astro project and set up my online accounts. 18 | 19 | 2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder. 20 | 21 | 3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts! 22 | 23 | ## What's next 24 | 25 | I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come. 26 | -------------------------------------------------------------------------------- /src/content/posts/en/post-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Second Blog Post 3 | author: Astro Learner 4 | description: "After learning some Astro, I couldn't stop!" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "The Astro logo on a dark background with a purple gradient arc." 8 | pubDate: 2020-07-08 9 | tags: ["astro", "blogging", "learning-in-public", "successes"] 10 | --- 11 | 12 | After a successful first week learning Astro, I decided to try some more. I wrote and imported a small component from memory! 13 | -------------------------------------------------------------------------------- /src/content/posts/en/post-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Third Blog Post 3 | author: Astro Learner 4 | description: "I had some challenges, but asking in the community really helped!" 5 | image: 6 | url: "https://docs.astro.build/assets/rays.webp" 7 | alt: "The Astro logo on a dark background with rainbow rays." 8 | pubDate: 2021-07-15 9 | tags: ["astro", "learning-in-public", "setbacks", "community"] 10 | --- 11 | 12 | It wasn't always smooth sailing, but I'm enjoying building with Astro. And, the [Discord community](https://astro.build/chat) is really friendly and helpful! 13 | -------------------------------------------------------------------------------- /src/content/posts/en/post-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Building a Personal Blog with Astro" 3 | author: Astro Learner 4 | description: "Sharing my experience and insights on building a personal blog with Astro" 5 | image: 6 | url: "https://docs.astro.build/assets/full-logo-light.png" 7 | alt: "Astro logo with full text" 8 | pubDate: 2022-07-22 9 | tags: ["astro", "blogging", "web-development", "tech-sharing"] 10 | --- 11 | 12 | Over the past few weeks, I've been building my personal blog using Astro. Astro is a modern static site generator that offers excellent performance and a great development experience. 13 | 14 | ## Why Astro? 15 | 16 | 1. **Lightning-fast page loads**: Astro uses innovative partial hydration techniques 17 | 2. **Great developer experience**: Supports components from React, Vue, Svelte, and more 18 | 3. **Powerful Markdown support**: Makes writing content a breeze 19 | 20 | ## Learnings from the Development Process 21 | 22 | Through this project, I've not only learned how to use Astro but also gained a deeper understanding of many modern frontend development concepts. I plan to continue improving this blog and adding more features. 23 | -------------------------------------------------------------------------------- /src/content/posts/en/post-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Customizing Your Astro Blog Theme" 3 | author: Astro Learner 4 | description: "Learn how to customize your Astro blog theme with Tailwind CSS" 5 | image: 6 | url: "https://docs.astro.build/assets/rays.webp" 7 | alt: "Astro rays on a dark background" 8 | pubDate: 2022-07-29 9 | tags: ["astro", "css", "tailwind", "design"] 10 | --- 11 | 12 | Today I'll share how I customized my Astro blog theme using Tailwind CSS. The combination of Astro and Tailwind makes styling incredibly efficient. 13 | 14 | ## Key Customizations 15 | 16 | 1. **Color Scheme**: Created a custom dark/light mode theme 17 | 2. **Typography**: Set up a consistent type scale 18 | 3. **Layout**: Implemented responsive design patterns 19 | 4. **Components**: Styled reusable UI components 20 | 21 | ## Tips for Theme Development 22 | 23 | - Start with a clear design system 24 | - Use Tailwind's configuration file for custom values 25 | - Leverage CSS variables for dynamic theming 26 | - Test across different devices and screen sizes 27 | -------------------------------------------------------------------------------- /src/content/posts/en/post-6.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Adding Dynamic Features to Static Astro Sites" 3 | author: Astro Learner 4 | description: "Exploring ways to add dynamic features to Astro's static pages" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "Astro arc on dark background" 8 | pubDate: 2023-08-05 9 | tags: ["astro", "javascript", "dynamic-content", "web-development"] 10 | --- 11 | 12 | While Astro excels at static site generation, sometimes we need dynamic features. Here's how to add interactivity without sacrificing performance. 13 | 14 | ## Dynamic Features Added 15 | 16 | 1. **Search Functionality**: Implemented client-side search 17 | 2. **Comment System**: Integrated with a third-party service 18 | 3. **Like Button**: Added interactive reactions 19 | 4. **View Counter**: Track page views 20 | 21 | ## Implementation Details 22 | 23 | The key is using Astro's partial hydration to only load JavaScript where needed. This keeps the site fast while adding necessary dynamic features. 24 | -------------------------------------------------------------------------------- /src/content/posts/en/post-7.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Adding Internationalization to Astro" 3 | author: Astro Learner 4 | description: "A guide to implementing i18n in your Astro website" 5 | image: 6 | url: "https://docs.astro.build/assets/full-logo-light.png" 7 | alt: "Astro logo" 8 | pubDate: 2024-08-19 9 | tags: ["astro", "i18n", "localization", "web-development"] 10 | --- 11 | 12 | Making your website accessible to a global audience is important. Here's how to implement internationalization in Astro. 13 | 14 | ## Implementation Steps 15 | 16 | 1. **Setup**: Installing necessary i18n packages 17 | 2. **Content Structure**: Organizing content for multiple languages 18 | 3. **Language Switching**: Adding a language toggle 19 | 4. **URL Management**: Handling language-specific routes 20 | 21 | ## Best Practices 22 | 23 | - Keep translations in separate files 24 | - Use ISO language codes 25 | - Implement fallback languages 26 | - Consider right-to-left languages 27 | -------------------------------------------------------------------------------- /src/content/posts/en/post-8.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Deploying Your Astro Site" 3 | author: Astro Learner 4 | description: "A comprehensive guide to deploying your Astro site to various platforms" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "Astro deployment illustration" 8 | pubDate: 2024-08-26 9 | tags: ["astro", "deployment", "hosting", "CI CD"] 10 | --- 11 | 12 | Ready to share your Astro site with the world? Let's explore different deployment options and best practices. 13 | 14 | ## Deployment Platforms 15 | 16 | 1. **Netlify**: Perfect for static sites 17 | 2. **Vercel**: Great for serverless functions 18 | 3. **GitHub Pages**: Ideal for project sites 19 | 4. **Self-hosted**: Maximum control 20 | 21 | ## Deployment Process 22 | 23 | We'll walk through the setup process for each platform, including configuration files, environment variables, and continuous deployment workflows. 24 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-1.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "我的第一篇博客文章" 3 | pubDate: 2020-07-01 4 | updatedDate: 2020-07-01 5 | description: "这是我 Astro 博客的第一篇文章。" 6 | author: "Astro 学习者" 7 | image: 8 | url: "https://docs.astro.build/assets/rose.webp" 9 | alt: "在黑色背景上带有粉色光晕的 Astro 标志。" 10 | tags: ["astro", "博客", "公开学习"] 11 | --- 12 | 13 | export const title = "mdx" 14 | 15 | ## {title} 16 | 17 | 欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。 18 | 19 | ## 我做了什么 20 | 21 | 1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。 22 | 23 | 2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。 24 | 25 | 3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了! 26 | 27 | ## 下一步计划 28 | 29 | 我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。 30 | 31 | ```python 32 | print("Hello, World!") 33 | ``` 34 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 我的第二篇博客文章 3 | author: Astro 学习者 4 | description: "在学习了一些 Astro 之后,我无法停止!" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "在深色背景上带有紫色渐变弧形的 Astro 标志。" 8 | pubDate: 2020-07-08 9 | tags: ["astro", "博客", "公开学习", "成功"] 10 | --- 11 | 12 | 在成功的第一周学习 Astro 之后,我决定再试试。我从记忆中编写并导入了一个小组件! 13 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 我的第三篇博客文章 3 | author: Astro 学习者 4 | description: "我遇到了一些挑战,但在社区中提问真的帮助了我!" 5 | image: 6 | url: "https://docs.astro.build/assets/rays.webp" 7 | alt: "在黑色背景上带有彩虹光线的 Astro 标志。" 8 | pubDate: 2021-07-15 9 | tags: ["astro", "公开学习", "挫折", "社区"] 10 | --- 11 | 12 | 事情并不总是一帆风顺,但我很享受使用 Astro 构建的过程。而且,[Discord 社区](https://astro.build/chat)真的很友好和乐于助人! 13 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用 Astro 构建个人博客" 3 | author: Astro 学习者 4 | description: "分享我使用 Astro 构建个人博客的经验和见解" 5 | image: 6 | url: "https://docs.astro.build/assets/full-logo-light.png" 7 | alt: "带有完整文本的 Astro 徽标" 8 | pubDate: 2022-07-22 9 | tags: ["astro", "博客", "网页开发", "技术分享"] 10 | --- 11 | 12 | 在过去的几周里,我一直在使用 Astro 构建我的个人博客。Astro 是一个现代静态网站生成器,提供出色的性能和良好的开发体验。 13 | 14 | ## 为什么选择 Astro? 15 | 16 | 1. **闪电般快速的页面加载**:Astro 使用创新的部分水合技术 17 | 2. **出色的开发者体验**:支持来自 React、Vue、Svelte 等的组件 18 | 3. **强大的 Markdown 支持**:让写作内容变得轻而易举 19 | 20 | ## 开发过程中的学习 21 | 22 | 通过这个项目,我不仅学会了如何使用 Astro,还对许多现代前端开发概念有了更深入的理解。我计划继续改进这个博客并添加更多功能。 23 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "自定义你的 Astro 博客主题" 3 | author: Astro 学习者 4 | description: "学习如何使用 Tailwind CSS 自定义你的 Astro 博客主题" 5 | image: 6 | url: "https://docs.astro.build/assets/rays.webp" 7 | alt: "黑色背景上的 Astro 光线" 8 | pubDate: 2022-07-29 9 | tags: ["astro", "css", "tailwind", "设计"] 10 | --- 11 | 12 | 今天我将分享如何使用 Tailwind CSS 自定义我的 Astro 博客主题。Astro 和 Tailwind 的结合使得样式设计变得非常高效。 13 | 14 | ## 关键自定义 15 | 16 | 1. **颜色方案**: 创建了一个自定义的暗/亮模式主题 17 | 2. **排版**: 设置了一致的字体比例 18 | 3. **布局**: 实现了响应式设计模式 19 | 4. **组件**: 样式化可重用的 UI 组件 20 | 21 | ## 主题开发提示 22 | 23 | - 从清晰的设计系统开始 24 | - 使用 Tailwind 的配置文件进行自定义值 25 | - 利用 CSS 变量进行动态主题 26 | - 在不同设备和屏幕尺寸上进行测试 27 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-6.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "为静态 Astro 网站添加动态功能" 3 | author: Astro Learner 4 | description: "探索为 Astro 的静态页面添加动态功能的方法" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "黑色背景上的 Astro 弧形图" 8 | pubDate: 2023-08-05 9 | tags: ["astro", "javascript", "动态内容", "网页开发"] 10 | --- 11 | 12 | 虽然 Astro 在静态网站生成方面表现出色,但有时我们需要动态功能。以下是如何在不牺牲性能的情况下添加交互性。 13 | 14 | ## 添加的动态功能 15 | 16 | 1. **搜索功能**:实现了客户端搜索 17 | 2. **评论系统**:与第三方服务集成 18 | 3. **点赞按钮**:添加了互动反应 19 | 4. **浏览计数器**:跟踪页面浏览量 20 | 21 | ## 实施细节 22 | 23 | 关键是使用 Astro 的部分水合,只在需要的地方加载 JavaScript。这使得网站保持快速,同时添加必要的动态功能。 24 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-7.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "为 Astro 添加国际化" 3 | author: Astro Learner 4 | description: "在您的 Astro 网站中实现 i18n 的指南" 5 | image: 6 | url: "https://docs.astro.build/assets/full-logo-light.png" 7 | alt: "Astro 标志" 8 | pubDate: 2024-08-19 9 | tags: ["astro", "i18n", "本地化", "网页开发"] 10 | --- 11 | 12 | 让您的网站能够接触到全球受众是很重要的。以下是如何在 Astro 中实现国际化的方法。 13 | 14 | ## 实施步骤 15 | 16 | 1. **设置**:安装必要的 i18n 包 17 | 2. **内容结构**:为多种语言组织内容 18 | 3. **语言切换**:添加语言切换功能 19 | 4. **URL 管理**:处理特定语言的路由 20 | 21 | ## 最佳实践 22 | 23 | - 将翻译保存在单独的文件中 24 | - 使用 ISO 语言代码 25 | - 实现后备语言 26 | - 考虑从右到左的语言 27 | -------------------------------------------------------------------------------- /src/content/posts/zh/post-8.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "部署您的 Astro 网站" 3 | author: Astro 学习者 4 | description: "全面指南,帮助您将 Astro 网站部署到各种平台" 5 | image: 6 | url: "https://docs.astro.build/assets/arc.webp" 7 | alt: "Astro 部署插图" 8 | pubDate: 2024-08-26 9 | tags: ["astro", "i18n", "本地化", "网页开发"] 10 | --- 11 | 12 | 准备好与世界分享您的 Astro 网站了吗?让我们探索不同的部署选项和最佳实践。 13 | 14 | ## 部署平台 15 | 16 | 1. **Netlify**: 适合静态网站 17 | 2. **Vercel**: 适合无服务器函数 18 | 3. **GitHub Pages**: 理想的项目网站 19 | 4. **自托管**: 最大的控制权 20 | 21 | ## 部署过程 22 | 23 | 我们将逐步介绍每个平台的设置过程,包括配置文件、环境变量和持续部署工作流。 24 | -------------------------------------------------------------------------------- /src/i18n/ui.ts: -------------------------------------------------------------------------------- 1 | export const languages = { 2 | en: "English", 3 | zh: "简体中文", 4 | } 5 | 6 | export const defaultLang = "en" 7 | 8 | export const langs = ["en", "zh"] 9 | 10 | export const ui = { 11 | en: { 12 | "nav.home": "Home", 13 | "nav.archive": "Archive", 14 | "nav.about": "About", 15 | "nav.links": "Links", 16 | "blog.latest": "Latest Posts", 17 | "archive.title": "All Posts", 18 | "links.title": "My Friends", 19 | "tag.title": "Tag:", 20 | "tag.no_posts": "No posts found for tag", 21 | }, 22 | zh: { 23 | "nav.home": "首页", 24 | "nav.about": "关于", 25 | "nav.archive": "归档", 26 | "nav.links": "友链", 27 | "links.title": "朋友们", 28 | "blog.latest": "近期文章", 29 | "archive.title": "所有文章", 30 | "tag.title": "标签:", 31 | "tag.no_posts": "没有找到标签为的文章", 32 | }, 33 | } as const 34 | -------------------------------------------------------------------------------- /src/i18n/utils.ts: -------------------------------------------------------------------------------- 1 | import { defaultLang, ui } from "./ui" 2 | 3 | export function getLangFromUrl(url: URL) { 4 | const [, lang] = url.pathname.split("/") 5 | if (lang in ui) return lang as keyof typeof ui 6 | return defaultLang 7 | } 8 | 9 | export function useTranslations(lang: keyof typeof ui) { 10 | return function t(key: keyof (typeof ui)[typeof defaultLang]) { 11 | // @ts-ignore 12 | return ui[lang][key] || ui[defaultLang][key] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/layouts/base.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { NoiseBackground } from "~/components/react/noise-background" 3 | import { getLangFromUrl } from "~/i18n/utils" 4 | import { GoogleAnalytics } from "astro-google-analytics" 5 | import { en, zh } from "~/config" 6 | import { ClientRouter } from "astro:transitions" 7 | 8 | import "~/styles/tailwind.css" 9 | import "~/styles/view-transition.css" 10 | 11 | const lang = getLangFromUrl(Astro.url) 12 | 13 | const { title, description, ogImage } = Astro.props 14 | 15 | const ogImageURL = new URL(ogImage, Astro.site).href 16 | const permalink = new URL(Astro.url.pathname, Astro.site).href 17 | 18 | const config = lang === "zh" ? zh : en 19 | --- 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | { 29 | title 30 | ? `${config.meta.title} - ${title}` 31 | : `${config.meta.title} - ${config.meta.slogan}` 32 | } 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 82 | 83 | 88 | 89 | { 90 | config.googleAnalyticsId && ( 91 | 92 | ) 93 | } 94 | 95 | 96 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/layouts/main.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Comments from "~/components/astro/comments.astro" 3 | import Header from "~/components/astro/header.astro" 4 | import Navigation from "~/components/astro/nav.astro" 5 | import BaseLayout from "~/layouts/base.astro" 6 | 7 | const { title, description, ogImage, needComment } = Astro.props 8 | const filename = Astro.url.pathname.split("/").filter(Boolean).pop() ?? "" 9 | const openGraphImage = !ogImage ? `/og/${filename}.png` : ogImage 10 | --- 11 | 12 | 18 |
19 |
20 | 21 | 22 |
23 | {needComment && } 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /src/pages/404.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import MainLayout from "~/layouts/main.astro" 3 | --- 4 | 5 | 6 |
7 |
8 |

404

9 | 10 |
11 |
Oops! Page not found
12 |

13 | The page you're looking for doesn't exist or has been moved. 14 |

15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/pages/[lang]/about/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { en, zh } from "~/config" 3 | import AboutContentEn from "~/config/en/about.mdx" 4 | import AboutContentZh from "~/config/zh/about.mdx" 5 | import { getLangFromUrl } from "~/i18n/utils" 6 | import MainLayout from "~/layouts/main.astro" 7 | import { getLanguagePaths } from "~/utils/langs" 8 | 9 | export function getStaticPaths() { 10 | return getLanguagePaths() 11 | } 12 | 13 | const lang = getLangFromUrl(Astro.url) 14 | const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta 15 | const AboutContent = lang === "zh" ? AboutContentZh : AboutContentEn 16 | --- 17 | 18 | 24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/pages/[lang]/archive/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import PostList from "~/components/astro/post-list.astro" 3 | import { en, zh } from "~/config" 4 | import { getLangFromUrl } from "~/i18n/utils" 5 | import MainLayout from "~/layouts/main.astro" 6 | import { getPostsByLocale } from "~/utils" 7 | import { getLanguagePaths } from "~/utils/langs" 8 | 9 | const lang = getLangFromUrl(Astro.url) 10 | const pageMeta = lang === "zh" ? zh.pageMeta : en.pageMeta 11 | 12 | export function getStaticPaths() { 13 | return getLanguagePaths() 14 | } 15 | 16 | const posts = await getPostsByLocale(lang) 17 | 18 | const postsByYear = posts.reduce( 19 | (acc: Record, post: any) => { 20 | const year = new Date(post.data.pubDate).getFullYear().toString() 21 | if (!acc[year]) { 22 | acc[year] = [] 23 | } 24 | acc[year].push(post) 25 | return acc 26 | }, 27 | {} as Record, 28 | ) 29 | 30 | const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a)) 31 | --- 32 | 33 | 38 | { 39 | years.map((year) => ( 40 |
41 |

{year}

42 | {postsByYear[year].map((post: any) => ( 43 | 49 | ))} 50 |
51 | )) 52 | } 53 |
54 | -------------------------------------------------------------------------------- /src/pages/[lang]/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Footer from "~/components/astro/footer.astro" 3 | import Intro from "~/components/astro/intro.astro" 4 | import RecentBlogs from "~/components/astro/recent-blogs.astro" 5 | import MainLayout from "~/layouts/main.astro" 6 | import { getLanguagePaths } from "~/utils/langs" 7 | 8 | export function getStaticPaths() { 9 | return getLanguagePaths() 10 | } 11 | --- 12 | 13 | 14 | 15 | 16 |