wangairnan 5a78de6aa6 feat(web): 后台主题切换与移动端溢出菜单,设置保存按钮上移 topbar
- 后台 topbar 右侧常驻主题切换按钮(复用博客端三态循环逻辑),
  tooltip 新增 tip-below 朝下变体适配近视口顶部的 topbar
- <768px 侧栏隐藏时主题/返回博客/退出登录收进「···」溢出菜单;
  博客 <640px 首页/标签收进 header 溢出菜单(带 active 高亮)
- 新增通用 MoreMenu 组件:点击外部/Escape/选中项自动关闭,
  面板用 --popover 令牌 + 0.15s 淡入,与现有卡片风格一致
- 站点设置保存按钮移至 topbar 标题右侧,删除底部 sticky 保存条
- 修复两处回归:tip-below 气泡居中锚定越界产生页面横向滚动条
  (改右对齐);pn-site-menu-btn 基础 display 规则源码顺序在
  640px MQ 之后导致移动端菜单按钮不显示(前移至博客 header 区)
- 已用 Playwright 在 1440×900 / 390×844 实测后台四页与博客
  菜单展开/跳转均无溢出
2026-09-09 00:12:21 +08:00
2026-09-08 14:30:50 +08:00

Pure Note

极简高安全私人笔记 + 博客。最终交付 = 一个二进制程序 + 一个数据文件(目录): 无数据库服务、无缓存服务、无 Node 运行时、无外部依赖。

设计文档:docs/design.md(v1.1);实施决策记录:docs/decisions.md;提交规范:docs/contributing.md

功能

  • 笔记:Markdown CRUD、粘贴/拖拽图片上传(≤5MB、魔数校验、BLOB 入库去重)、公开/私有两态、置顶、标签
  • 博客:首页(置顶优先 + 分页 + 标签云)、详情(上一篇/下一篇)、标签页、RSS、sitemap、SEO meta 注入(仅公开内容)
  • 管理:单管理员口令登录(Argon2id)、会话 7 天滑动续期(CSRF 轮换保持不变)、防爆破限流(per-IP + per-账号)、改密
  • 回收站:删除 = 软删除,30 天内可恢复,gc 到期物理清除
  • 界面:9 页面响应式 SPA(React 19 + Tailwind 4),深色/浅色/跟随系统三态主题
  • 运维:init / passwd(重设口令并吊销全部会话)/ start / backup(在线一致快照)/ gc(默认 dry-run)/ version(亦可 -v)

快速开始

# 1. 构建单二进制(前端 + 后端)
make build

# 2. 初始化(设置管理员口令与站点标题)
./pn init
#   非交互:PN_ADMIN_PASSWORD=xxx PN_SITE_TITLE=yyy ./pn init

# 3. 启动
./pn start --addr 127.0.0.1:8080

# 4. 访问
#    博客:   http://127.0.0.1:8080/
#    管理后台:http://127.0.0.1:8080/admin

开发模式(本机调试,Cookie 允许非 Secure,仅限 loopback 监听):

./pn start --dev --addr 127.0.0.1:8080
# 前端热重载(另开终端):
cd web && npm install && npm run dev   # /api 代理到 127.0.0.1:8080

为什么必须带 --dev:明文 HTTP 下 Safari/WebKit 不保存 Secure Cookie(Chrome/Firefox 有 localhost 豁免,Safari 没有),不带 --dev 时 __Host- 会话被静默丢弃,Safari 登录成功后仍会被弹回登录页。--dev 改用无 Secure 的 pn_session Cookie 并强制仅监听 loopback(§7.3-3);生产走 HTTPS 不带 --dev,自动恢复 __Host- + Secure 完整加固。

忘记口令(CLI 可达即具备服务器权限,可直接重设;重设后全部会话被吊销):

./pn passwd
#   非交互:PN_ADMIN_PASSWORD=xxx ./pn passwd

测试

make test    # go vet + go test ./... + 前端 vitest
make smoke   # 构建冒烟:起服务 → SPA 资源 200/MIME → meta 注入 → 可见性抽查

测试矩阵覆盖(§13):可见性矩阵(匿名 × 管理员 × {公开, 私有, 回收站, 不存在} × 全部出口)、 迁移与 user_version 守卫、登录限流 429、CSRF、上传魔数/去重、回收站生命周期、gc 宽限期、 slug 冲突策略、设置白名单(永不泄露口令哈希)、meta 注入转义。

部署(生产)

make linux                              # 产出 pn-linux-amd64
sudo cp pn-linux-amd64 /opt/pure-note/pn
sudo -u purenote ./pn init --data-dir /opt/pure-note/data
sudo cp deploy/pn.service deploy/pn-maint.{service,timer} /etc/systemd/system/
sudo systemctl enable --now pn pn-maint.timer

反向代理用 Caddy(自动 TLS),示例见 deploy/Caddyfile; 启动需带 --behind-proxy(取 X-Forwarded-For 最右条目)。

升级 SOP:pn backup → 停服 → 换二进制 → 起服(迁移自动执行; 库版本高于代码支持范围时拒绝启动,--allow-newer 显式放行)。

恢复 SOP:停服 → 删除 data/pn.db-wal 与 -shm → 以备份文件替换 pn.db → 起服。

架构

cmd/pn/                 CLI(start/init/passwd/backup/gc/version)
internal/config/        命令行解析(--dev loopback 守卫)
internal/store/         SQLite(modernc 纯 Go 驱动):迁移(user_version) + DAO + 备份 + gc
internal/auth/          Argon2id(PHC) + 随机 token
internal/markdown/      goldmark + bluemonday(RSS/meta 服务端渲染)
internal/middleware/    安全头 / 日志 / 限流 / Origin 校验 / MaxBytes
internal/httpapi/       路由与 handler(公共 / 认证 / 管理 / feed)
internal/webui/         go:embed 前端产物 + SPA fallback + index.html meta 注入
web/                    React 19 + Vite + Tailwind 4 前端
├─ src/pages/           9 页面:首页 / 笔记详情 / 标签页 / 登录 / 后台列表 / 编辑 / 回收站 / 设置 / 404
├─ src/components/      Layout(导航与主题切换)、Editor、MarkdownViewer、Toast
└─ src/lib/             api(CSRF 注入)/ auth(会话上下文)/ theme(三态主题)/ sanitize / invalidate
deploy/                 systemd 单元 ×3 + Caddyfile

安全要点(详见设计 §9):CSP script-src 'self'、CSRF 双保险(token + Origin 校验)、 __Host- Cookie、服务端统一可见性过滤(单一可信点)、图片并集可见性、 上传魔数校验拒绝 SVG、统一 404 防枚举、安全响应头全家桶。

S
Description
No description provided
Readme
895 KiB
pn v0.0.2
Latest
2026-09-11 01:24:38 +08:00
Languages
TypeScript 43.2%
Go 41.9%
CSS 12.7%
Shell 1.4%
Makefile 0.5%
Other 0.3%