From 88492fdcded31cfe5cb6a8462b27c2964a678dd6 Mon Sep 17 00:00:00 2001 From: eezd Date: Sun, 25 Oct 2026 13:22:16 +0800 Subject: [PATCH] feat(settings): organize categories and include optional font storage --- Dockerfile | 2 +- README.md | 4 +-- cmd/rawmark/main.go | 5 ++++ internal/api/api.go | 11 ++++---- internal/api/openapi.json | 12 ++++++++- internal/api/storage.go | 16 +++++++---- internal/api/storage_test.go | 31 +++++++++++++++++++++ web/font_storage.go | 21 +++++++++++++++ web/src/app/SettingsDialog.vue | 5 ++-- web/src/app/StoragePanel.vue | 12 ++++++--- web/src/app/api.ts | 2 ++ web/src/app/settings.test.ts | 49 +++++++++++----------------------- web/src/app/settingsCatalog.ts | 10 +++---- web/src/app/workspace.ts | 4 +-- web/vite.config.ts | 9 +++++++ 15 files changed, 134 insertions(+), 59 deletions(-) create mode 100644 web/font_storage.go diff --git a/Dockerfile b/Dockerfile index b1113dc..48a0505 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY cmd ./cmd COPY internal ./internal -COPY web/embed.go ./web/ +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 diff --git a/README.md b/README.md index 641c471..b494449 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ docker run -d --name rawmark \ - 请通过 HTTPS 反向代理(Caddy、Nginx 等)访问,并转发 WebSocket(`/api/v1/events`)。 - 多选与导出:Ctrl/Cmd 点击切换选择,Shift 点击选择可见范围;右键批量删除、移动或导出 Markdown。单篇无附件直接下载 `.md`,其他情况下载包含 Markdown 与本地附件的 ZIP;重名目标不会被覆盖,批量操作会汇报单项失败。 - 设置:桌面使用居中窗口,手机使用分类与内容两级布局;支持搜索设置并定位、常用排版项单独重置,自动保存状态与失败提示可见。外观与编辑器分别管理,安装入口位于「关于」。 -- 回收站与存储:回收站支持清空全部;默认按原删除时间保留 30 天,0 表示永不自动删除,服务启动及每小时执行清理,修改期限同样影响已有条目。设置「应用 → 存储空间」按需统计数据目录与外置笔记库,不跟随内部符号链接;附件与回收站包含在笔记库总量中。 +- 回收站与存储:回收站支持清空全部;默认按原删除时间保留 30 天,0 表示永不自动删除,服务启动及每小时执行清理,修改期限同样影响已有条目。设置「存储空间」进入即统计数据目录、外置笔记库及内置可选字体(不含默认 Maple),支持刷新和字体明细;不跟随内部符号链接,附件与回收站包含在笔记库总量中。 - 快捷键:支持按命令名称、ID 或组合键搜索,添加、逐条移除和单项/全部重置,并拒绝冲突绑定。默认跟随 [Typora](https://support.typora.io/Shortcut-Keys/) 的 Windows/Linux 或 macOS 组合,无对应项和插件默认留空;浏览器保留键可自行重新绑定。Ctrl/Cmd+1~6 设置标题,同级再次触发恢复正文;字号可直接输入,编辑器字号随有效输入即时生效。 - 文件目录操作:右键「文件目录」标题或目录空白处,可搜索、打开标签索引、新建笔记、新建目录、导入文档及管理图片;也可点击标题打开菜单。文件夹右键操作以所选文件夹为目标。 - 导入:文件目录或文件夹右键菜单选择「导入」,支持多选 UTF-8 `.md` 文件或单个 ZIP(总上传上限 100 MB)。ZIP 新建同名文件夹并保留笔记层级,本地图片归入各笔记同级 `assets/`;全部改链后移除已迁移且无引用的重复原图,其他附件和未使用图片保留。同名不同内容不覆盖,缺失的本地链接会提示,外部图片不会自动下载。 -- 自动化:「API 令牌」中创建只读或读写令牌,以 `Authorization: Bearer <令牌>` 调用 REST API,接口说明见 `/api/v1/openapi.json`。 +- 自动化:在设置「账户与安全 → API 令牌」中创建只读或读写令牌,以 `Authorization: Bearer <令牌>` 调用 REST API,接口说明见 `/api/v1/openapi.json`。 | 环境变量 | 默认值 | 说明 | | --- | --- | --- | diff --git a/cmd/rawmark/main.go b/cmd/rawmark/main.go index b40b98a..6f74b55 100644 --- a/cmd/rawmark/main.go +++ b/cmd/rawmark/main.go @@ -85,6 +85,11 @@ func run(addr string) error { srv.ThemesDir = filepath.Join(dataDir, "themes") srv.PluginsDir = filepath.Join(dataDir, "plugins") srv.FontsDir = filepath.Join(dataDir, "fonts") + fontsSize, err := web.FontStorage() + if err != nil { + return fmt.Errorf("读取内置字体统计失败: %w", err) + } + srv.BuiltinFontsSize = fontsSize trashDone := make(chan struct{}) go func() { defer close(trashDone) diff --git a/internal/api/api.go b/internal/api/api.go index 324d659..f247c04 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -38,11 +38,12 @@ type Server struct { // 串行化设置的"读取-合并-写入" settingsMu sync.Mutex // ThemesDir 指向数据目录中的主题目录;为空时只有内置主题。 - DataDir string - ThemesDir string - PluginsDir string - FontsDir string - pluginsMu sync.Mutex + DataDir string + ThemesDir string + PluginsDir string + FontsDir string + pluginsMu sync.Mutex + BuiltinFontsSize int64 } func New(v *vault.Vault, a *auth.Manager, st *store.Store, hub *events.Hub, index *search.Index) *Server { diff --git a/internal/api/openapi.json b/internal/api/openapi.json index cbe4170..d7e4748 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -2469,7 +2469,9 @@ "categories", "assetsSize", "trashSize", - "calculatedAt" + "calculatedAt", + "builtinFontsSize", + "customFontsSize" ], "properties": { "totalSize": { @@ -2514,6 +2516,14 @@ } } } + }, + "builtinFontsSize": { + "type": "integer", + "minimum": 0 + }, + "customFontsSize": { + "type": "integer", + "minimum": 0 } } } diff --git a/internal/api/storage.go b/internal/api/storage.go index 7199e5f..164eff3 100644 --- a/internal/api/storage.go +++ b/internal/api/storage.go @@ -17,11 +17,13 @@ type storageCategory struct { } type storageUsage struct { - TotalSize int64 `json:"totalSize"` - Categories []storageCategory `json:"categories"` - AssetsSize int64 `json:"assetsSize"` - TrashSize int64 `json:"trashSize"` - CalculatedAt int64 `json:"calculatedAt"` + TotalSize int64 `json:"totalSize"` + Categories []storageCategory `json:"categories"` + AssetsSize int64 `json:"assetsSize"` + TrashSize int64 `json:"trashSize"` + BuiltinFontsSize int64 `json:"builtinFontsSize"` + CustomFontsSize int64 `json:"customFontsSize"` + CalculatedAt int64 `json:"calculatedAt"` } func (s *Server) getStorage(w http.ResponseWriter, r *http.Request) { @@ -30,6 +32,10 @@ func (s *Server) getStorage(w http.ResponseWriter, r *http.Request) { internalError(w, r, err) return } + usage.BuiltinFontsSize = s.BuiltinFontsSize + usage.CustomFontsSize = usage.Categories[2].Size + usage.Categories[2].Size += s.BuiltinFontsSize + usage.TotalSize += s.BuiltinFontsSize writeJSON(w, http.StatusOK, usage) } diff --git a/internal/api/storage_test.go b/internal/api/storage_test.go index e125c3f..44ea0d9 100644 --- a/internal/api/storage_test.go +++ b/internal/api/storage_test.go @@ -2,9 +2,12 @@ package api import ( "context" + "encoding/json" "errors" + "net/http/httptest" "os" "path/filepath" + "rawmark/internal/vault" "testing" ) @@ -79,3 +82,31 @@ func TestStorageDoesNotReturnPartialSuccess(t *testing.T) { t.Fatalf("请求取消应停止扫描: %v", err) } } + +func TestStorageIncludesOptionalAndImportedFontBytes(t *testing.T) { + root := t.TempDir() + os.MkdirAll(filepath.Join(root, "vault"), 0700) + os.MkdirAll(filepath.Join(root, "fonts/custom"), 0700) + os.WriteFile(filepath.Join(root, "fonts/custom/font.woff2"), make([]byte, 123), 0600) + v, err := vault.Open(filepath.Join(root, "vault")) + if err != nil { + t.Fatal(err) + } + s := &Server{DataDir: root, vault: v, BuiltinFontsSize: 456} + recorder := httptest.NewRecorder() + s.getStorage(recorder, httptest.NewRequest("GET", "/", nil)) + var usage storageUsage + if err = json.Unmarshal(recorder.Body.Bytes(), &usage); err != nil { + t.Fatal(err) + } + if usage.BuiltinFontsSize != 456 || usage.CustomFontsSize != 123 || usage.Categories[2].Size != 579 { + t.Fatalf("字体统计错误: %+v", usage) + } + var sum int64 + for _, category := range usage.Categories { + sum += category.Size + } + if sum != usage.TotalSize { + t.Fatal("分类与总量不一致") + } +} diff --git a/web/font_storage.go b/web/font_storage.go new file mode 100644 index 0000000..3aa6965 --- /dev/null +++ b/web/font_storage.go @@ -0,0 +1,21 @@ +package web + +import ( + "encoding/json" + "io/fs" +) + +// FontStorage 读取构建工具统计的内置可选字体大小,不包含默认 Maple。 +func FontStorage() (int64, error) { + data, err := fs.ReadFile(Dist(), "font-storage.json") + if err != nil { + return 0, err + } + var sizes struct { + OptionalFontsSize int64 `json:"optionalFontsSize"` + } + if err := json.Unmarshal(data, &sizes); err != nil { + return 0, err + } + return sizes.OptionalFontsSize, nil +} diff --git a/web/src/app/SettingsDialog.vue b/web/src/app/SettingsDialog.vue index 444977d..fef3bcc 100644 --- a/web/src/app/SettingsDialog.vue +++ b/web/src/app/SettingsDialog.vue @@ -199,13 +199,14 @@ async function changePassword() { +

API 令牌

- + - +