From ccb16acbeaf33b03d9dfa53c6f9daee0f874f631 Mon Sep 17 00:00:00 2001 From: eezd Date: Mon, 26 Oct 2026 22:52:12 +0800 Subject: [PATCH] chore: standardize project comments in English --- .gitignore | 8 +- AGENTS.md | 4 +- Dockerfile | 6 +- cmd/rawmark/main.go | 14 +- deploy/nginx/raw.134333.xyz.conf | 6 +- internal/api/api.go | 42 ++--- internal/api/api_test.go | 10 +- internal/api/events.go | 10 +- internal/api/export_notes_test.go | 2 +- internal/api/fonts.go | 2 +- internal/api/images_test.go | 2 +- internal/api/import.go | 10 +- internal/api/import_notes.go | 2 +- internal/api/import_test.go | 2 +- internal/api/lifecycle.go | 2 +- internal/api/maintenance.go | 4 +- internal/api/maintenance_test.go | 6 +- internal/api/maintenance_uploads.go | 8 +- internal/api/openapi_test.go | 4 +- internal/api/plugins_test.go | 2 +- internal/api/settings.go | 8 +- internal/api/static.go | 16 +- internal/api/storage.go | 6 +- internal/api/themes/black.css | 2 +- internal/api/themes_test.go | 2 +- internal/api/tokens.go | 2 +- internal/api/trash.go | 6 +- internal/auth/auth.go | 28 +-- internal/auth/auth_test.go | 4 +- internal/backup/archive.go | 8 +- internal/backup/log.go | 4 +- internal/backup/restore.go | 18 +- internal/events/hub.go | 18 +- internal/search/maintenance.go | 2 +- internal/search/maintenance_test.go | 2 +- internal/search/search.go | 32 ++-- internal/search/search_test.go | 16 +- internal/store/backup.go | 8 +- internal/store/store.go | 16 +- internal/store/tickets.go | 4 +- internal/vault/cleanup.go | 2 +- internal/vault/export.go | 8 +- internal/vault/export_notes.go | 6 +- internal/vault/images.go | 6 +- internal/vault/import.go | 14 +- internal/vault/import_links.go | 14 +- internal/vault/links.go | 32 ++-- internal/vault/metadata.go | 8 +- internal/vault/metadata_math.go | 6 +- internal/vault/shared_images.go | 6 +- internal/vault/trash.go | 10 +- internal/vault/trash_test.go | 2 +- internal/vault/tree_icons.go | 6 +- internal/vault/tree_icons_test.go | 2 +- internal/vault/vault.go | 74 ++++---- internal/vault/vault_test.go | 4 +- internal/watch/watch.go | 26 +-- scripts/dev.sh | 26 +-- scripts/prepare-maple-font.py | 10 +- scripts/remove-sharing.sql | 4 +- scripts/reset-shortcuts.sql | 2 +- web/.editorconfig | 20 +-- web/.npmrc | 2 +- web/embed.go | 8 +- web/embed_dev.go | 8 +- web/eslint.config.js | 18 +- web/font_storage.go | 2 +- web/pnpm-workspace.yaml | 2 +- web/src/App.vue | 2 +- web/src/common/apis/assets/index.ts | 10 +- web/src/common/apis/auth/index.ts | 16 +- web/src/common/apis/events/index.ts | 2 +- web/src/common/apis/files/index.ts | 16 +- web/src/common/apis/maintenance/index.ts | 26 +-- web/src/common/apis/metadata/index.ts | 2 +- web/src/common/apis/plugins/index.ts | 12 +- web/src/common/apis/search/index.ts | 2 +- web/src/common/apis/search/type.ts | 2 +- web/src/common/apis/settings/index.ts | 4 +- web/src/common/apis/settings/type.ts | 2 +- web/src/common/apis/storage/index.ts | 2 +- web/src/common/apis/themes/index.ts | 4 +- web/src/common/apis/tokens/index.ts | 6 +- web/src/common/apis/trash/index.ts | 8 +- web/src/common/apis/tree/index.ts | 4 +- web/src/common/appearance/appearance.ts | 2 +- web/src/common/appearance/base.css | 36 ++-- web/src/common/appearance/font-loading.ts | 6 +- web/src/common/appearance/fonts.ts | 2 +- web/src/common/appearance/maple-font.css | 2 +- web/src/common/appearance/typography.ts | 2 +- web/src/common/assets/styles/app.css | 56 +++--- .../common/assets/styles/element-plus.scss | 2 +- web/src/common/components/AppStatus/index.vue | 4 +- .../common/components/FontPicker/index.vue | 2 +- .../components/InlineAppearance/index.vue | 2 +- web/src/common/highlight/client.ts | 2 +- web/src/common/highlight/protocol.ts | 6 +- web/src/common/highlight/worker.test.ts | 2 +- web/src/common/highlight/worker.ts | 4 +- web/src/common/i18n/runtime.ts | 2 +- web/src/common/markdown/extensions.test.ts | 4 +- web/src/common/markdown/extensions.ts | 26 +-- web/src/common/markdown/html.test.ts | 10 +- web/src/common/markdown/html.ts | 44 ++--- web/src/common/markdown/inline-styles.ts | 2 +- web/src/common/markdown/links.ts | 2 +- web/src/common/markdown/render.ts | 14 +- web/src/common/plugin-api/index.d.ts | 2 +- web/src/common/plugins/host.ts | 4 +- web/src/common/plugins/mount.ts | 2 +- web/src/common/utils/clipboard.ts | 4 +- web/src/common/utils/commands.ts | 16 +- web/src/common/utils/confirm.ts | 2 +- web/src/common/utils/icons.ts | 2 +- web/src/common/utils/keys.ts | 6 +- web/src/common/utils/paths.ts | 6 +- web/src/common/utils/pwa.ts | 6 +- web/src/common/utils/resource-tickets.ts | 2 +- web/src/common/utils/typora-keys.ts | 2 +- web/src/common/utils/viewport.ts | 8 +- web/src/http/axios.ts | 2 +- web/src/main.ts | 2 +- .../components/AppearanceSettings.vue | 2 +- .../settings/components/MaintenancePanel.vue | 2 +- .../pages/settings/components/TokensPanel.vue | 2 +- web/src/pages/settings/settings-catalog.ts | 2 +- .../pages/workspace/components/EditorPane.vue | 10 +- .../workspace/components/ImportDialog.vue | 4 +- .../pages/workspace/components/MoveDialog.vue | 2 +- .../workspace/components/OutlinePanel.vue | 8 +- .../workspace/components/QuickSwitcher.vue | 14 +- .../workspace/components/SearchPanel.vue | 4 +- .../workspace/components/SelectionToolbar.vue | 2 +- web/src/pages/workspace/components/TabBar.vue | 4 +- .../pages/workspace/components/TreeNode.vue | 6 +- web/src/pages/workspace/demo/main.ts | 4 +- web/src/pages/workspace/drafts.ts | 6 +- web/src/pages/workspace/editor/blocks.ts | 6 +- web/src/pages/workspace/editor/codeblocks.ts | 16 +- web/src/pages/workspace/editor/focus.ts | 8 +- web/src/pages/workspace/editor/format.test.ts | 4 +- web/src/pages/workspace/editor/format.ts | 24 +-- web/src/pages/workspace/editor/html-paste.ts | 8 +- web/src/pages/workspace/editor/index.ts | 14 +- .../pages/workspace/editor/inline-styles.ts | 16 +- web/src/pages/workspace/editor/keymap.test.ts | 2 +- web/src/pages/workspace/editor/livepreview.ts | 22 +-- web/src/pages/workspace/editor/paste.ts | 20 +-- web/src/pages/workspace/editor/table.ts | 40 ++--- web/src/pages/workspace/editor/theme.ts | 2 +- web/src/pages/workspace/editor/widgets.ts | 30 ++-- web/src/pages/workspace/fuzzy.ts | 16 +- web/src/pages/workspace/index.vue | 10 +- web/src/pages/workspace/reading.ts | 2 +- web/src/pages/workspace/selection.ts | 2 +- web/src/pages/workspace/tree-icon-catalog.ts | 2 +- web/src/pages/workspace/tree.ts | 6 +- web/src/pages/workspace/workspace.ts | 164 +++++++++--------- web/src/pinia/stores/settings.ts | 20 +-- web/src/pinia/stores/user.ts | 2 +- web/src/router/guard.test.ts | 2 +- web/src/service-worker.js | 10 +- web/tests/api-response.ts | 2 +- web/types/api.d.ts | 2 +- web/uno.config.ts | 10 +- web/vite.config.ts | 10 +- 167 files changed, 820 insertions(+), 820 deletions(-) diff --git a/.gitignore b/.gitignore index 808bfbd..b77ee22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# 本地交接文件与历史设计文档不入库;公开文档单独放行 +# Ignore local handoff and historical design documents; allow public guides explicitly. CONTEXT.md docs/* !docs/guide.md @@ -11,14 +11,14 @@ docs/* !docs/api.zh-CN.md !docs/images/ -# 前端 +# Frontend node_modules/ web/dist/ -# 本地开发数据 +# Local development data .dev-data/ deploy/data/ -# 仓库维护的工具规则(其他本地技能不入库) +# Repository-maintained tool rules (other local skills stay untracked) .agents/skills/* !.agents/skills/v3-*/ diff --git a/AGENTS.md b/AGENTS.md index 49891c4..44d04b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## 语言与沟通 -- 回复使用简体中文;注释、文档字符串、日志和错误信息遵循项目既有语言约定,无约定时优先使用简体中文。 +- 回复使用简体中文;代码注释、文档字符串及配置/脚本中的注释统一使用英文。日志和错误信息遵循项目既有语言约定,无约定时优先使用简体中文。 - 命名使用清晰英文;Git commit 标题与正文使用英文并遵循项目约定。 - Git commit 不得添加 Claude、Claude Code、AI 助手等作为 Contributor、Co-authored-by 或其他署名;不得添加 AI 生成声明或署名 trailer,除非用户明确要求。 - 先给结论,按需解释,明确未验证的信息。 @@ -110,7 +110,7 @@ RawMark 是单用户 Markdown 网页笔记编辑器。文件是真源;Go 标 ```bash pnpm -C web install --frozen-lockfile -scripts/dev.sh # 隔离开发数据默认 .dev-data,API_PORT/WEB_PORT 控制端口 +scripts/dev.sh # Isolated data defaults to .dev-data; API_PORT/WEB_PORT select ports. pnpm -C web lint:check pnpm -C web typecheck pnpm -C web test diff --git a/Dockerfile b/Dockerfile index 3c3e405..377daa1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -# ---- 前端 ---- +# ---- Frontend ---- FROM node:24-alpine AS web WORKDIR /src/web RUN corepack enable && corepack prepare pnpm@12.11.2 --activate @@ -9,7 +9,7 @@ RUN pnpm install --frozen-lockfile COPY web/ ./ RUN pnpm build -# ---- 服务端(前端产物嵌入二进制) ---- +# ---- Backend (frontend assets embedded in the binary) ---- FROM golang:1-alpine AS server WORKDIR /src COPY go.mod go.sum ./ @@ -20,7 +20,7 @@ COPY web/embed.go web/font_storage.go ./web/ COPY --from=web /src/web/dist ./web/dist RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/rawmark ./cmd/rawmark && mkdir -p /out/data -# ---- 运行 ---- +# ---- Runtime ---- FROM gcr.io/distroless/static:nonroot COPY --from=server /out/rawmark /rawmark COPY --from=server --chown=nonroot:nonroot /out/data /data diff --git a/cmd/rawmark/main.go b/cmd/rawmark/main.go index 59d8eb5..60345e3 100644 --- a/cmd/rawmark/main.go +++ b/cmd/rawmark/main.go @@ -1,8 +1,8 @@ -// RawMark 服务入口。配置全部来自环境变量: +// RawMark service entry point. All configuration comes from environment variables: // -// RAWMARK_DATA 数据目录,默认 /data(存放 rawmark.db、可重建的搜索索引 index.db 与默认仓库) -// RAWMARK_VAULT 笔记仓库目录,默认 $RAWMARK_DATA/vault;可指向已有的 Typora 笔记目录 -// RAWMARK_ADDR 监听地址,默认 :8080 +// RAWMARK_DATA Data directory; defaults to /data (rawmark.db, rebuildable index.db, and the default vault). +// RAWMARK_VAULT Note vault; defaults to $RAWMARK_DATA/vault and may point to an existing Typora directory. +// RAWMARK_ADDR Listen address; defaults to :8080. package main import ( @@ -65,7 +65,7 @@ func run(addr string) error { if err != nil { return err } - // 所有标准库上传、导出临时文件归本进程专属目录,不能清理宿主机 /tmp。 + // Keep all standard-library upload/export temporary files in this process's own directory; never clean the host /tmp. tempDir := filepath.Join(dataDir, "temp") if err = os.MkdirAll(tempDir, 0700); err != nil { return err @@ -208,7 +208,7 @@ func serve(parent context.Context, addr, dataDir, vaultDir, version string, font return requested, nil } -// permissionHint 为权限错误补充排查提示:Docker 绑定挂载的宿主机目录常常对容器内的非 root 用户不可写 +// permissionHint adds guidance for permission errors: Docker bind mounts are often not writable by non-root container users. func permissionHint(err error) error { if errors.Is(err, fs.ErrPermission) { return fmt.Errorf("%w(当前 UID %d 对数据目录没有写权限:请用 --user 指定宿主机目录所有者的 UID:GID,或执行 chown 修改目录所有者)", err, os.Getuid()) @@ -231,7 +231,7 @@ func cleanupSessions(ctx context.Context, st *store.Store) { } } -// healthcheck 供 Docker HEALTHCHECK 使用(distroless 镜像中没有 curl) +// healthcheck supports Docker HEALTHCHECK without curl in the distroless image. func healthcheck(addr string) int { _, port, err := net.SplitHostPort(addr) if err != nil { diff --git a/deploy/nginx/raw.134333.xyz.conf b/deploy/nginx/raw.134333.xyz.conf index b552300..8ff5a92 100644 --- a/deploy/nginx/raw.134333.xyz.conf +++ b/deploy/nginx/raw.134333.xyz.conf @@ -1,4 +1,4 @@ -# 票据属于资源凭据,日志保留资源路径但不记录明文票据。 +# Tickets are resource credentials; retain resource paths in logs without plaintext tickets. map $uri $rawmark_log_path { ~^/api/v1/resources/[^/]+(?/.*)$ /api/v1/resources/[redacted]$rawmark_resource_suffix; default $uri; @@ -7,7 +7,7 @@ log_format rawmark '$remote_addr - $remote_user [$time_local] ' '"$request_method $rawmark_log_path $server_protocol" $status $body_bytes_sent ' '"$http_user_agent"'; -# raw.134333.xyz -> RawMark 容器(127.0.0.1:21000) +# raw.134333.xyz -> RawMark container (127.0.0.1:21000) server { listen 80; @@ -38,7 +38,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 1h; - # Nginx 错误日志不能脱敏请求行;票据路径只保留脱敏访问日志和应用错误日志。 + # Nginx error logs cannot redact request lines; ticket paths retain only redacted access logs and application error logs. location ^~ /api/v1/resources/ { proxy_pass http://127.0.0.1:21000; error_log /dev/null; diff --git a/internal/api/api.go b/internal/api/api.go index a999040..4f640e1 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -1,4 +1,4 @@ -// Package api 提供 REST API(/api/v1)、事件推送与前端静态资源。前端只通过这套 API 访问数据。 +// Package api serves the REST API (/api/v1), events, and frontend assets. The frontend accesses data only through this API. package api import ( @@ -37,9 +37,9 @@ type Server struct { store *store.Store hub *events.Hub index *search.Index - // 串行化设置的"读取-合并-写入" + // Serialize settings read/merge/write operations. settingsMu sync.Mutex - // ThemesDir 指向数据目录中的主题目录;为空时只有内置主题。 + // ThemesDir points to the data directory's themes folder; an empty value enables only built-in themes. DataDir string ThemesDir string PluginsDir string @@ -62,14 +62,14 @@ func New(v *vault.Vault, a *auth.Manager, st *store.Store, hub *events.Hub, inde return &Server{vault: v, auth: a, store: st, hub: hub, index: index, captchas: newCaptchas()} } -// access 为接口的访问要求 +// access defines an endpoint's access requirement. type access int const ( public access = iota - // 登录会话或 API 令牌(只读令牌可用 GET 和只读导出接口) + // Login session or API token (read-only tokens may use GET and read-only exports). authed - // 只允许登录会话:管理 API 令牌,避免令牌给自己扩权 + // Login sessions only: prevent API tokens from expanding their own permissions through token management. sessionOnly ) @@ -79,7 +79,7 @@ type route struct { handler http.HandlerFunc } -// routes 为全部接口;openapi.json 须与之一致(见 TestOpenAPICoversRoutes) +// routes lists all endpoints; openapi.json must match it (see TestOpenAPICoversRoutes). func (s *Server) routes() []route { return []route{ {"GET /api/v1/health", public, func(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]bool{"ok": true}) }}, @@ -170,7 +170,7 @@ func (s *Server) Handler(static fs.FS) http.Handler { return localized(securityHeaders(sameOrigin(s.guardAPI(mux)))) } -// ---- 中间件 ---- +// ---- Middleware ---- func securityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -185,8 +185,8 @@ func securityHeaders(next http.Handler) http.Handler { }) } -// sameOrigin 保持写请求的同源边界;Bearer 鉴权不依赖浏览器 Cookie。 -// 没有 Origin 头的请求来自非浏览器客户端,放行 +// sameOrigin preserves the same-origin boundary for writes; Bearer authentication does not rely on browser Cookies. +// Requests without Origin come from non-browser clients and are allowed. func sameOrigin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { @@ -201,7 +201,7 @@ func sameOrigin(next http.Handler) http.Handler { }) } -// ---- 鉴权 ---- +// ---- Authentication ---- func (s *Server) authState(w http.ResponseWriter, r *http.Request) { setUp, err := s.auth.IsSetUp() @@ -276,7 +276,7 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]any{"permissions": []string{"vault:read", "vault:write", "instance:manage"}, "expiresAt": identity(r).ExpiresAt}) } -// ---- 文件 ---- +// ---- Files ---- func (s *Server) tree(w http.ResponseWriter, r *http.Request) { entries, err := s.vault.Tree() @@ -331,7 +331,7 @@ func (s *Server) readFile(w http.ResponseWriter, r *http.Request) { w.Write(data) } -// writeFile 以请求体整体作为笔记内容。If-Match: "rev" 要求版本一致;If-None-Match: * 要求文件不存在;都不带则直接覆盖 +// writeFile uses the entire request body as note content. If-Match: "rev" requires a matching revision; If-None-Match: * requires a new file; omitting both overwrites unconditionally. func (s *Server) writeFile(w http.ResponseWriter, r *http.Request) { data, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxNoteBytes)) if err != nil { @@ -371,10 +371,10 @@ func (s *Server) move(w http.ResponseWriter, r *http.Request) { return } if rewritten { - // 原子改写替换了文件身份,监听器无法再从快照中识别这次移动。 + // Atomic rewriting replaced the file identity, so the watcher can no longer identify this move from its snapshot. s.hub.Broadcast(events.Event{Type: "moved", From: body.From, Path: body.To}, events.Event{Type: "tree"}) } else { - // 图标提交在文件移动之后;不能仅依赖文件监听器较早的通知。 + // Icons commit after the file move; the watcher's earlier notification alone is insufficient. s.hub.Broadcast(events.Event{Type: "tree"}) } writeJSON(w, 200, map[string]bool{"ok": true}) @@ -392,7 +392,7 @@ func (s *Server) mkdir(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]bool{"ok": true}) } -// uploadAsset 接收 multipart 表单中的 file 字段,存入指定笔记或目录下的共享 assets 目录。 +// uploadAsset accepts the multipart file field and saves it in the shared assets directory for the selected note or folder. func (s *Server) uploadAsset(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxAssetBytes+1<<20) file, header, err := r.FormFile("file") @@ -418,7 +418,7 @@ func (s *Server) uploadAsset(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"link": link}) } -// replaceImage 仅替换已存在的图片;路径与引用保持不变。 +// replaceImage replaces only an existing image, preserving its path and references. func (s *Server) replaceImage(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxAssetBytes+1<<20) if err := r.ParseMultipartForm(1 << 20); err != nil { @@ -444,7 +444,7 @@ func (s *Server) replaceImage(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]bool{"ok": true}) } -// raw 输出仓库内任意非隐藏文件(主要是图片)。以沙箱 CSP 返回,防止 SVG/HTML 中的脚本在本站执行 +// raw serves any non-hidden vault file (mainly images). A sandbox CSP prevents SVG/HTML scripts from running on this origin. func (s *Server) raw(w http.ResponseWriter, r *http.Request) { s.serveRaw(w, r, r.PathValue("path")) } @@ -465,7 +465,7 @@ func (s *Server) serveRaw(w http.ResponseWriter, r *http.Request, rel string) { http.ServeContent(w, r, info.Name(), info.ModTime(), f) } -// ---- 回收站 ---- +// ---- Recycle bin ---- func (s *Server) trashList(w http.ResponseWriter, r *http.Request) { items, err := s.vault.TrashList() @@ -494,9 +494,9 @@ func (s *Server) trashPurge(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]bool{"ok": true}) } -// ---- 响应工具 ---- +// ---- Response helpers ---- -// clientIP 返回限速用的来源地址(未经反向代理头部,避免伪造) +// clientIP returns the source address for rate limiting, ignoring proxy headers to prevent spoofing. func clientIP(r *http.Request) string { host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 360f6bb..f98ee08 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -33,7 +33,7 @@ type fixture struct { root string store *store.Store hub *events.Hub - // 已登录的客户端 + // Authenticated client. admin *http.Client } @@ -88,7 +88,7 @@ func TestEventsRejectRevokedCredentials(t *testing.T) { t.Fatal(err) } defer conn.CloseNow() - // 首条事件确认订阅已经建立,避免把握手和广播的时序当作鉴权结果。 + // The first event confirms subscription before testing authorization, avoiding handshake/broadcast timing races. f.hub.Broadcast(events.Event{Type: "tree"}) if _, _, err := conn.Read(ctx); err != nil { t.Fatal(err) @@ -238,7 +238,7 @@ func decode[T any](t *testing.T, res *http.Response) T { return envelope.Data } -// 文档与实际接口一一对应:新增接口忘记写文档、文档中残留已删除的接口都会失败 +// Documentation must match actual endpoints: missing new routes and obsolete documented routes both fail. func TestOpenAPICoversRoutes(t *testing.T) { var spec struct { Paths map[string]map[string]json.RawMessage @@ -271,7 +271,7 @@ func TestOpenAPICoversRoutes(t *testing.T) { func TestAPIResponsesAreNotCached(t *testing.T) { f := newFixture(t) f.write("笔记.md", "# 正文") - // 登录状态与目录曾在 Firefox 离线时命中 HTTP 缓存,误进入无法加载的工作区。 + // Firefox once cached identity and tree responses offline, incorrectly opening an unloadable workspace. for _, path := range []string{"/api/v1/auth/state", "/api/v1/tree", "/api/v1/files/笔记.md", "/api/v1/raw/笔记.md"} { res := f.do(f.admin, "GET", path, "", nil) if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store" { @@ -443,7 +443,7 @@ func TestSetupWithoutCode(t *testing.T) { if status := f.status(newClient(), "POST", "/api/v1/auth/setup", `{"password":"replacement"}`, nil); status != 409 { t.Fatalf("重复初始化必须拒绝: %d", status) } - // 新建 Manager 模拟重启,不能重新开放密码设置。 + // A new Manager simulates a restart and must not reopen password setup. reopened := auth.New(st) if err := reopened.Setup("replacement"); !errors.Is(err, auth.ErrAlreadySetUp) { t.Fatalf("重启后不能覆盖密码: %v", err) diff --git a/internal/api/events.go b/internal/api/events.go index 2018c0c..dbaa853 100644 --- a/internal/api/events.go +++ b/internal/api/events.go @@ -9,11 +9,11 @@ import ( "github.com/coder/websocket" ) -// events 通过 WebSocket 推送仓库变更。websocket.Accept 默认校验 Origin 与 Host 一致 +// events pushes vault changes over WebSocket. websocket.Accept checks that Origin matches Host by default. func (s *Server) events(w http.ResponseWriter, r *http.Request) { s.eventWG.Add(1) defer s.eventWG.Done() - // 先订阅再完成握手,避免连接建立后首条变更落在订阅空窗中。 + // Subscribe before completing the handshake so the first change cannot fall into a subscription gap. msgs, unsubscribe := s.hub.Subscribe() defer unsubscribe() conn, err := websocket.Accept(w, r, nil) @@ -36,9 +36,9 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) { } return true } - // 客户端不发消息;CloseRead 负责处理控制帧并在连接断开时取消 ctx + // Clients send no messages; CloseRead handles control frames and cancels ctx on disconnect. ctx := conn.CloseRead(r.Context()) - // 定期 ping,避免反向代理因空闲断开连接 + // Ping periodically to prevent idle disconnects by reverse proxies. ping := time.NewTicker(30 * time.Second) defer ping.Stop() for { @@ -47,7 +47,7 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) { return case msg, ok := <-msgs: if !ok { - // 客户端消费过慢被移出广播列表,断开后由客户端重连并全量同步 + // Disconnect slow subscribers removed from the hub; clients reconnect and perform a full sync. conn.Close(websocket.StatusTryAgainLater, "too slow") return } diff --git a/internal/api/export_notes_test.go b/internal/api/export_notes_test.go index ad136d8..fac0792 100644 --- a/internal/api/export_notes_test.go +++ b/internal/api/export_notes_test.go @@ -76,7 +76,7 @@ func TestPortableNotesExcludeApplicationData(t *testing.T) { t.Fatal(err) } } - // 与真实进程一致,导出暂存文件位于应用数据目录内。 + // As in the real process, export temporary files live inside the application data directory. t.Setenv("TMPDIR", filepath.Join(data, "temp")) res = f.do(f.admin, "POST", "/api/v1/maintenance/notes/export", "", nil) defer res.Body.Close() diff --git a/internal/api/fonts.go b/internal/api/fonts.go index 818f53e..d6963e7 100644 --- a/internal/api/fonts.go +++ b/internal/api/fonts.go @@ -235,7 +235,7 @@ func (s *Server) deleteFont(w http.ResponseWriter, r *http.Request) { return } defer root.Close() - // 先撤下资源;设置写失败时恢复,避免留下指向已删除字体的设置。 + // Remove the resource first and restore it if settings fail to save, avoiding references to a deleted font. removed := ".remove-" + id if err := root.Rename(id, removed); err != nil { internalError(w, r, err) diff --git a/internal/api/images_test.go b/internal/api/images_test.go index 8788d2e..02dc3fd 100644 --- a/internal/api/images_test.go +++ b/internal/api/images_test.go @@ -79,7 +79,7 @@ func TestReplaceImageAPI(t *testing.T) { if err != nil || string(data) != "![](assets/a.png)" { t.Fatalf("替换不应改变引用:%q %v", data, err) } - // 复用现有移动与回收站接口,图片不会被当作 Markdown 正文读取。 + // Reuse move and recycle-bin endpoints without reading images as Markdown text. if code := f.status(f.admin, "POST", "/api/v1/move", `{"from":"assets/a.png","to":"assets/b.png"}`, nil); code != 200 { t.Fatal(code) } diff --git a/internal/api/import.go b/internal/api/import.go index 8076c2a..b2d667b 100644 --- a/internal/api/import.go +++ b/internal/api/import.go @@ -42,7 +42,7 @@ func publicImageIP(ip netip.Addr) bool { if !ip.IsGlobalUnicast() || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() { return false } - // 不接受 IPv6 地址转换前缀,避免其中嵌入的 IPv4 地址访问内网。 + // Reject IPv6 translation prefixes so embedded IPv4 addresses cannot reach private networks. if ip.Is6() && !netip.MustParsePrefix("2000::/3").Contains(ip) { return false } @@ -64,7 +64,7 @@ func checkImageURL(u *url.URL) error { var remoteImageClient = &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ - // 不使用环境代理,确保每个实际连接都经过目标地址校验。 + // Ignore environment proxies so every actual connection passes destination-address validation. DialContext: (&net.Dialer{ Timeout: 5 * time.Second, ControlContext: func(_ context.Context, _, address string, _ syscall.RawConn) error { @@ -105,7 +105,7 @@ var remoteImageClient = &http.Client{ }, } -// readImageDownload 按实际文件签名确定类型,不信任来源的扩展名或 Content-Type。 +// readImageDownload determines type from file signatures, not the source extension or Content-Type. func readImageDownload(body io.Reader) ([]byte, string, error) { data, err := io.ReadAll(io.LimitReader(body, maxAssetBytes+1)) if err != nil { @@ -174,7 +174,7 @@ func (s *Server) importAsset(w http.ResponseWriter, r *http.Request) { writeError(w, 400, i18n.RenderError(errImageAddress, responseLocale(w))) return } - // url.Error 含完整来源 URL,其中可能有临时签名,日志只保留底层原因。 + // url.Error includes the full source URL, potentially with temporary signatures; log only its underlying cause. var urlError *url.Error if errors.As(err, &urlError) { err = urlError.Err @@ -208,7 +208,7 @@ func (s *Server) importAsset(w http.ResponseWriter, r *http.Request) { } return } - // 下载期间笔记可能被外部程序移动或删除;不能在旧路径下创建孤立资源。 + // External programs may move or delete the note during download; do not create orphaned resources at the old path. info, err := s.vault.Stat(note) if err != nil { vaultError(w, r, err) diff --git a/internal/api/import_notes.go b/internal/api/import_notes.go index 3c7e23c..0155036 100644 --- a/internal/api/import_notes.go +++ b/internal/api/import_notes.go @@ -9,7 +9,7 @@ import ( ) func (s *Server) importNotes(w http.ResponseWriter, r *http.Request) { - // 总请求上限包含表单开销,文件总量另由 vault 校验。 + // The total request limit includes form overhead; vault validates the aggregate file size separately. r.Body = http.MaxBytesReader(w, r.Body, vault.MaxImportBytes+(1<<20)) if err := r.ParseMultipartForm(8 << 20); err != nil { var limit *http.MaxBytesError diff --git a/internal/api/import_test.go b/internal/api/import_test.go index 606ac56..2390d45 100644 --- a/internal/api/import_test.go +++ b/internal/api/import_test.go @@ -95,7 +95,7 @@ func TestImportImageDownloadAndRedirects(t *testing.T) { transport := original.Transport.(*http.Transport).Clone() dial := transport.DialContext transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { - // 只把固定的测试来源映射到本地真实 HTTP 服务,其他目标仍走生产地址校验。 + // Map only the fixed test origin to a real local HTTP server; other destinations retain production address validation. if address == "images.example:80" { return (&net.Dialer{}).DialContext(ctx, network, source.Listener.Addr().String()) } diff --git a/internal/api/lifecycle.go b/internal/api/lifecycle.go index e6bf8d6..50202ab 100644 --- a/internal/api/lifecycle.go +++ b/internal/api/lifecycle.go @@ -1,4 +1,4 @@ package api -// CloseEvents 在数据库关闭前断开并等待全部 WebSocket 处理结束。 +// CloseEvents disconnects and waits for all WebSocket handlers before databases close. func (s *Server) CloseEvents() { s.hub.Close(); s.eventWG.Wait() } diff --git a/internal/api/maintenance.go b/internal/api/maintenance.go index 01eeed0..7ac8f4e 100644 --- a/internal/api/maintenance.go +++ b/internal/api/maintenance.go @@ -29,7 +29,7 @@ type maintenanceStatus struct { cause error } -// gate 排空普通 API 操作后再执行维护;WebSocket 不持有长期读锁。 +// gate drains ordinary API operations before maintenance; WebSockets do not hold long-lived read locks. func (s *Server) guardAPI(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, "/api/") || r.URL.Path == "/api/v1/health" { @@ -324,7 +324,7 @@ func readPreferences(stage string) (map[string]json.RawMessage, error) { return values, nil } -// importSettings 在确认后合并资源;原资源与数据库值都保留到成功提交为止。 +// importSettings merges resources after confirmation, retaining original resources and database values until commit succeeds. func (s *Server) applySettings(w http.ResponseWriter, r *http.Request) { var body struct { ID string diff --git a/internal/api/maintenance_test.go b/internal/api/maintenance_test.go index 2f31857..672d696 100644 --- a/internal/api/maintenance_test.go +++ b/internal/api/maintenance_test.go @@ -71,7 +71,7 @@ func uploadedPackage(t *testing.T, f *fixture, kind string, data []byte) string t.Fatalf("创建上传失败: %d", res.StatusCode) } id := decode[struct{ ID string }](t, res).ID - // 跨多个分块验证偏移,不引入测试开关。 + // Verify offsets across multiple chunks without introducing test switches. offset := 0 for offset < len(data) { end := min(offset+137, len(data)) @@ -192,7 +192,7 @@ func TestFullBackupRestoresFilesDatabaseAndInvalidatesSessions(t *testing.T) { root := t.TempDir() vault := filepath.Join(root, "vault") os.MkdirAll(vault, 0700) - // 解包后落入目标实例,目标的环境路径与源实例不同。 + // Extract into the target instance, whose environment paths differ from the source. if err = backup.CopyTree(filepath.Join(stage, "vault"), vault); err != nil { t.Fatal(err) } @@ -273,7 +273,7 @@ func TestSettingsPackageDatabaseFailureRollsBack(t *testing.T) { f.store.SetSetting(settingsKey, `{"fontSize":24}`) data := exportedPackage(t, f, "settings") f.store.SetSetting(settingsKey, `{"fontSize":18}`) - // 通过实际 SQLite 约束制造提交失败,验证备份和事务边界。 + // Use an actual SQLite constraint to force commit failure and verify backup and transaction boundaries. raw, err := sql.Open("sqlite", filepath.Join(filepath.Dir(f.root), "rawmark.db")) if err != nil { t.Fatal(err) diff --git a/internal/api/maintenance_uploads.go b/internal/api/maintenance_uploads.go index 902ba00..e2c4e23 100644 --- a/internal/api/maintenance_uploads.go +++ b/internal/api/maintenance_uploads.go @@ -181,7 +181,7 @@ func (s *Server) appendMaintenanceUpload(w http.ResponseWriter, r *http.Request) } func backupResourceError(err error) error { var pathError *os.PathError - // 缺失文件或文件/目录类型错误来自归档内容;其他磁盘故障仍由服务端处理。 + // Missing files or file/directory type mismatches are archive errors; other disk failures remain server errors. if errors.As(err, &pathError) && !errors.Is(pathError, os.ErrNotExist) && !errors.Is(pathError, syscall.ENOTDIR) && !errors.Is(pathError, syscall.EISDIR) { return err } @@ -368,7 +368,7 @@ type cleanupResult struct { func (s *Server) clearTemp(w http.ResponseWriter, r *http.Request) { result := cleanupResult{Failed: []string{}} err := s.maintain("clean_temporary_files_af91ae", func() error { - // temp 为进程专属目录,普通 API 与维护已排空;恢复包和回退备份在其他目录。 + // temp belongs to this process; ordinary APIs and maintenance have drained. Restore archives and rollback backups live elsewhere. temp, err := s.tempDir() if err != nil { return err @@ -381,7 +381,7 @@ func (s *Server) clearTemp(w http.ResponseWriter, r *http.Request) { for _, entry := range entries { targets = append(targets, filepath.Join(temp, entry.Name())) } - // 只清理过期未使用上传;当前会话新上传和待恢复内容不会被清除。 + // Clean only expired unused uploads; retain new uploads from the current session and pending restore contents. uploads := filepath.Join(s.DataDir, ".maintenance", "uploads") entries, err = os.ReadDir(uploads) if err != nil && !errors.Is(err, os.ErrNotExist) { @@ -450,7 +450,7 @@ func (s *Server) clearTemp(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, result) } -// 仅用于服务启动前读取恢复结果,不以空响应伪装已恢复。 +// Read restore results only before service startup; never disguise an empty response as a completed restore. func (s *Server) SetRestoreFailure(err error) { s.maintenanceStatusMu.Lock() defer s.maintenanceStatusMu.Unlock() diff --git a/internal/api/openapi_test.go b/internal/api/openapi_test.go index cdc2177..2c743ff 100644 --- a/internal/api/openapi_test.go +++ b/internal/api/openapi_test.go @@ -38,7 +38,7 @@ func TestOpenAPIResponseContracts(t *testing.T) { } return op } - // 下载、原文、资源代理与 OpenAPI 本身不使用业务 JSON 外壳。 + // Downloads, raw files, resource proxies, and OpenAPI itself do not use the business JSON envelope. raw := map[string]bool{ "get /openapi.json": true, "get /files/{path}": true, "get /raw/{path}": true, "get /plugins/{id}/assets/{revision}/{path}": true, "get /fonts/{id}/file": true, @@ -133,7 +133,7 @@ func TestOpenAPIResponseContracts(t *testing.T) { check("get", "/maintenance/status", f.do(f.admin, "GET", "/api/v1/maintenance/status", "", nil)) check("post", "/maintenance/uploads", f.do(f.admin, "POST", "/api/v1/maintenance/uploads", `{"kind":"settings","size":10}`, nil)) id := stagedPackage(t, f, "full", exportedPackage(t, f, "full")) - // 仅验证提交契约;合成实例不触发真实进程重启或恢复执行。 + // Verify only the submission contract; the synthetic instance must not trigger a real process restart or restore. f.server.Restart = func() {} check("post", "/maintenance/restore", f.do(f.admin, "POST", "/api/v1/maintenance/restore", `{"id":"`+id+`","confirmed":true}`, nil)) } diff --git a/internal/api/plugins_test.go b/internal/api/plugins_test.go index e920720..78fb261 100644 --- a/internal/api/plugins_test.go +++ b/internal/api/plugins_test.go @@ -124,7 +124,7 @@ func TestPluginInstallBoundariesAndData(t *testing.T) { if res := pluginUpload(t, f, f.admin, large); res.StatusCode != 400 { t.Fatalf("解压超限得到 %d", res.StatusCode) } - // 令牌即使有写权限,也不能植入或读取插件代码及私有配置。 + // Even write-capable API tokens cannot install or read plugin code or private configuration. token := decode[struct{ Token string }](t, f.do(f.admin, "POST", "/api/v1/tokens", `{"name":"测试","scope":"write"}`, nil)).Token for _, target := range []string{"/api/v1/plugins", base + "/data", asset} { if code := f.status(guest, "GET", target, "", map[string]string{"Authorization": "Bearer " + token}); code != 403 { diff --git a/internal/api/settings.go b/internal/api/settings.go index 155b52e..a086e22 100644 --- a/internal/api/settings.go +++ b/internal/api/settings.go @@ -12,7 +12,7 @@ import ( "rawmark/internal/events" ) -// 应用设置:一份 JSON 对象,全部设备共用。内容由前端定义,服务端合并与保存,并校验由服务端执行的清理期限 +// Application settings are one JSON object shared across devices. The frontend defines contents; the server merges, saves, and validates server-enforced retention. const settingsKey = "app.settings" func (s *Server) readSettings() (map[string]json.RawMessage, error) { @@ -34,8 +34,8 @@ func (s *Server) getSettings(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, settings) } -// patchSettings 按顶层键合并:请求中的键覆盖原值,值为 null 的键被删除。 -// 不同设备同时修改不同的设置不会互相覆盖;合并后通知其他设备重新读取 +// patchSettings merges top-level keys: supplied values replace existing values, and null removes a key. +// Concurrent changes to different settings do not overwrite each other; notify other devices to reload after merging. func (s *Server) patchSettings(w http.ResponseWriter, r *http.Request) { data, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxJSONBytes)) if err != nil { @@ -86,7 +86,7 @@ func (s *Server) patchSettings(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, settings) } -// changePassword 修改登录密码;当前会话保留,其他设备需重新登录 +// changePassword changes the login password, retaining the current session and requiring other devices to sign in again. func (s *Server) changePassword(w http.ResponseWriter, r *http.Request) { var body struct{ Current, Next string } if !readJSON(w, r, &body) { diff --git a/internal/api/static.go b/internal/api/static.go index b1449ce..a3e63ac 100644 --- a/internal/api/static.go +++ b/internal/api/static.go @@ -12,19 +12,19 @@ import ( "time" ) -// 应用页面的 CSP:CodeMirror 通过