diff --git a/README.md b/README.md index 5023654..64e0e4e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ 评论配置在 `src/data/comments.ts`。 +> 日常操作(写文章、发偶得、加照片、部署)看 **[docs/操作指南.md](docs/操作指南.md)**,那份更偏"照着做";本文偏项目结构与原理。 + --- ## 技术栈 diff --git a/docs/操作指南.md b/docs/操作指南.md new file mode 100644 index 0000000..d3d512f --- /dev/null +++ b/docs/操作指南.md @@ -0,0 +1,240 @@ +# 操作指南 + +这个博客的日常操作:写文章、写偶得、加照片、本地预览、部署上线。 + +--- + +## 0. 首次准备 + +```bash +cd /home/tianxuetao/文档/VermilionVoid-main +pnpm install # 只需在第一次、或依赖变动后执行 +``` + +平时不需要重复装依赖。 + +--- + +## 1. 本地预览(写得最多的一步) + +```bash +pnpm dev +``` + +打开 **http://localhost:4321**。改任何文件浏览器会自动刷新,不用重启。 + +停止:在终端按 `Ctrl + C`。 + +> 端口被占用时 Astro 会自动换到 4322、4323……看终端输出的那行 `Local` 网址即可。 + +--- + +## 2. 写一篇正式文章 + +### 建文件 + +```bash +pnpm new-post "树莓派装 Docker 全程记录" --category 教程 --tags Docker,树莓派 +``` + +常用选项: + +| 选项 | 作用 | +|---|---| +| `--category 教程` | 分类,同时决定放进哪个子目录 | +| `--tags Docker,树莓派` | 标签,逗号分隔 | +| `--description "摘要"` | 摘要,用于 SEO 和列表卡片 | +| `--image /assets/images/xxx.webp` | 封面图 | +| `--file my-slug` | 自定义文件名(默认用标题) | +| `--pinned` | 置顶 | +| `--draft` | 草稿,构建时不会输出 | + +生成的文件长这样: + +```yaml +--- +title: "树莓派装 Docker 全程记录" +published: 2026-09-14 +description: "摘要" +tags: + - "Docker" + - "树莓派" +category: "教程" +--- +``` + +### 写正文 + +直接在 `---` 下面往下写 Markdown。支持: + +- 标题、列表、表格、引用、代码块(带行号和复制按钮) +- 数学公式:`$行内$` 和 `$$独立成段$$` +- 提示框: + + ```markdown + > [!NOTE] + > 这是一条提示。也可以用 TIP / IMPORTANT / CAUTION / WARNING + ``` + +- 图片:`![说明](/assets/images/图片.webp)` + +### 不想用脚本 + +手动在 `src/content/posts/` 下建 `.md` 文件也可以,**只有 `title` 和 `published` 是必填的**,其余字段都能省略。 + +### 加密文章(可选) + +```yaml +encrypted: true +password: "你的密码" +``` + +正文会被加密,需要输密码才能阅读。 + +--- + +## 3. 写一条「偶得」 + +```bash +pnpm new-thought "换了散热,温度降了 15 度。" --location 张掖 --tags 硬件 +``` + +| 选项 | 作用 | +|---|---| +| `--title` | 可选标题 | +| `--location` | 地点,显示在日期旁 | +| `--weather` | 天气 | +| `--images a.png,b.png` | 配图,逗号分隔 | +| `--tags 生活,硬件` | 标签 | +| `--pinned` | 置顶 | + +> ⚠️ **`published` 一定要带时区**(脚本会自动写成 `2026-09-14T12:36:47.215Z`)。 +> 手写成不带时区的 `2026-09-14 12:36`,YAML 会按 UTC 解析,**显示的日期会差一天**。 + +偶得页有两种模式:**专注**(一次一条,←/→ 切换)和**时间线**(按年份分组 + 标签筛选)。 + +--- + +## 4. 加照片到相册 + +照片放在 `public/gallery/<相册id>/`,**构建时自动扫描目录**,不用在配置里逐个登记。 + +加一个新相册 = 两步: + +1. 建目录并放照片:`public/gallery/我的相册/` +2. 在 `src/data/gallery.ts` 的 `albums` 里加一项: + +```ts +{ + id: "我的相册", // 必须和目录名一致 + name: "相册显示名", + description: "一句话描述", + location: "甘肃-张掖", // 可选 + date: "2026-09-14", // 可选 + tags: ["旅行"], // 可选,用于索引页筛选 +} +``` + +封面优先级:`cover` 字段 > 目录里的 `cover.*` 文件 > 第一张图。 + +> 📏 **放进去之前先压图。** `public/` 下的资源由服务器原样下发,**不经过 Astro 优化**。 +> 一张 4K 原图可能十几 MB,会让页面加载非常慢。建议先压成 WebP 再放。 + +--- + +## 5. 检查与构建 + +```bash +pnpm check # 类型 + 内容校验,frontmatter 写错会在这里报出来 +pnpm build # 构建到 dist/,并建立搜索索引 +pnpm preview # 本地预览构建产物(搜索功能只有这里才生效) +``` + +写完一篇后建议跑一次 `pnpm check`,能提前发现 frontmatter 字段写错。 + +--- + +## 6. 部署上线 + +```bash +./deploy.sh +``` + +它会构建一次,然后 rsync 到树莓派和云服务器,覆盖生产前会问你一句确认。 + +| 命令 | 用途 | +|---|---| +| `./deploy.sh` | 构建 + 部署两个目标 | +| `./deploy.sh -y` | 同上,跳过确认(适合脚本) | +| `./deploy.sh --pi-only` | 只推树莓派,先在内网看效果 | +| `./deploy.sh --cloud-only` | 只推云服务器(正式上线) | +| `./deploy.sh --dry-run` | 空跑,只显示会同步哪些文件 | +| `./deploy.sh --skip-build` | 不重新构建,直接推现有 `dist/` | + +推荐节奏:先 `--pi-only` 在树莓派上确认没问题,再 `--cloud-only` 上生产。 + +> 目标地址和 SSH 密钥在 `deploy.sh` 顶部的**配置区**。 +> 脚本在构建失败或产物异常时会**直接中止**——因为推送用的是 `rsync --delete`, +> 拿一个残缺的 `dist/` 上去会清空线上目录。 + +--- + +## 7. 改站点信息 + +日常最常改的三个文件: + +| 想改什么 | 改哪里 | +|---|---| +| 站名、域名、描述、主题色、统计 | `src/data/site.ts` | +| 昵称、签名、头像、GitHub/邮箱 | `src/data/profile.ts` | +| 首页打字机文案 | `src/components/hero.tsx` 顶部的 `phrases` | + +主题色只改 `site.ts` 里的 `themeHue` 一个数字:`0` 红、`25` 朱红(当前)、`200` 青、`250` 蓝绿、`345` 粉。 + +--- + +## 8. 常见问题 + +**改了文件但浏览器没变** +先手动刷新。若仍无效,`Ctrl + C` 停掉再 `pnpm dev` 重启。 + +**构建时报 `ENOSPC: System limit for number of file watchers reached`** +系统的 inotify 监视器配额用完了,不是代码问题。页面照常访问,但热更新可能失效。 +彻底解决需要放宽内核参数(要 sudo): + +```bash +sudo sysctl fs.inotify.max_user_watches=524288 +``` + +**偶得的日期差一天** +看第 3 节:`published` 必须带时区。 + +**图片不显示** +检查路径是否以 `/` 开头(`/assets/images/x.webp`),以及文件是否真在 `public/` 下对应位置。 +注意 `public/` 里的文件路径就是 URL 路径,**没有 `public` 这一段**。 + +**想回退到之前的状态** + +```bash +git log --oneline # 看提交 +git reset --hard <提交号> +``` + +--- + +## 附:目录速查 + +``` +src/content/posts/ 文章(按分类分子目录) +src/content/thoughts/ 偶得 +src/content/studies/ 专题(目前为空) +src/data/site.ts 站点配置 +src/data/profile.ts 个人信息 +src/data/gallery.ts 相册配置 +src/data/comments.ts 评论配置 +src/components/hero.tsx 首页打字机文案 +public/assets/images/ 文章配图 +public/gallery/ 相册照片 +scripts/ 辅助脚本 +dist/ 构建产物(gitignore) +``` diff --git a/package.json b/package.json index fdbafbe..364f393 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "astro build && pagefind --site dist", "preview": "astro preview", "check": "astro check", + "new-post": "node scripts/new-post.mjs", "new-thought": "node scripts/new-thought.mjs" }, "dependencies": { diff --git a/scripts/new-post.mjs b/scripts/new-post.mjs new file mode 100644 index 0000000..28e4c09 --- /dev/null +++ b/scripts/new-post.mjs @@ -0,0 +1,137 @@ +/* 创建一篇新文章 + * + * 用法: + * pnpm new-post "文章标题" + * pnpm new-post "树莓派装 Docker" --category 教程 --tags Docker,树莓派 + * pnpm new-post "读《XX》有感" --category 思考 --description "摘要" --pinned + * pnpm new-post "标题" --file my-custom-slug --category 建站 + * + * 不加 --category 时文件放在 src/content/posts/ 根目录; + * 加了则同时写进 frontmatter 的 category 并放进同名子目录,和现有文章结构一致。 + */ + +import fs from "node:fs" +import path from "node:path" + +const POSTS_DIR = "./src/content/posts" + +function today() { + const d = new Date() + const p = (n) => String(n).padStart(2, "0") + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` +} + +const args = process.argv.slice(2) + +if (args.length === 0 || args[0] === "--help" || args[0] === "-h") { + console.log(`创建一篇新文章 + +用法: + pnpm new-post "文章标题" [选项] + +选项: + --category <分类> 分类名,同时决定存放的子目录 + --file <文件名> 自定义文件名(默认用标题) + --tags <标签,标签> 标签,逗号分隔 + --description <摘要> 摘要,用于 SEO 和列表卡片 + --image <路径> 封面图,public 下的绝对路径 + --pinned 置顶 + --draft 存为草稿(构建时不会输出) + +示例: + pnpm new-post "树莓派装 Docker" --category 教程 --tags Docker,树莓派`) + process.exit(args.length === 0 ? 1 : 0) +} + +const title = args[0] + +const opts = { + category: "", + file: "", + tags: "", + description: "", + image: "", + pinned: false, + draft: false, +} + +for (let i = 1; i < args.length; i++) { + const arg = args[i] + + if (arg === "--pinned") { + opts.pinned = true + continue + } + if (arg === "--draft") { + opts.draft = true + continue + } + if (!arg.startsWith("--")) { + console.error(`错误:无法识别的参数 "${arg}"`) + process.exit(1) + } + + const key = arg.slice(2) + if (!(key in opts)) { + console.error(`错误:未知选项 --${key}`) + process.exit(1) + } + + const value = args[i + 1] + if (value === undefined || value.startsWith("--")) { + console.error(`错误:--${key} 缺少值`) + process.exit(1) + } + opts[key] = value + i++ +} + +/** 文件名里不能出现 / : 等字符,统一替换掉 */ +const safeName = (s) => + s + .replace(/[/\\:*?"<>|]/g, "") + .replace(/\s+/g, "-") + .trim() + +const fileName = `${safeName(opts.file || title)}.md` +const dir = opts.category ? path.join(POSTS_DIR, safeName(opts.category)) : POSTS_DIR +const fullPath = path.join(dir, fileName) + +if (fs.existsSync(fullPath)) { + console.error(`错误:${fullPath} 已存在`) + process.exit(1) +} + +fs.mkdirSync(dir, { recursive: true }) + +const list = (v) => + v + ? v + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + : [] + +const tags = list(opts.tags) + +/** 双引号包裹并转义,避免标题里的冒号、引号破坏 YAML */ +const q = (v) => `"${String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` + +const frontmatter = [ + `title: ${q(title)}`, + `published: ${today()}`, // 只写日期,YAML 按 UTC 零点解析,不会有时区偏移 + opts.description ? `description: ${q(opts.description)}` : "", + opts.image ? `image: ${q(opts.image)}` : "", + tags.length > 0 ? `tags:\n${tags.map((t) => ` - ${q(t)}`).join("\n")}` : "tags: []", + `category: ${q(opts.category)}`, + opts.pinned ? "pinned: true" : "", + opts.draft ? "draft: true" : "", +] + .filter(Boolean) + .join("\n") + +fs.writeFileSync(fullPath, `---\n${frontmatter}\n---\n\n在这里写正文。\n`, "utf8") + +console.log(`已创建文章: ${fullPath}`) +if (opts.draft) console.log("状态:草稿(draft: true),构建时不会输出。写完记得删掉这一行。") +console.log("运行 pnpm dev 可以在浏览器里实时预览。") diff --git a/src/pages/gallery/index.astro b/src/pages/gallery/index.astro index 364a40a..523f8b4 100644 --- a/src/pages/gallery/index.astro +++ b/src/pages/gallery/index.astro @@ -124,7 +124,7 @@ const allTags = getAllAlbumTags(albums)