Compare commits

...

10 Commits

Author SHA1 Message Date
65960f41ba change style of header & download tailwindv4
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 1m19s
2026-05-07 16:39:11 +08:00
701f4700e4 change: src/compoents UI Links
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 1m17s
2026-04-29 14:57:23 +08:00
6837a10f47 upload something new
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 44s
2026-04-22 16:58:59 +08:00
cf7c859d55 fix: use strip_components to avoid nested dist
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 41s
2026-04-22 12:18:51 +08:00
a58d6db78f fix: change value of source again
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 39s
2026-04-22 11:43:24 +08:00
808f328fd0 fix: clean target dir before upload and change the value of source
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 43s
2026-04-22 11:35:59 +08:00
d47d1ae4ef fix: upgrade node to 22
All checks were successful
自动构建并部署Astro博客 / build-and-deploy (push) Successful in 42s
2026-04-22 11:04:54 +08:00
ee48fb79fe test cicd
Some checks failed
自动构建并部署Astro博客 / build-and-deploy (push) Failing after 5m14s
2026-04-22 10:44:09 +08:00
393621776c 提交完整Astro博客源码
Some checks failed
自动构建并部署Astro博客 / build-and-deploy (push) Has been cancelled
2026-04-22 10:33:20 +08:00
houston[bot]
d15c709fd7 "Initial commit from Astro" 2026-04-22 10:33:18 +08:00
40 changed files with 7446 additions and 4 deletions

View File

@@ -19,8 +19,9 @@ jobs:
- name: 安装Node.js - name: 安装Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 22
cache: 'npm' env:
NODEJS_ORG_MIRROR: https://npmmirror.com/mirrors/node
# 3. 安装依赖 + 打包Astro项目生成dist目录 # 3. 安装依赖 + 打包Astro项目生成dist目录
- name: 安装依赖并构建 - name: 安装依赖并构建
@@ -36,6 +37,7 @@ jobs:
username: ${{ secrets.SERVER_USER }} username: ${{ secrets.SERVER_USER }}
port: ${{ secrets.SERVER_PORT }} port: ${{ secrets.SERVER_PORT }}
key: ${{ secrets.SERVER_SSH_KEY }} key: ${{ secrets.SERVER_SSH_KEY }}
source: "dist/*" source: "dist/"
target: "/var/www/blog/" target: "/var/www/blog/"
overwrite: true overwrite: true
strip_components: 1

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/

4
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

63
README.md Normal file
View File

@@ -0,0 +1,63 @@
# Astro Starter Kit: Blog
```sh
npm create astro@latest -- --template blog
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
Features:
- ✅ Minimal styling (make it your own!)
- ✅ 100/100 Lighthouse performance
- ✅ SEO-friendly with canonical URLs and Open Graph data
- ✅ Sitemap support
- ✅ RSS Feed support
- ✅ Markdown & MDX support
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
├── public/
├── src/
│   ├── assets/
│   ├── components/
│   ├── content/
│   ├── layouts/
│   └── pages/
├── astro.config.mjs
├── README.md
├── package.json
└── tsconfig.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
## Credit
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).

42
astro.config.mjs Normal file
View File

@@ -0,0 +1,42 @@
// @ts-check
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import { defineConfig, fontProviders } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
// https://astro.build/config
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
fonts: [
{
provider: fontProviders.local(),
name: 'Atkinson',
cssVariable: '--font-atkinson',
fallbacks: ['sans-serif'],
options: {
variants: [
{
src: ['./src/assets/fonts/atkinson-regular.woff'],
weight: 400,
style: 'normal',
display: 'swap',
},
{
src: ['./src/assets/fonts/atkinson-bold.woff'],
weight: 700,
style: 'normal',
display: 'swap',
},
],
},
},
],
vite: {
plugins: [tailwindcss()],
},
});

6329
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "myblog",
"type": "module",
"version": "0.0.1",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2",
"@tailwindcss/vite": "^4.2.4",
"astro": "^6.1.5",
"lightningcss": "^1.32.0",
"sharp": "^0.34.3",
"tailwindcss": "^4.2.4"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
public/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1777435362830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22652" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 944c-194.56 0-350.08-240-335.04-427.52C188.96 696.48 334.24 896 512 896s323.04-199.52 335.04-379.52C862.08 704 706.4 944 512 944z" fill="#69CC00" p-id="22653"></path><path d="M770.4 240l-42.4 42.72A32 32 0 0 1 688 288a333.44 333.44 0 0 0-352 0 32 32 0 0 1-39.36-4.48L253.6 240c4.64-17.44 4.8-36.8-20.96-62.08a61.12 61.12 0 0 0-102.24 28.64c-16-54.72 54.88-108 102.24-60.64 25.76 25.28 25.6 44.64 20.96 62.08l42.4 42.4A32 32 0 0 0 336 256a333.44 333.44 0 0 1 352 0 32 32 0 0 0 39.36-4.48l41.92-41.92a58.56 58.56 0 0 0 1.12 30.4zM893.92 208a64 64 0 0 0-123.68 0c-21.92-75.52 92.64-113.12 121.12-39.52a64 64 0 0 1 2.56 39.52z" fill="#C5EE09" p-id="22654"></path><path d="M778.4 290.88a32 32 0 0 0-2.4 42.56c52.32 66.24 75.2 151.52 69.6 199.04C826.4 708 684.48 896 512 896S197.6 708 178.4 532.48c-5.6-46.88 16-131.84 69.6-199.04a32 32 0 0 0-2.4-42.56L208 253.6c-17.44 4.64-36.8 4.8-62.08-20.96a62.08 62.08 0 0 1-16-25.76 61.12 61.12 0 0 1 102.24-28.64c25.76 25.28 25.6 44.64 20.96 62.08l42.4 42.4A32 32 0 0 0 336 288a333.44 333.44 0 0 1 352 0 32 32 0 0 0 39.36-4.48L770.4 240a53.12 53.12 0 0 1 0-32 64 64 0 0 1 123.68 0A64 64 0 0 1 832 256c-23.68 0-4.48-14.24-53.6 34.88z" fill="#84E507" p-id="22655"></path><path d="M456.96 727.04c-37.44 37.44-132 3.68-169.44-33.76a151.84 151.84 0 0 1-39.2-126.72c16.96-75.84 128-56.16 174.72-8.96 37.12 37.12 71.52 131.84 33.92 169.44zM736 693.28c-54.4 54.4-208 92.8-183.2-39.68a199.84 199.84 0 0 1 48-96c37.44-37.44 120.8-60 158.24-22.56S773.12 656 736 693.28z" fill="#FDA736" p-id="22656"></path><path d="M470.72 653.76c-18.72 72.64-137.92 36.8-183.2-8.48a156.32 156.32 0 0 1-39.2-78.72c16.96-75.84 128-56.16 174.72-8.96a202.08 202.08 0 0 1 47.68 96.16zM774.72 566.56A156.32 156.32 0 0 1 736 645.28c-46.24 46.24-164.48 80.8-183.2 8.32a199.84 199.84 0 0 1 48-96c46.4-47.36 156.96-67.04 173.92 8.96z" fill="#FED370" p-id="22657"></path><path d="M169.6 459.52a16 16 0 0 1 31.04 7.36C147.84 688 359.68 998.56 592 913.6a16 16 0 0 1 10.08 30.4C347.2 1037.12 109.76 710.24 169.6 459.52zM653.76 916.32a16 16 0 0 1-8.64-29.44c160-101.28 260.64-363.2 118.24-544a48 48 0 0 1 3.68-64C823.68 223.04 802.88 240 832 240a48 48 0 1 0-29.92-85.6 47.04 47.04 0 0 0-16 49.6c3.36 11.84-2.08 13.76-46.4 58.08a48 48 0 0 1-59.2 6.72 317.6 317.6 0 0 0-336 0 48 48 0 0 1-59.2-6.72c-42.24-42.24-49.76-45.76-46.56-57.92s4.64-25.6-16-46.56c-41.92-41.76-105.6 21.92-64 64 36 36.8 46.4 5.44 62.08 20.96l37.28 37.28a48 48 0 0 1 3.68 64 326.88 326.88 0 0 0-40.48 66.24 16 16 0 0 1-29.12-13.12c48-103.84 74.56-62.88 12.16-125.44A84.64 84.64 0 0 1 112 187.68a77.6 77.6 0 0 1 131.84-52.8c27.2 26.88 30.08 49.92 27.04 68.32l36.48 36.8a16 16 0 0 0 19.52 2.24 349.92 349.92 0 0 1 370.24 0 16 16 0 0 0 19.52-2.24c44.48-45.12 35.36-32 35.36-48a80 80 0 1 1 142.56 49.92 77.28 77.28 0 0 1-73.76 29.12l-31.04 31.2a16 16 0 0 0-1.12 21.28A347.68 347.68 0 0 1 864 540.64c0 209.6-175.04 375.68-210.24 375.68z" fill="#35214C" p-id="22658"></path><path d="M409.92 759.2A214.88 214.88 0 0 1 276.16 704c-40.96-40.32-68.16-134.4-22.56-180.32 22.72-22.72 60.32-29.6 103.2-19.04a16 16 0 0 1-7.68 31.04c-114.72-28.32-100 96-50.24 146.24 33.12 33.28 118.24 62.4 146.72 33.76 32-32-5.12-126.88-44.96-156.16a16 16 0 0 1 18.88-25.92c72.16 52.96 115.04 225.6-9.6 225.6zM554.72 738.4c-46.4-46.56-5.92-152.48 33.76-192 43.04-43.04 136.16-67.36 180.96-22.56s18.4 139.84-22.56 180.16a173.44 173.44 0 0 1-40.16 28.64 16 16 0 0 1-15.04-28.16 136.8 136.8 0 0 0 32-23.2c33.92-33.76 51.2-107.04 22.56-135.52s-104.32-8.8-135.52 22.56c-36.8 36.8-92.96 178.4 26.08 156a16 16 0 0 1 6.08 32c-31.04 6.08-65.6 4.48-88.16-17.92zM455.36 817.92a16 16 0 0 1 22.72 0 49.28 49.28 0 0 0 67.84 0 16 16 0 0 1 22.72 22.72c-53.28 53.12-137.92 2.08-113.28-22.72z" fill="#35214C" p-id="22659"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/BladeRunner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
src/assets/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,57 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '../styles/global.css';
import type { ImageMetadata } from 'astro';
import FallbackImage from '../assets/blog-placeholder-1.jpg';
import { SITE_TITLE } from '../consts';
import { Font } from 'astro:assets';
interface Props {
title: string;
description: string;
image?: ImageMetadata;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = FallbackImage } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL('rss.xml', Astro.site)}
/>
<meta name="generator" content={Astro.generator} />
<Font cssVariable="--font-atkinson" preload />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image.src, Astro.url)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image.src, Astro.url)} />

View File

@@ -0,0 +1,57 @@
---
const today = new Date();
---
<footer>
&copy; {today.getFullYear()} Marvin. All rights reserved.
<div class="social-links">
<a href="mailto:marvinmarchok@gmail.com" target="_blank">
<span class="sr-only">Send Marvin an email</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/mastodon"
><path
fill="currentColor"
d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
></path></svg
>
</a>
<a href="https://x.com/Marvinhers" target="_blank">
<span class="sr-only">Follow Marvin on Twitter</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/twitter"
><path
fill="currentColor"
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
></path></svg
>
</a>
<a href="https://github.com/Marvin-March" target="_blank">
<span class="sr-only">Go to Marvin's GitHub repo</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
</footer>
<style>
footer {
padding: 2em 1em 6em 1em;
background: linear-gradient(var(--gray-gradient)) no-repeat;
color: rgb(var(--gray));
text-align: center;
}
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a {
text-decoration: none;
color: rgb(var(--gray));
}
.social-links a:hover {
color: rgb(var(--gray-dark));
}
</style>

View File

@@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View File

@@ -0,0 +1,84 @@
---
import { SITE_TITLE } from '../consts';
import HeaderLink from './HeaderLink.astro';
---
<header class="fixed top-0 left-0 w-full z-50 bg-white/70 backdrop-blur-md shadow-sm h-20">
<nav>
<h2 class="font-bold !text-blue-500">{SITE_TITLE}</h2>
<div class="internal-links">
<HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="/about">About</HeaderLink>
</div>
<div class="social-links">
<a href="mailto:marvinmarchok@gmail.com" target="_blank">
<span class="sr-only">Send Marvin an email</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
><path
fill="currentColor"
d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
></path></svg
>
</a>
<a href="https://x.com/Marvinhers" target="_blank">
<span class="sr-only">Follow Marvin on Twitter</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
><path
fill="currentColor"
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
></path></svg
>
</a>
<a href="https://github.com/Marvin-March" target="_blank">
<span class="sr-only">Go to Marvin's GitHub repo</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
><path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg
>
</a>
</div>
</nav>
</header>
<style>
header {
margin: 0;
padding: 0 1em;
box-shadow: 0 2px 8px rgba(var(--black), 5%);
}
h2 {
margin: 0;
font-size: 1em;
}
h2 a,
h2 a.active {
text-decoration: none;
}
nav {
display: flex;
align-items: center;
justify-content: space-between;
}
nav a {
padding: 1em 0.5em;
color: var(--black);
border-bottom: 4px solid transparent;
text-decoration: none;
}
nav a.active {
text-decoration: none;
border-bottom-color: var(--accent);
}
.social-links,
.social-links a {
display: flex;
}
@media (max-width: 720px) {
.social-links {
display: none;
}
}
</style>

View File

@@ -0,0 +1,24 @@
---
import type { HTMLAttributes } from 'astro/types';
type Props = HTMLAttributes<'a'>;
const { href, class: className, ...props } = Astro.props;
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
---
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
<style>
a {
display: inline-block;
text-decoration: none;
}
a.active {
font-weight: bolder;
text-decoration: underline;
}
</style>

2
src/consts.ts Normal file
View File

@@ -0,0 +1,2 @@
export const SITE_TITLE = 'Marvin Blog';
export const SITE_DESCRIPTION = 'Welcome to my website!';

21
src/content.config.ts Normal file
View File

@@ -0,0 +1,21 @@
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.optional(image()),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };

View File

@@ -0,0 +1,16 @@
---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 08 2022'
heroImage: '../../assets/blog-placeholder-3.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,177 @@
---
title: '部署了人生中第一个博客'
description: 'Astro 博客部署'
pubDate: 'Apr 22 2026'
heroImage: '../../assets/BladeRunner.png'
---
## 在真正部署前我干了啥😫(可以不看)
>由于本人急切想实现外网访问且不知为何偏好Apache于是很早之前就把本地项目以下载的形式放在了服务器上而不是git push。且gitea仓库里也是zip文件😫还写了如下文字。千万不要这样。
1. 把本地的Astro项目打包。根目录里输入
```bash
npm run build
```
会生成一个`dist`文件夹,即我们的博客成品
2. 将一整个文件夹传到服务器`/var/www/blog`用的apache我这里绕了一点弯路本来打算先将dist.zip传到gitea,再在服务器端下载,后来发现一种更简单的方式
- 安装lrzsz(Linux下命令行文件传输工具实现本地-服务器互传文件,但不能传文件夹,要打包)
```bash
apt install lrzsz -y
cd /var/www/blog
rz #上传文件到服务器,会弹出窗口
sz #下载
```
- 解压
```bash
unzip -o dist.zip -d /var/www/blog/ #-o 覆盖已有文件
```
我还碰到了问题就是它传成了/var/www/blog/dist使用`mv dist/* .`就可以解决了。
3. 配置Apache
```bash
vim /etc/apache2/sites-available/000-default.conf
#改为 DocumentRoot /var/www/blog
systemctl restart apache2
```
4. 配置HTTPS
用 Certbot 给 Apache 配置免费 Let's Encrypt 证书,一键开启 HTTPS还没有深入理解原理
```bash
apt install certbot python3-certbot-apache -y
certbot --apache -d marvinhers.site
```
并且由于之前在服务器docker里装了gitea,本人不知为何想实现自个网站可登gitea,由此配了子域名git.marvinhers.site这会牵扯到后面的反向代理😅
## 浅谈一下部署过程🤯
成果🫠:[点击访问虽然没什么内容主页也没写好](https://marvinhers.site/about/) [配了个子域名](https://git.marvinhers.site)
>博客用TuF3i推荐的Astro框架nodejs 开发构建代码放在GiteaAct runner 实现cicdweb服务器用的Nginxhttps加了密的。
### 配置Nginx
>同为web服务器软件它们都可以让别人通过网址访问服务器上的网站、接口、文件。但nginx轻量占有内存少并发能力强在负载均衡和反向代理领域表现出众处理静态资源也更👍
针对我这个博客,先不管主配置`/etc/nginx/nginx.conf`,因为它有`include /etc/nginx/sites-enabled/*;`所以主要是修改它的子配置
在`sites-available`下增加了`blog`和`gitea`两个配置文件gitea配的是反向代理虽然这样意义不大但也算是把学的知识运用一下...
```nginx
# 以gitea配置为例
# 仅保留核心80端口 + 子域名 + 反向代理
server {
listen 80;
server_name git.marvinhers.site;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
```bash
# 软链接启用配置
ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
# 测试配置无误
nginx -t
nginx -s reload
```
#后续还要深入学习
[Nginx 服务器安装及配置文件详解 | 菜鸟教程](https://www.runoob.com/w3cnote/nginx-install-and-config.html)
[反向代理](https://mp.weixin.qq.com/s?__biz=MzA5MTkyMTQ0NA==&mid=2247486136&idx=1&sn=fb7ca1c55e681bdb082d3796e9dbbe04&chksm=907447f3a703cee53b7067685c9d710f5f99946c9d09865099647edd061df7ebdbb010bc34fe&scene=178&cur_album_id=3920274935729356806&search_click_id=#rd)
(虽然学长的课还没听完)
预留`acme.sh`
先用certbot简单配一下吧以后使用泛域名证书再改用acme.sh......
所以后面就简单地...
```bash
certbot --nginx -d marvinhers.site -d git.marvinhers.site
```
这样讲起来怎么只有这么一点,鬼知道我弄了多久。
### 在Gitea上实现CICD
第一节课的时候接触了gitea 的 actions配置了一些东西当时确实挺懵的。
再从头顺一遍吧
#### 开启actions功能
```bash
docker exec -it gitea bash
vim /data/gitea/conf/app.ini
# 添加以下然后退出容器
# [actions]
# ENABLED=true
docker restart gitea
```
#### 开启act runner
Act Runner是一个独立的小程序它从 Gitea 那里领任务(比如“有人推送代码了,去执行 .gitea/workflows/deploy.yml 文件里的命令”),然后在服务器上执行这些命令。
推荐是在docker里运行的这里建议看官方文档是如何注册挂载运行。这里重点讲一下==挂载==
==挂载 volume 是实现 服务器 和 Act Runner容器 之间的文件互通==
```yml
# 这里展示一下 /root/runner/docker-compose.yml
# 关于令牌,建议参考官方文档
services:
runner:
image: gitea/act_runner:nightly
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "https://git.marvinhers.site"
GITEA_RUNNER_REGISTRATION_TOKEN: "{Token}"
GITEA_RUNNER_NAME: "marvin-blog-runner"
GITEA_RUNNER_LABELS: "ubuntu-latest,self-hosted"
volumes:
- ./config.yaml:/config.yaml
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
- /var/www/blog:/var/www/blog
```
==令牌Token==是一个注册认证绑定runner到你的Gitea实例连接gitea服务器有不同级别可复用可重置。
我是直接用了实例级别的,仓库级别获取好像出了点问题
#后续再了解
```bash
docker exec -u git -it gitea gitea actions generate-runner-token
```
然后`docker compose down` `docker compose up -d` `docker ps` 就能看到在运行了!
#### 终于workflows
##### 讲几个笨得流黄汤的点(没按顺序):
- 同步到服务器
```yml
- name: 同步到服务器
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
port: ${{ secrets.SERVER_PORT }}
key: ${{ secrets.SERVER_SSH_KEY }}
source: "dist/"
target: "/var/www/blog/"
overwrite: true
strip_components: 1
# 1.那一大堆变量这些都是和服务器相关的尤其ssh_key是在服务器上生成的我记得我之前填的是在本地生成的。
# 2.source 的值和 strip_components 扯上关系。后者让前者剥离dist目录
```
生成密钥的bash
#后续了解
```bash
cd ~/.ssh
ssh-keygen -t rsa -f id_rsa -N ""
cat id_rsa.pub >> authorized_keys
chmod 600 authorized_keys
chmod 700 ~/.ssh
```
- ==git==相关,与我自个有关。
由于之前笨得流黄汤把zip传到了gitea...
然后还有对git命令还不太熟悉...这没啥好说的,肯定是要掌握的东西。💪
#后续还要深入学习
```bash
# 然后简单触发一下工作流
git add .
git commit -m "fix: some mistakes"
git push -u origin main
```
然后其实是碰上各种各样的问题的,包括一些`.gitignore`的书写导致上传巨慢HTTP 413报错上传的代码包体积超过了 Nginx 上限后在Nignx配置的server块下添加`client_max_body_size 100M;`指令等等啊。
## 浅浅总结
啊确实学到了很多但是我的基础不太行像什么docker,git以及文章中一些标签的地方还要深入学习。
然后其实在部署的时候是一个逆向由深入浅的过程碰到问题--再解决,这很不顺,也很浪费时间,所以还是要打好基础🐷以后多多部署东西熟悉熟悉🐷

View File

@@ -0,0 +1,16 @@
---
title: 'Second post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 15 2022'
heroImage: '../../assets/blog-placeholder-4.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,16 @@
---
title: 'Third post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 22 2022'
heroImage: '../../assets/blog-placeholder-2.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -0,0 +1,31 @@
---
title: 'Using MDX'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jun 01 2024'
heroImage: '../../assets/blog-placeholder-5.jpg'
---
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
## Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/integrations-guide/mdx/#mdx-in-astro) for things like interactive charts or alerts.
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
## Example
Here is how you import and use a UI component inside of MDX.
When you open this page in the browser, you should see the clickable button below.
import HeaderLink from '../../components/HeaderLink.astro';
<HeaderLink href="#" onclick="alert('clicked!')">
Embedded component in MDX
</HeaderLink>
## More Links
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
- [Astro Usage Documentation](https://docs.astro.build/en/basics/astro-pages/#markdownmdx-pages)
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.

View File

@@ -0,0 +1,87 @@
---
import { Image } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
import Header from '../components/Header.astro';
import '../styles/global.css';
type Props = CollectionEntry<'blog'>['data'];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main class="pt-20 px-4 max-w-4xl mx-auto">
<article>
<div class="hero-image">
{heroImage && <Image width={1020} height={510} src={heroImage} alt="" />}
</div>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

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

@@ -0,0 +1,16 @@
---
import AboutHeroImage from "../assets/background.jpg";
import Layout from "../layouts/BlogPost.astro";
---
<Layout
title="About Marvin"
description="一个有意思的大学生,正在探索世界..."
pubDate={new Date("April 04 2026")}
heroImage={AboutHeroImage}
>
<p>我是Marvin,一个对 Life IT Money 充满热情的在校大学生。</p>
<p>
这是我开发的首个个人博客请大家见证我的成长我的博客已经实现cicd啦哈哈哈
</p>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import { type CollectionEntry, getCollection, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
</BlogPost>

114
src/pages/blog/index.astro Normal file
View File

@@ -0,0 +1,114 @@
---
import { Image } from 'astro:assets';
import { getCollection } from 'astro:content';
import BaseHead from '../../components/BaseHead.astro';
import Footer from '../../components/Footer.astro';
import FormattedDate from '../../components/FormattedDate.astro';
import Header from '../../components/Header.astro';
import { SITE_DESCRIPTION, SITE_TITLE } from '../../consts';
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 960px;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0;
padding: 0;
}
ul li {
width: calc(50% - 1rem);
}
ul li * {
text-decoration: none;
transition: 0.2s ease;
}
ul li:first-child {
width: 100%;
margin-bottom: 1rem;
text-align: center;
}
ul li:first-child img {
width: 100%;
}
ul li:first-child .title {
font-size: 2.369rem;
}
ul li img {
margin-bottom: 0.5rem;
border-radius: 12px;
}
ul li a {
display: block;
}
.title {
margin: 0;
color: rgb(var(--black));
line-height: 1;
}
.date {
margin: 0;
color: rgb(var(--gray));
}
ul li a:hover h4,
ul li a:hover .date {
color: rgb(var(--accent));
}
ul a:hover img {
box-shadow: var(--box-shadow);
}
@media (max-width: 720px) {
ul {
gap: 0.5em;
}
ul li {
width: 100%;
text-align: center;
}
ul li:first-child {
margin-bottom: 0;
}
ul li:first-child .title {
font-size: 1.563em;
}
}
</style>
</head>
<body>
<Header />
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>
{post.data.heroImage && (
<Image width={720} height={360} src={post.data.heroImage} alt="" />
)}
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>

22
src/pages/index.astro Normal file
View File

@@ -0,0 +1,22 @@
---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro';
import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main>
<h2>主页还没开发</h2>
<p class="font-bold text-blue-500">Hello Tailwind v4!</p>
</main>
<Footer />
</body>
</html>

16
src/pages/rss.xml.js Normal file
View File

@@ -0,0 +1,16 @@
import { getCollection } from 'astro:content';
import rss from '@astrojs/rss';
import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.id}/`,
})),
});
}

141
src/styles/global.css Normal file
View File

@@ -0,0 +1,141 @@
/*
The CSS in this style tag is based off of Bear Blog's default CSS.
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
*/
@import "tailwindcss";
:root {
--accent: #2337ff;
--accent-dark: #000d8a;
--black: 15, 18, 25;
--gray: 96, 115, 159;
--gray-light: 229, 233, 240;
--gray-dark: 34, 41, 57;
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
--box-shadow:
0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
0 16px 32px rgba(var(--gray), 33%);
}
body {
font-family: var(--font-atkinson);
margin: 0;
padding: 0;
text-align: left;
background: linear-gradient(var(--gray-gradient)) no-repeat;
background-size: 100% 600px;
word-wrap: break-word;
overflow-wrap: break-word;
color: rgb(var(--gray-dark));
font-size: 20px;
line-height: 1.7;
}
main {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 3em 1em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 0 0.5rem 0;
color: rgb(var(--black));
line-height: 1.2;
}
h1 {
font-size: 3.052em;
}
h2 {
font-size: 2.441em;
}
h3 {
font-size: 1.953em;
}
h4 {
font-size: 1.563em;
}
h5 {
font-size: 1.25em;
}
strong,
b {
font-weight: 700;
}
a {
color: var(--accent);
}
a:hover {
color: var(--accent);
}
p {
margin-bottom: 1em;
}
.prose p {
margin-bottom: 2em;
}
textarea {
width: 100%;
font-size: 16px;
}
input {
font-size: 16px;
}
table {
width: 100%;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
code {
padding: 2px 5px;
background-color: rgb(var(--gray-light));
border-radius: 2px;
}
pre {
padding: 1.5em;
border-radius: 8px;
}
pre > code {
all: unset;
}
blockquote {
border-left: 4px solid var(--accent);
padding: 0 0 0 20px;
margin: 0;
font-size: 1.333em;
}
hr {
border: none;
border-top: 1px solid rgb(var(--gray-light));
}
@media (max-width: 720px) {
body {
font-size: 18px;
}
main {
padding: 1em;
}
}
.sr-only {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px 1px 1px 1px);
/* maybe deprecated but we need to support legacy browsers */
clip: rect(1px, 1px, 1px, 1px);
/* modern browsers, clip-path works inwards from each corner */
clip-path: inset(50%);
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
white-space: nowrap;
}

8
tsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"strictNullChecks": true
}
}