From a874a39ea5bb72e48e52881b2b9e06b284719a4a Mon Sep 17 00:00:00 2001 From: eezd Date: Mon, 26 Oct 2026 22:52:12 +0800 Subject: [PATCH] docs: add bilingual guides and English API descriptions --- .gitignore | 4 + README.md | 4 +- README.zh-CN.md | 16 +- docs/api.md | 92 ++--- docs/api.zh-CN.md | 94 +++++ docs/deployment.md | 106 +++--- docs/deployment.zh-CN.md | 121 ++++++ docs/development.md | 100 ++--- docs/development.zh-CN.md | 109 ++++++ docs/guide.md | 78 ++-- docs/guide.zh-CN.md | 73 ++++ internal/api/openapi.json | 776 +++++++++++++++++++------------------- 12 files changed, 991 insertions(+), 582 deletions(-) create mode 100644 docs/api.zh-CN.md create mode 100644 docs/deployment.zh-CN.md create mode 100644 docs/development.zh-CN.md create mode 100644 docs/guide.zh-CN.md diff --git a/.gitignore b/.gitignore index 6e567bb..808bfbd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,13 @@ CONTEXT.md docs/* !docs/guide.md +!docs/guide.zh-CN.md !docs/deployment.md +!docs/deployment.zh-CN.md !docs/development.md +!docs/development.zh-CN.md !docs/api.md +!docs/api.zh-CN.md !docs/images/ # 前端 diff --git a/README.md b/README.md index 31d8e66..c14989e 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ These commands listen on localhost only. For a remote server, complete the initi ## Documentation -Detailed guides are currently available in Simplified Chinese. +All guides are available in English and Simplified Chinese. Use the language link at the top of each guide to switch. | What you want to do | Start here | | --- | --- | @@ -67,7 +67,7 @@ Detailed guides are currently available in Simplified Chinese. Bug reports and suggestions are welcome in [Issues](https://github.com/eezd/RawMark/issues). Include your version, deployment method, browser, and steps to reproduce. Remove passwords, tokens, and private note content from logs and screenshots. -Before contributing, read [AGENTS.md](AGENTS.md) and the [development guide](docs/development.md), both currently in Simplified Chinese. RawMark prioritizes simplicity and clarity; please discuss substantial features or architectural changes in an issue first. +Before contributing, read [AGENTS.md](AGENTS.md) (in Simplified Chinese) and the [development guide](docs/development.md). RawMark prioritizes simplicity and clarity; please discuss substantial features or architectural changes in an issue first. ## Acknowledgments and license diff --git a/README.zh-CN.md b/README.zh-CN.md index 8222c5d..b4f4625 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,8 +6,8 @@

English · 简体中文

快速开始 · - 使用指南 · - 部署文档 · + 使用指南 · + 部署文档 · 反馈问题

@@ -50,22 +50,22 @@ docker run -d --name rawmark --restart unless-stopped \ 打开 **http://localhost:21000**,首次访问设置登录密码,即可创建第一篇笔记。数据保存在 Docker 卷 `rawmark-data` 中,重建容器不会丢失。 -以上命令仅监听本机。部署到服务器时,请先完成初始化,再配置 HTTPS 访问;Docker Compose、已有笔记目录接入、备份与升级见[部署文档](docs/deployment.md)。 +以上命令仅监听本机。部署到服务器时,请先完成初始化,再配置 HTTPS 访问;Docker Compose、已有笔记目录接入、备份与升级见[部署文档](docs/deployment.zh-CN.md)。 ## 文档 | 想做什么 | 从这里开始 | | --- | --- | -| 写作、整理笔记、管理图片与个性化设置 | [使用指南](docs/guide.md) | -| 部署、配置存储、备份和升级 | [部署与维护](docs/deployment.md) | -| 本地开发、了解目录职责和运行检查 | [开发指南](docs/development.md) | -| 编写脚本、接入认证和调用接口 | [API 与认证](docs/api.md) | +| 写作、整理笔记、管理图片与个性化设置 | [使用指南](docs/guide.zh-CN.md) | +| 部署、配置存储、备份和升级 | [部署与维护](docs/deployment.zh-CN.md) | +| 本地开发、了解目录职责和运行检查 | [开发指南](docs/development.zh-CN.md) | +| 编写脚本、接入认证和调用接口 | [API 与认证](docs/api.zh-CN.md) | ## 参与项目 欢迎通过 [Issues](https://github.com/eezd/RawMark/issues) 报告问题或提出建议。报告问题时,请附上版本、部署方式、浏览器和复现步骤;日志与截图中请移除密码、令牌及私人笔记内容。 -提交改动前请阅读 [AGENTS.md](AGENTS.md) 和[开发指南](docs/development.md)。RawMark 优先保持简单、清晰,较大的功能或架构调整建议先通过 Issue 讨论。 +提交改动前请阅读 [AGENTS.md](AGENTS.md) 和[开发指南](docs/development.zh-CN.md)。RawMark 优先保持简单、清晰,较大的功能或架构调整建议先通过 Issue 讨论。 ## 致谢与许可 diff --git a/docs/api.md b/docs/api.md index 0d46f21..9767016 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,27 +1,29 @@ -# API 与认证 +# API and authentication -[返回项目首页](../README.zh-CN.md) · [开发指南](development.md) +**English** · [简体中文](api.zh-CN.md) -API 基础路径为 `/api/v1`。完整端点、请求字段、权限和响应以 [OpenAPI 源文件](../internal/api/openapi.json)为准,运行中的实例在 `GET /api/v1/openapi.json` 提供同一文档。测试 `TestOpenAPICoversRoutes` 核对路由覆盖。 +[Project home](../README.md) · [Development guide](development.md) -## 脚本接入 +The API base path is `/api/v1`. The [OpenAPI source](../internal/api/openapi.json) is authoritative for endpoints, request fields, permissions, and responses. A running instance serves the same document at `GET /api/v1/openapi.json`. `TestOpenAPICoversRoutes` checks route coverage. -在「设置 → 账户与安全 → API 令牌」创建只读或读写令牌。受保护 API 只接受 `Authorization: Bearer `,不接受 Cookie,也不要将主令牌放进 URL。 +## Script access -以下只读示例从环境变量读取凭据: +Create a read-only or read/write token under **Settings → Account and security → API tokens**. Protected APIs accept only `Authorization: Bearer `; they do not accept Cookies. Never put the main token in a URL. + +This read-only example reads credentials from environment variables: ```sh -# 先在本机环境中设置 RAWMARK_URL 和 RAWMARK_TOKEN,不要提交真实值。 +# Set RAWMARK_URL and RAWMARK_TOKEN locally first; never commit real values. curl --fail-with-body \ -H "Authorization: Bearer $RAWMARK_TOKEN" \ "$RAWMARK_URL/api/v1/tree" ``` -`rm_` API 令牌在服务端只存哈希,保留 read/write 权限。只读令牌可读取笔记、搜索和执行只读 POST 导出,不能写入;API 令牌不能管理账户、令牌、维护或资源票据,不能为自身扩权。管理操作需要浏览器登录会话。 +The server stores only hashes of `rm_` API tokens, retaining read/write permissions. Read-only tokens can read notes, search, and use read-only POST exports, but cannot write. API tokens cannot manage the account, tokens, maintenance, or resource tickets, or expand their own permissions. Management operations require a browser sign-in session. -## JSON 与 HTTP 语义 +## JSON and HTTP semantics -普通 JSON 成功响应: +A successful ordinary JSON response: ```json { @@ -31,62 +33,62 @@ curl --fail-with-body \ } ``` -`code = 0` 表示成功,非零表示失败;失败时 `message` 是可展示的原因。保留真实 HTTP 状态,不把错误全部转为 200。通用错误码为 HTTP 状态乘 100,特定业务码如下: +`code = 0` means success; nonzero means failure, with `message` providing a displayable reason. Real HTTP status codes are retained rather than converting all errors to 200. Generic error codes are the HTTP status multiplied by 100; specific business codes include: -| HTTP | code | 含义 | +| HTTP | code | Meaning | | --- | --- | --- | -| 400 | 40001 | 验证码错误、无效或已使用 | -| 400 | 40002 | 验证码过期 | -| 400 | 40003 | 修改密码时当前密码错误,不清理身份 | -| 401 | 40101 | 登录密码错误 | -| 412 | 41200 | 文件版本冲突,当前版本在 `data.rev` | +| 400 | 40001 | Incorrect, invalid, or already-used captcha | +| 400 | 40002 | Expired captcha | +| 400 | 40003 | Incorrect current password during a password change; does not clear the identity | +| 401 | 40101 | Incorrect sign-in password | +| 412 | 41200 | File version conflict; the current revision is in `data.rev` | -401 表示需要重新认证;403 表示当前身份没有权限,不能据此注销用户。维护输入或备份内容无效返回 400,占用或空间不足返回 503,文件系统/数据库内部故障返回 500。 +401 requires reauthentication. 403 means the current identity lacks permission and must not sign the user out. Invalid maintenance input or backup contents return 400; a busy service or insufficient space returns 503; internal filesystem/database failures return 500. -**文件原文、下载、WebSocket、204 及 OpenAPI 文档不包 JSON 外壳**。`GET /files/{path}` 返回 Markdown 原文和 `ETag`;`PUT /files/{path}` 接收原文,成功返回包含 `rev` 的 JSON。用 `If-Match: "rev"` 防止覆盖新版本,或 `If-None-Match: *` 要求目标不存在;都不带会直接覆盖,不适合需要冲突保护的写入。 +**Raw files, downloads, WebSocket connections, 204 responses, and the OpenAPI document do not use the JSON envelope.** `GET /files/{path}` returns raw Markdown and an `ETag`; `PUT /files/{path}` accepts raw text and returns JSON containing `rev` on success. Use `If-Match: "rev"` to prevent overwriting a newer version, or `If-None-Match: *` to require that the target does not exist. Omitting both overwrites directly and is unsuitable when conflict protection is needed. -## 响应语言 +## Response language -发送 `Accept-Language: en` 或 `Accept-Language: zh-CN` 选择应用提示的语言,未指定或不支持的语言默认英文。服务端按质量权重选择支持的语言,并在 API 响应的 `Content-Language` 中返回实际语言;API 继续 `Cache-Control: no-store`。`message`、导入警告、维护状态及清理跳过原因会本地化,HTTP 状态、业务码、字段、笔记正文及用户资源名称保持不变。客户端应按状态和 `code` 判断结果,不能匹配翻译后的文案。 +Send `Accept-Language: en` or `Accept-Language: zh-CN` to select the language of application messages. Missing or unsupported languages default to English. The server selects supported languages by quality weight and returns the actual language in `Content-Language`; API responses retain `Cache-Control: no-store`. Messages, import warnings, maintenance status, and cleanup skip reasons are localized. HTTP statuses, business codes, fields, note contents, and user resource names remain unchanged. Clients must use status and `code` to determine results, not match translated text. -账户语言位于 `/settings` 的 `locale` 字段,仅支持 `en`、`zh-CN`;PATCH 传 `null` 删除覆盖,恢复默认英文。该字段与其他设置一同导出、导入和同步;登录页的临时选择不写入账户。请求头只影响当前响应,不修改账户语言。 +Account language is the `locale` field on `/settings`, supporting only `en` and `zh-CN`. PATCH with `null` removes the override and restores default English. The field is exported, imported, and synchronized with other settings. The sign-in page's temporary choice does not write to the account. Request headers affect only the current response, not account language. -## 浏览器登录 +## Browser sign-in -实例没有用户名、注册或角色系统。尚未初始化时,`POST /auth/setup` 使用 `{ password }` 设置密码并返回会话;已经初始化的实例不能再次初始化。 +There are no usernames, registration, or roles. Before initialization, `POST /auth/setup` sets the password using `{ password }` and returns a session. An initialized instance cannot be initialized again. -正常登录流程: +Normal sign-in flow: -1. `POST /auth/captcha` 获取 `{ captchaId, imageDataUrl, expiresAt }`。 -2. 展示图片,用户填写密码与六位数字验证码。 -3. `POST /auth/login` 提交 `{ password, captchaId, captchaCode }`。 -4. 成功返回 `{ token, expiresAt }`,再通过 `GET /auth/me` 恢复真实身份与权限。 +1. Call `POST /auth/captcha` to get `{ captchaId, imageDataUrl, expiresAt }`. +2. Show the image and ask for the password and six-digit captcha. +3. Submit `{ password, captchaId, captchaCode }` to `POST /auth/login`. +4. On success, receive `{ token, expiresAt }`, then call `GET /auth/me` to restore the actual identity and permissions. -以上字段均位于普通响应的 `data` 中。刷新验证码时传 `{ replaceId }` 使旧挑战失效。挑战绑定来源,五分钟过期;每次登录提交一次性消费,即使密码或验证码错误也不能重用,失败必须刷新。签发与登录均保留限流。 +These response fields are inside the ordinary envelope's `data`. To refresh a captcha, send `{ replaceId }` to invalidate the old challenge. Challenges are bound to their source, expire after five minutes, and are consumed once on every sign-in submission, even if the password or captcha is wrong. Refresh after any failure. Issuance and sign-in remain rate-limited. -`rms_` 浏览器会话 Token 保存在 localStorage 的 `rawmark.token`,固定三十天有效、不滑动续期,服务端仅存哈希。退出调用 `POST /auth/logout`,服务端撤销成功返回 204 后才清理本地身份;请求失败时保留身份并提示错误,不能仅删除本地 Token 宣称注销完成。 +The `rms_` browser session token is stored under localStorage key `rawmark.token`, lasts a fixed thirty days without sliding renewal, and is stored only as a hash on the server. Sign out through `POST /auth/logout`; clear local identity only after server revocation succeeds with 204. On request failure, retain identity and show the error. Merely deleting a local token is not a completed logout. -localStorage 可被同源脚本读取;有完整前端权限的插件必须视为可信代码,不能承诺凭据隔离。 +Same-origin scripts can read localStorage. Plugins with full frontend privileges must be treated as trusted code; credential isolation is not guaranteed. -## 原生资源与事件 +## Native resources and events -原生图片、字体、插件模块与浏览器 WebSocket 不能统一设置认证头,通过 `POST /auth/tickets` 获取受限资源 URL;主 Token 不进入 URL。票据服务端只保存哈希,每次读取检查父会话,父会话退出后即失效。 +Native images, fonts, plugin modules, and browser WebSocket connections cannot all set authentication headers. Obtain restricted resource URLs through `POST /auth/tickets`; the main token never enters the URL. The server stores only ticket hashes and checks the parent session on every read. Tickets stop working when that session signs out. -- 图片/字体票据不超过父会话有效期。 -- 插件票据额外绑定插件 ID 和版本,支持相对模块导入。 -- 事件握手票据一分钟有效、仅消费一次;已连接事件仍会检查父凭据。 -- 脚本 WebSocket 客户端可以通过 Bearer 请求头继续连接 `/api/v1/events`。 +- Image/font tickets do not outlive the parent session. +- Plugin tickets also bind the plugin ID and version and support relative module imports. +- Event handshake tickets last one minute and are consumed once. Established connections continue checking the parent credential. +- Script WebSocket clients can still connect to `/api/v1/events` with a Bearer request header. -私有响应使用 `Cache-Control: no-store` 与 `Referrer-Policy: no-referrer`,API 和私有资源不进入 PWA 静态缓存。代理日志也需脱敏,见[部署文档](deployment.md#https-与反向代理)。 +Private responses use `Cache-Control: no-store` and `Referrer-Policy: no-referrer`. APIs and private resources never enter the PWA's static cache. Proxy logs also need redaction; see [Deployment and maintenance](deployment.md#https-and-reverse-proxy). -## 前端请求责任 +## Frontend request ownership -请求处理、认证与路由守卫分别位于 `http/`、`pinia/` 和 `router/`。匿名跳登录并保留合法站内回跳,拒绝外部地址与循环跳转。401 清理当前身份、权限与动态路由,403 保留身份。 +Request handling, authentication, and route guards live in `http/`, `pinia/`, and `router/`, respectively. Anonymous users are redirected to sign-in with a valid internal return path; external addresses and redirect loops are rejected. 401 clears the current identity, permissions, and dynamic routes; 403 retains identity. -每次替换或退出会话更新代号、取消旧请求。用户信息、路由安装、异步响应与排队写入均须检查所属会话;旧结果按 Axios 取消语义拒绝,不能覆盖或注销新身份,也不提示取消错误。 +Each session replacement or logout changes the session generation and cancels old requests. User information, route installation, asynchronous responses, and queued writes must verify session ownership. Old results reject using Axios cancellation semantics. They cannot overwrite or sign out a new identity, and cancellation errors are not shown. -同一错误只由一个责任层提示。请求默认负责展示,已有行内状态、保存重试或批量汇总的 API 使用 `errorOwner: 'caller'`;失败依然 reject,不以空列表或默认值伪装成功。 +Only one layer displays a given error. Requests own display by default; APIs with inline status, save retries, or bulk summaries use `errorOwner: 'caller'`. Failures still reject rather than masquerading as successful empty lists or defaults. -## 旧客户端迁移 +## Migrating older clients -统一协议需要前后端同时更新,不保留 Cookie/Bearer 或两种 JSON 格式兜底。旧 `rm_` API 令牌无需重建,但调用方要改为读取 `{ code, data, message }` 中的 `data`;文件及导出字节语义不变。旧浏览器页面需重新登录,不能连接新旧混用的后端。 +The unified protocol requires frontend and backend updates together. There is no Cookie/Bearer fallback or support for two JSON formats. Existing `rm_` API tokens need not be recreated, but callers must read `data` from `{ code, data, message }`. Raw file and export byte semantics remain unchanged. Old browser pages must sign in again and cannot use mixed old/new backends. diff --git a/docs/api.zh-CN.md b/docs/api.zh-CN.md new file mode 100644 index 0000000..b44be3f --- /dev/null +++ b/docs/api.zh-CN.md @@ -0,0 +1,94 @@ +# API 与认证 + +[English](api.md) · **简体中文** + +[返回项目首页](../README.zh-CN.md) · [开发指南](development.zh-CN.md) + +API 基础路径为 `/api/v1`。完整端点、请求字段、权限和响应以 [OpenAPI 源文件](../internal/api/openapi.json)为准,运行中的实例在 `GET /api/v1/openapi.json` 提供同一文档。测试 `TestOpenAPICoversRoutes` 核对路由覆盖。 + +## 脚本接入 + +在「设置 → 账户与安全 → API 令牌」创建只读或读写令牌。受保护 API 只接受 `Authorization: Bearer `,不接受 Cookie,也不要将主令牌放进 URL。 + +以下只读示例从环境变量读取凭据: + +```sh +# Set RAWMARK_URL and RAWMARK_TOKEN locally first; never commit real values. +curl --fail-with-body \ + -H "Authorization: Bearer $RAWMARK_TOKEN" \ + "$RAWMARK_URL/api/v1/tree" +``` + +`rm_` API 令牌在服务端只存哈希,保留 read/write 权限。只读令牌可读取笔记、搜索和执行只读 POST 导出,不能写入;API 令牌不能管理账户、令牌、维护或资源票据,不能为自身扩权。管理操作需要浏览器登录会话。 + +## JSON 与 HTTP 语义 + +普通 JSON 成功响应: + +```json +{ + "code": 0, + "data": { "ok": true }, + "message": "Success" +} +``` + +`code = 0` 表示成功,非零表示失败;失败时 `message` 是可展示的原因。保留真实 HTTP 状态,不把错误全部转为 200。通用错误码为 HTTP 状态乘 100,特定业务码如下: + +| HTTP | code | 含义 | +| --- | --- | --- | +| 400 | 40001 | 验证码错误、无效或已使用 | +| 400 | 40002 | 验证码过期 | +| 400 | 40003 | 修改密码时当前密码错误,不清理身份 | +| 401 | 40101 | 登录密码错误 | +| 412 | 41200 | 文件版本冲突,当前版本在 `data.rev` | + +401 表示需要重新认证;403 表示当前身份没有权限,不能据此注销用户。维护输入或备份内容无效返回 400,占用或空间不足返回 503,文件系统/数据库内部故障返回 500。 + +**文件原文、下载、WebSocket、204 及 OpenAPI 文档不包 JSON 外壳**。`GET /files/{path}` 返回 Markdown 原文和 `ETag`;`PUT /files/{path}` 接收原文,成功返回包含 `rev` 的 JSON。用 `If-Match: "rev"` 防止覆盖新版本,或 `If-None-Match: *` 要求目标不存在;都不带会直接覆盖,不适合需要冲突保护的写入。 + +## 响应语言 + +发送 `Accept-Language: en` 或 `Accept-Language: zh-CN` 选择应用提示的语言,未指定或不支持的语言默认英文。服务端按质量权重选择支持的语言,并在 API 响应的 `Content-Language` 中返回实际语言;API 继续 `Cache-Control: no-store`。`message`、导入警告、维护状态及清理跳过原因会本地化,HTTP 状态、业务码、字段、笔记正文及用户资源名称保持不变。客户端应按状态和 `code` 判断结果,不能匹配翻译后的文案。 + +账户语言位于 `/settings` 的 `locale` 字段,仅支持 `en`、`zh-CN`;PATCH 传 `null` 删除覆盖,恢复默认英文。该字段与其他设置一同导出、导入和同步;登录页的临时选择不写入账户。请求头只影响当前响应,不修改账户语言。 + +## 浏览器登录 + +实例没有用户名、注册或角色系统。尚未初始化时,`POST /auth/setup` 使用 `{ password }` 设置密码并返回会话;已经初始化的实例不能再次初始化。 + +正常登录流程: + +1. `POST /auth/captcha` 获取 `{ captchaId, imageDataUrl, expiresAt }`。 +2. 展示图片,用户填写密码与六位数字验证码。 +3. `POST /auth/login` 提交 `{ password, captchaId, captchaCode }`。 +4. 成功返回 `{ token, expiresAt }`,再通过 `GET /auth/me` 恢复真实身份与权限。 + +以上字段均位于普通响应的 `data` 中。刷新验证码时传 `{ replaceId }` 使旧挑战失效。挑战绑定来源,五分钟过期;每次登录提交一次性消费,即使密码或验证码错误也不能重用,失败必须刷新。签发与登录均保留限流。 + +`rms_` 浏览器会话 Token 保存在 localStorage 的 `rawmark.token`,固定三十天有效、不滑动续期,服务端仅存哈希。退出调用 `POST /auth/logout`,服务端撤销成功返回 204 后才清理本地身份;请求失败时保留身份并提示错误,不能仅删除本地 Token 宣称注销完成。 + +localStorage 可被同源脚本读取;有完整前端权限的插件必须视为可信代码,不能承诺凭据隔离。 + +## 原生资源与事件 + +原生图片、字体、插件模块与浏览器 WebSocket 不能统一设置认证头,通过 `POST /auth/tickets` 获取受限资源 URL;主 Token 不进入 URL。票据服务端只保存哈希,每次读取检查父会话,父会话退出后即失效。 + +- 图片/字体票据不超过父会话有效期。 +- 插件票据额外绑定插件 ID 和版本,支持相对模块导入。 +- 事件握手票据一分钟有效、仅消费一次;已连接事件仍会检查父凭据。 +- 脚本 WebSocket 客户端可以通过 Bearer 请求头继续连接 `/api/v1/events`。 + +私有响应使用 `Cache-Control: no-store` 与 `Referrer-Policy: no-referrer`,API 和私有资源不进入 PWA 静态缓存。代理日志也需脱敏,见[部署文档](deployment.zh-CN.md#https-与反向代理)。 + +## 前端请求责任 + +请求处理、认证与路由守卫分别位于 `http/`、`pinia/` 和 `router/`。匿名跳登录并保留合法站内回跳,拒绝外部地址与循环跳转。401 清理当前身份、权限与动态路由,403 保留身份。 + +每次替换或退出会话更新代号、取消旧请求。用户信息、路由安装、异步响应与排队写入均须检查所属会话;旧结果按 Axios 取消语义拒绝,不能覆盖或注销新身份,也不提示取消错误。 + +同一错误只由一个责任层提示。请求默认负责展示,已有行内状态、保存重试或批量汇总的 API 使用 `errorOwner: 'caller'`;失败依然 reject,不以空列表或默认值伪装成功。 + +## 旧客户端迁移 + +统一协议需要前后端同时更新,不保留 Cookie/Bearer 或两种 JSON 格式兜底。旧 `rm_` API 令牌无需重建,但调用方要改为读取 `{ code, data, message }` 中的 `data`;文件及导出字节语义不变。旧浏览器页面需重新登录,不能连接新旧混用的后端。 diff --git a/docs/deployment.md b/docs/deployment.md index 20a46e6..a65c0b1 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,12 +1,14 @@ -# 部署与维护 +# Deployment and maintenance -[返回项目首页](../README.zh-CN.md) · [使用指南](guide.md) · [开发指南](development.md) +**English** · [简体中文](deployment.zh-CN.md) -RawMark 将前端嵌入 Go 服务,运行时使用 SQLite 与文件系统,不需要额外数据库服务。目前从源码构建 Docker 镜像;以下命令不依赖预先发布的镜像仓库。 +[Project home](../README.md) · [User guide](guide.md) · [Development guide](development.md) -## Docker 快速启动 +RawMark embeds the frontend in its Go service and uses SQLite and the filesystem at runtime. No separate database service is needed. Docker images are currently built from source; the commands below do not depend on a prepublished image registry. -安装 Git、Docker 后: +## Docker quick start + +With Git and Docker installed: ```sh git clone https://github.com/eezd/RawMark.git @@ -18,17 +20,17 @@ docker run -d --name rawmark --restart unless-stopped \ rawmark ``` -访问 http://localhost:21000 完成首次密码设置。命名卷 `rawmark-data` 保存应用数据,重新创建容器时应继续挂载同一个卷,不要删除它。服务器上的本机端口可通过 SSH 隧道访问,再完成初始化: +Open http://localhost:21000 to set the initial password. The named volume `rawmark-data` holds application data; reuse it when recreating the container and do not delete it. For a remote server, reach its local port through an SSH tunnel to complete setup: ```sh ssh -L 21000:127.0.0.1:21000 user@your-server ``` -容器默认以 UID/GID `65532:65532` 运行,Docker 新建命名卷会继承镜像数据目录的权限。 +The container defaults to UID/GID `65532:65532`. A new Docker named volume inherits permissions from the image's data directory. ## Docker Compose -仓库提供 [deploy/docker-compose.yml](../deploy/docker-compose.yml),使用宿主机的 `deploy/data/` 持久化。Linux 上首次部署可执行: +The repository includes [deploy/docker-compose.yml](../deploy/docker-compose.yml), which persists data in the host's `deploy/data/`. For a first deployment on Linux: ```sh mkdir -p deploy/data @@ -36,30 +38,30 @@ sudo chown 65532:65532 deploy/data docker compose -f deploy/docker-compose.yml up -d --build --wait ``` -该配置默认发布到所有网卡的 **21000** 端口。对公网开放前完成初始化并设置访问边界;使用反向代理时,可将 Compose 端口绑定改为 `127.0.0.1:${RAWMARK_PORT:-21000}:8080`。自定义端口可使用 `RAWMARK_PORT=21001 docker compose -f deploy/docker-compose.yml up -d --build --wait`。 +This configuration publishes port **21000** on all network interfaces by default. Complete initialization and configure access boundaries before exposing it publicly. With a reverse proxy, you can change the Compose port binding to `127.0.0.1:${RAWMARK_PORT:-21000}:8080`. For a custom port, use `RAWMARK_PORT=21001 docker compose -f deploy/docker-compose.yml up -d --build --wait`. -不要把快速开始中的容器与 Compose 实例同时指向同一份数据库或笔记库;切换部署方式前先停止旧实例、备份并确认数据位置。 +Do not point the quick-start container and a Compose instance at the same database or vault concurrently. Stop the old instance, back up, and verify data locations before switching deployment methods. -## 存储与配置 +## Storage and configuration -| 环境变量 | 默认值 | 说明 | +| Environment variable | Default | Description | | --- | --- | --- | -| `RAWMARK_DATA` | `/data` | 应用数据库、索引、资源与默认笔记库所在目录 | -| `RAWMARK_VAULT` | `$RAWMARK_DATA/vault` | Markdown 笔记库,可以单独挂载已有目录 | -| `RAWMARK_ADDR` | `:8080` | 容器或二进制内部的监听地址 | +| `RAWMARK_DATA` | `/data` | Application database, index, resources, and default vault directory | +| `RAWMARK_VAULT` | `$RAWMARK_DATA/vault` | Markdown vault; can be a separately mounted existing directory | +| `RAWMARK_ADDR` | `:8080` | Listen address inside the container or binary | -主要数据: +Main data locations: -| 路径 | 内容 | +| Path | Contents | | --- | --- | -| `vault/` | Markdown 文件、附件与笔记库元数据;设定外置笔记库时位于对应目录 | -| `rawmark.db` | 密码、会话、API 令牌、设置等应用状态 | -| `index.db` | 可重建的搜索索引 | -| `fonts/`、`themes/`、`plugins/` | 用户导入的字体、主题和插件 | -| `backups/` | 导入或恢复操作前生成的回退备份 | -| `logs/` | 运行日志 | +| `vault/` | Markdown files, attachments, and vault metadata; stored at the configured location when using an external vault | +| `rawmark.db` | Password, sessions, API tokens, settings, and other application state | +| `index.db` | Rebuildable search index | +| `fonts/`, `themes/`, `plugins/` | User-imported fonts, themes, and plugins | +| `backups/` | Rollback backups created before imports or restores | +| `logs/` | Runtime logs | -要接入已有文件夹,可额外挂载它并设置 `RAWMARK_VAULT`: +To use an existing folder, mount it separately and set `RAWMARK_VAULT`: ```sh docker run -d --name rawmark --restart unless-stopped \ @@ -71,49 +73,49 @@ docker run -d --name rawmark --restart unless-stopped \ rawmark ``` -先创建 `$HOME/rawmark-data` 和 `$HOME/notes`,确保所选用户可写。此例以宿主机当前用户运行,适合管理自己的已有目录;不设置 `--user` 时须给容器默认用户写权限。RawMark 会修改笔记和链接,首次接入前请备份已有目录。 +Create `$HOME/rawmark-data` and `$HOME/notes` first and ensure the selected user can write to them. This example runs as the current host user, which suits an existing personal directory. Without `--user`, grant write access to the container's default user. RawMark modifies notes and links, so back up an existing directory before first use. -## HTTPS 与反向代理 +## HTTPS and reverse proxy -远程访问使用 HTTPS。代理到 RawMark 时需要转发 Host、协议与来源地址,并启用 WebSocket 升级和长连接;浏览器事件连接使用 `/api/v1/resources//events/`。代理上传限制也应满足实际导入需求。 +Use HTTPS for remote access. The proxy must forward Host, protocol, and client address information and support WebSocket upgrades and long-lived connections. Browser event connections use `/api/v1/resources//events/`. Proxy upload limits should accommodate your imports. -[仓库 Nginx 配置](../deploy/nginx/raw.134333.xyz.conf)包含 WebSocket 和票据日志脱敏示例,但其中域名、证书 include、反爬 include、端口及日志路径属于原部署环境,**需要按自己的服务器调整,不能原样安装**。`$connection_upgrade` 的 map 也需要在 Nginx 的 `http` 上下文定义。 +The [repository's Nginx configuration](../deploy/nginx/raw.134333.xyz.conf) includes examples of WebSocket support and redacted ticket logging. Its domain, certificate includes, anti-bot includes, port, and log paths belong to the original deployment environment: **adapt them to your server rather than installing the file unchanged**. Define the `$connection_upgrade` map in Nginx's `http` context as well. -资源票据是凭据:代理、CDN 和访问日志不得记录明文票据、查询参数或携带凭据的 Referer;应用的 `no-store` 不能代替代理日志脱敏。Nginx 错误日志也可能包含原始请求路径,仓库示例对票据资源路径关闭错误日志,保留脱敏访问日志及应用错误日志。 +Resource tickets are credentials. Proxies, CDNs, and access logs must not record plaintext tickets, query parameters, or credential-bearing Referer headers. The application's `no-store` policy does not replace proxy log redaction. Nginx error logs can also contain raw request paths; the repository example disables error logging for ticket resource paths while retaining redacted access logs and application error logs. -若使用 CDN,需独立完成自定义域名与 TLS 证书配置,并确认 API、私有资源和 WebSocket 不被错误缓存;仅配置 DNS 不代表 HTTPS 接入已完成。 +When using a CDN, configure its custom domain and TLS certificate separately, and ensure APIs, private resources, and WebSocket connections are not cached incorrectly. DNS configuration alone does not complete HTTPS setup. -## 备份与恢复 +## Backup and restore -在「设置 → 存储空间」选择所需导出类型: +Choose an export type under **Settings → Storage**: -| 类型 | 适合用途 | 恢复行为 | +| Type | Intended use | Restore behavior | | --- | --- | --- | -| 普通 Markdown 笔记库 | 迁移到其他笔记工具 | 导入到新文件夹,不替换实例 | -| 设置与资源 | 迁移偏好、插件数据、字体/主题/插件 | 合并资源,同 ID 替换,包外资源保留 | -| 完整实例 | 迁移或恢复 RawMark | 替换业务数据,包含登录信息、回收站和资源,完成后重新登录 | +| Plain Markdown vault | Move to another note-taking tool | Imports into a new folder without replacing the instance | +| Settings and resources | Migrate preferences, plugin data, fonts, themes, and plugins | Merges resources, replaces matching IDs, and retains resources absent from the archive | +| Full instance | Migrate or restore RawMark | Replaces application data, including login credentials, recycle bin, and resources; requires signing in again | -设置导入与完整恢复只接受本程序的版本化 ZIP,应用前会在 `backups/` 保留回退包。上传顺序分块,每块 8 MiB,压缩包上限 2 GiB,展开上限 8 GiB、100000 项。不包含运维环境配置、派生索引或临时文件。 +Settings imports and full restores accept only RawMark's versioned ZIP format. A rollback archive is kept in `backups/` before applying changes. Uploads use sequential 8 MiB chunks. Limits are 2 GiB compressed, 8 GiB expanded, and 100,000 entries. Operational environment configuration, derived indexes, and temporary files are excluded. -完整恢复前停止其他程序直接改写笔记库。维护时暂时暂停 API 操作,关闭数据库和后台任务后恢复,中断时回退操作前备份。导出的完整包包含账户和笔记等敏感数据,应妥善保管,不提交到仓库或附在公开 Issue 中。 +Stop other programs from directly modifying the vault before a full restore. API operations pause during maintenance; the restore closes databases and background tasks, and rolls back to the pre-operation backup if interrupted. Full exports contain sensitive account and note data. Store them securely; do not commit them or attach them to public issues. -独立于应用备份,升级前建议停止服务后备份完整数据目录、外置笔记库、部署配置与旧镜像。SQLite 运行时可能有 WAL 文件,不要只复制正在写入的 `rawmark.db` 来冒充一致备份。 +In addition to application backups, stop the service before upgrades and back up the complete data directory, external vault, deployment configuration, and previous image. SQLite may have WAL files while running; copying only an actively written `rawmark.db` is not a consistent backup. -## 升级与回退 +## Upgrade and rollback -1. 确认目标版本的行为和数据兼容性,保留当前镜像及部署配置。 -2. 停止旧服务,备份应用数据与外置笔记库。 -3. 更新源码并重新构建,使用同一份挂载启动新实例;不要并行运行新旧服务。 -4. 核对容器健康、登录、笔记与附件。回退时使用配套的旧镜像与数据备份。 +1. Check the target version's behavior and data compatibility; retain the current image and deployment configuration. +2. Stop the old service and back up application data and any external vault. +3. Update the source, rebuild, and start the new instance with the same mounts. Do not run old and new services concurrently. +4. Verify container health, sign-in, notes, and attachments. Roll back using the matching previous image and data backup. -Bearer 认证升级需要前后端一起发布。旧 Cookie 页面不能继续使用,需重新登录;启动程序会沿现有 SQLite 初始化流程创建资源票据表和索引,笔记、密码、设置与已有 API 令牌保留。第三方客户端的 JSON 解包方式须遵循[当前协议](api.md)。 +The Bearer authentication upgrade requires the frontend and backend to be released together. Old Cookie-based pages require signing in again. Startup creates the resource-ticket table and indexes through the existing SQLite initialization flow; notes, passwords, settings, and existing API tokens are retained. Third-party clients must follow the [current JSON protocol](api.md). -`scripts/remove-sharing.sql` 和 `scripts/reset-shortcuts.sql` 是历史版本的一次性迁移:前者移除旧分享数据,后者清空旧快捷键覆盖。**新部署无需运行,已执行过的实例不要重跑**;只有确认来自对应旧版本且需要该迁移时,才在停服并备份后单独执行。 +`scripts/remove-sharing.sql` and `scripts/reset-shortcuts.sql` are one-time historical migrations. The former removes old sharing data; the latter clears old shortcut overrides. **New deployments do not need them, and instances that have already run them must not rerun them.** Execute one separately only after confirming an applicable old version, stopping the service, and backing up. -## 日常维护与排查 +## Routine maintenance and troubleshooting -- 容器自带健康检查,也可访问 `GET /api/v1/health`;健康不代表所有业务流程已验证。 -- 设置内的优化/重建仅处理派生索引;清理临时文件不删除用户文件、活动上传或恢复备份。 -- 重置设置保留账户、资源、插件存储和本机标签页。 -- 日志位于 `logs/`,轮转保留三份,每份至多 10 MiB;可在设置中导出日志及版本摘要。该功能不补录历史日志。 -- 更新后界面异常先核对前后端是否来自同一构建,再查看浏览器网络请求和应用日志;插件与外观恢复入口见[使用指南](guide.md#插件与界面恢复)。 +- The container includes a health check. You can also call `GET /api/v1/health`; a healthy result does not verify every business workflow. +- Index optimization/rebuilding affects only derived indexes. Temporary-file cleanup does not delete user files, active uploads, or restore backups. +- Resetting settings retains the account, resources, plugin storage, and local tabs. +- Logs live in `logs/`, with three rotated files retained, each up to 10 MiB. Settings can export logs and a version summary; this does not reconstruct historical logs. +- If the interface breaks after an update, first check that the frontend and backend come from the same build, then inspect browser network requests and application logs. See the [user guide](guide.md#plugins-and-interface-recovery) for plugin and appearance recovery entry points. diff --git a/docs/deployment.zh-CN.md b/docs/deployment.zh-CN.md new file mode 100644 index 0000000..4a1ec74 --- /dev/null +++ b/docs/deployment.zh-CN.md @@ -0,0 +1,121 @@ +# 部署与维护 + +[English](deployment.md) · **简体中文** + +[返回项目首页](../README.zh-CN.md) · [使用指南](guide.zh-CN.md) · [开发指南](development.zh-CN.md) + +RawMark 将前端嵌入 Go 服务,运行时使用 SQLite 与文件系统,不需要额外数据库服务。目前从源码构建 Docker 镜像;以下命令不依赖预先发布的镜像仓库。 + +## Docker 快速启动 + +安装 Git、Docker 后: + +```sh +git clone https://github.com/eezd/RawMark.git +cd RawMark +docker build -t rawmark . +docker run -d --name rawmark --restart unless-stopped \ + -p 127.0.0.1:21000:8080 \ + -v rawmark-data:/data \ + rawmark +``` + +访问 http://localhost:21000 完成首次密码设置。命名卷 `rawmark-data` 保存应用数据,重新创建容器时应继续挂载同一个卷,不要删除它。服务器上的本机端口可通过 SSH 隧道访问,再完成初始化: + +```sh +ssh -L 21000:127.0.0.1:21000 user@your-server +``` + +容器默认以 UID/GID `65532:65532` 运行,Docker 新建命名卷会继承镜像数据目录的权限。 + +## Docker Compose + +仓库提供 [deploy/docker-compose.yml](../deploy/docker-compose.yml),使用宿主机的 `deploy/data/` 持久化。Linux 上首次部署可执行: + +```sh +mkdir -p deploy/data +sudo chown 65532:65532 deploy/data +docker compose -f deploy/docker-compose.yml up -d --build --wait +``` + +该配置默认发布到所有网卡的 **21000** 端口。对公网开放前完成初始化并设置访问边界;使用反向代理时,可将 Compose 端口绑定改为 `127.0.0.1:${RAWMARK_PORT:-21000}:8080`。自定义端口可使用 `RAWMARK_PORT=21001 docker compose -f deploy/docker-compose.yml up -d --build --wait`。 + +不要把快速开始中的容器与 Compose 实例同时指向同一份数据库或笔记库;切换部署方式前先停止旧实例、备份并确认数据位置。 + +## 存储与配置 + +| 环境变量 | 默认值 | 说明 | +| --- | --- | --- | +| `RAWMARK_DATA` | `/data` | 应用数据库、索引、资源与默认笔记库所在目录 | +| `RAWMARK_VAULT` | `$RAWMARK_DATA/vault` | Markdown 笔记库,可以单独挂载已有目录 | +| `RAWMARK_ADDR` | `:8080` | 容器或二进制内部的监听地址 | + +主要数据: + +| 路径 | 内容 | +| --- | --- | +| `vault/` | Markdown 文件、附件与笔记库元数据;设定外置笔记库时位于对应目录 | +| `rawmark.db` | 密码、会话、API 令牌、设置等应用状态 | +| `index.db` | 可重建的搜索索引 | +| `fonts/`、`themes/`、`plugins/` | 用户导入的字体、主题和插件 | +| `backups/` | 导入或恢复操作前生成的回退备份 | +| `logs/` | 运行日志 | + +要接入已有文件夹,可额外挂载它并设置 `RAWMARK_VAULT`: + +```sh +docker run -d --name rawmark --restart unless-stopped \ + --user "$(id -u):$(id -g)" \ + -p 127.0.0.1:21000:8080 \ + -v "$HOME/rawmark-data:/data" \ + -v "$HOME/notes:/vault" \ + -e RAWMARK_VAULT=/vault \ + rawmark +``` + +先创建 `$HOME/rawmark-data` 和 `$HOME/notes`,确保所选用户可写。此例以宿主机当前用户运行,适合管理自己的已有目录;不设置 `--user` 时须给容器默认用户写权限。RawMark 会修改笔记和链接,首次接入前请备份已有目录。 + +## HTTPS 与反向代理 + +远程访问使用 HTTPS。代理到 RawMark 时需要转发 Host、协议与来源地址,并启用 WebSocket 升级和长连接;浏览器事件连接使用 `/api/v1/resources//events/`。代理上传限制也应满足实际导入需求。 + +[仓库 Nginx 配置](../deploy/nginx/raw.134333.xyz.conf)包含 WebSocket 和票据日志脱敏示例,但其中域名、证书 include、反爬 include、端口及日志路径属于原部署环境,**需要按自己的服务器调整,不能原样安装**。`$connection_upgrade` 的 map 也需要在 Nginx 的 `http` 上下文定义。 + +资源票据是凭据:代理、CDN 和访问日志不得记录明文票据、查询参数或携带凭据的 Referer;应用的 `no-store` 不能代替代理日志脱敏。Nginx 错误日志也可能包含原始请求路径,仓库示例对票据资源路径关闭错误日志,保留脱敏访问日志及应用错误日志。 + +若使用 CDN,需独立完成自定义域名与 TLS 证书配置,并确认 API、私有资源和 WebSocket 不被错误缓存;仅配置 DNS 不代表 HTTPS 接入已完成。 + +## 备份与恢复 + +在「设置 → 存储空间」选择所需导出类型: + +| 类型 | 适合用途 | 恢复行为 | +| --- | --- | --- | +| 普通 Markdown 笔记库 | 迁移到其他笔记工具 | 导入到新文件夹,不替换实例 | +| 设置与资源 | 迁移偏好、插件数据、字体/主题/插件 | 合并资源,同 ID 替换,包外资源保留 | +| 完整实例 | 迁移或恢复 RawMark | 替换业务数据,包含登录信息、回收站和资源,完成后重新登录 | + +设置导入与完整恢复只接受本程序的版本化 ZIP,应用前会在 `backups/` 保留回退包。上传顺序分块,每块 8 MiB,压缩包上限 2 GiB,展开上限 8 GiB、100000 项。不包含运维环境配置、派生索引或临时文件。 + +完整恢复前停止其他程序直接改写笔记库。维护时暂时暂停 API 操作,关闭数据库和后台任务后恢复,中断时回退操作前备份。导出的完整包包含账户和笔记等敏感数据,应妥善保管,不提交到仓库或附在公开 Issue 中。 + +独立于应用备份,升级前建议停止服务后备份完整数据目录、外置笔记库、部署配置与旧镜像。SQLite 运行时可能有 WAL 文件,不要只复制正在写入的 `rawmark.db` 来冒充一致备份。 + +## 升级与回退 + +1. 确认目标版本的行为和数据兼容性,保留当前镜像及部署配置。 +2. 停止旧服务,备份应用数据与外置笔记库。 +3. 更新源码并重新构建,使用同一份挂载启动新实例;不要并行运行新旧服务。 +4. 核对容器健康、登录、笔记与附件。回退时使用配套的旧镜像与数据备份。 + +Bearer 认证升级需要前后端一起发布。旧 Cookie 页面不能继续使用,需重新登录;启动程序会沿现有 SQLite 初始化流程创建资源票据表和索引,笔记、密码、设置与已有 API 令牌保留。第三方客户端的 JSON 解包方式须遵循[当前协议](api.zh-CN.md)。 + +`scripts/remove-sharing.sql` 和 `scripts/reset-shortcuts.sql` 是历史版本的一次性迁移:前者移除旧分享数据,后者清空旧快捷键覆盖。**新部署无需运行,已执行过的实例不要重跑**;只有确认来自对应旧版本且需要该迁移时,才在停服并备份后单独执行。 + +## 日常维护与排查 + +- 容器自带健康检查,也可访问 `GET /api/v1/health`;健康不代表所有业务流程已验证。 +- 设置内的优化/重建仅处理派生索引;清理临时文件不删除用户文件、活动上传或恢复备份。 +- 重置设置保留账户、资源、插件存储和本机标签页。 +- 日志位于 `logs/`,轮转保留三份,每份至多 10 MiB;可在设置中导出日志及版本摘要。该功能不补录历史日志。 +- 更新后界面异常先核对前后端是否来自同一构建,再查看浏览器网络请求和应用日志;插件与外观恢复入口见[使用指南](guide.zh-CN.md#插件与界面恢复)。 diff --git a/docs/development.md b/docs/development.md index 2aec60f..e6184b7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,16 +1,18 @@ -# 开发指南 +# Development guide -[返回项目首页](../README.zh-CN.md) · [API 与认证](api.md) · [项目约束](../AGENTS.md) +**English** · [简体中文](development.zh-CN.md) -## 工具链 +[Project home](../README.md) · [API and authentication](api.md) · [Project instructions](../AGENTS.md) -- Go 1.27.2 或更高版本,具体要求见 [go.mod](../go.mod)。 -- Node.js 24+、pnpm **12.11.2**;前端依赖以 [pnpm-lock.yaml](../web/pnpm-lock.yaml) 为准。 -- Docker 仅在验证容器构建或部署时需要,本地开发不强制使用。 +## Toolchain -前端采用 Vue 3、TypeScript、Vite、Router、Pinia、Element Plus、Axios、SCSS 与 UnoCSS;编辑器使用 CodeMirror 6。服务端使用 Go 标准库 HTTP、SQLite 与文件系统;Markdown 文件是真源,搜索索引可以重建。 +- Go 1.27.2 or later; see [go.mod](../go.mod) for the requirement. +- Node.js 24+ and pnpm **12.11.2**. Frontend dependencies follow [pnpm-lock.yaml](../web/pnpm-lock.yaml). +- Docker is needed only for container build verification or deployment, not for local development. -## 启动本地开发 +The frontend uses Vue 3, TypeScript, Vite, Router, Pinia, Element Plus, Axios, SCSS, and UnoCSS, with CodeMirror 6 for editing. The backend uses Go's standard HTTP library, SQLite, and the filesystem. Markdown files are the source of truth; the search index can be rebuilt. + +## Start local development ```sh corepack enable @@ -18,50 +20,50 @@ corepack prepare pnpm@12.11.2 --activate scripts/dev.sh ``` -脚本使用锁文件安装前端依赖,同时启动 Go 开发服务和 Vite 热更新。打开 http://localhost:5173,首次访问初始化密码;Ctrl+C 同时退出。 +The script installs frontend dependencies from the lockfile and starts the Go development service and Vite with hot updates. Open http://localhost:5173 and initialize a password on first use. Ctrl+C stops both services. ```sh -scripts/dev.sh --host # 允许局域网设备访问前端 +scripts/dev.sh --host # Allow LAN devices to access the frontend scripts/dev.sh --help ``` -`RAWMARK_DATA` 默认指向仓库的 `.dev-data/`,`API_PORT` 默认 8080,`WEB_PORT` 默认 5173。可通过环境变量覆盖,例如 `API_PORT=18080 WEB_PORT=15173 scripts/dev.sh`。不要将开发服务指向生产数据;初始化、恢复、导入和删除测试只使用合成数据。 +`RAWMARK_DATA` defaults to the repository's `.dev-data/`, `API_PORT` to 8080, and `WEB_PORT` to 5173. Override them through environment variables, for example `API_PORT=18080 WEB_PORT=15173 scripts/dev.sh`. Do not point a development service at production data. Use synthetic data for initialization, restore, import, and deletion tests. -开发模式从源码统计可选字体,日志版本显示为 `dev`,不读取旧 `web/dist`。生产模式使用嵌入的前端构建、字体清单和构建版本。 +Development mode calculates optional-font sizes from source, reports the log version as `dev`, and does not read an old `web/dist`. Production mode uses the embedded frontend build, font manifest, and build version. -## 目录职责 +## Directory responsibilities ```text -cmd/rawmark/ 服务入口 +cmd/rawmark/ Service entry point internal/ - api/ HTTP 路由、授权、协议及 OpenAPI - auth/ 密码、会话与 API 凭据 - store/ 应用 SQLite - vault/ 真源笔记、附件与文件操作 - search/ 可重建的派生索引 + api/ HTTP routes, authorization, contracts, and OpenAPI + auth/ Passwords, sessions, and API credentials + store/ Application SQLite storage + vault/ Source notes, attachments, and file operations + search/ Rebuildable derived index web/ src/ - pages/login/ 初始化与验证码登录 - pages/workspace/ 笔记、目录、编辑器与图片管理 - pages/settings/ 设置、账户与维护 - common/apis/ 全部资源 API - common/components/ 跨模块公共组件 - common/ 共享外观、Markdown、插件 SDK 与工具 - http/ Axios、认证头、统一响应与错误 - router/ 路由与守卫 - pinia/ 实例及共享认证、权限、设置状态 - types/auto/ 自动生成的导入声明 -deploy/ 容器与代理配置 -scripts/ 开发、资源生成及历史迁移脚本 + pages/login/ Initialization and captcha sign-in + pages/workspace/ Notes, tree, editor, and image management + pages/settings/ Settings, account, and maintenance + common/apis/ All resource APIs + common/components/ Components shared across modules + common/ Shared appearance, Markdown, plugin SDK, and utilities + http/ Axios, auth headers, responses, and errors + router/ Routes and guards + pinia/ Pinia instance and shared auth, permission, settings state + types/auto/ Generated import declarations +deploy/ Container and proxy configuration +scripts/ Development, resource generation, historical migrations ``` -页面私有组件、类型、状态、组合函数和资源跟随 `pages//`,确实跨模块复用的能力才进入 `common`;公共代码不得依赖页面私有实现。模块内优先相对导入,跨职责用 `@`(`web/src`)与 `@@`(`web/src/common`)。 +Page-private components, types, state, composables, and assets belong to `pages//`. Only capabilities reused across modules belong in `common`, which must not depend on page-private implementations. Prefer relative imports within modules; use `@` (`web/src`) and `@@` (`web/src/common`) across responsibilities. -全部资源接口位于 `common/apis//index.ts` 和 `type.ts`。函数直接调用 `request({ url, method, data, params })`,返回完整 `{ code, data, message }`,调用方显式读取 `.data`。原文/下载设置 `returnResponse: true` 保留 Axios 响应与响应头,204 返回 void,事件连接也归资源 API。不增加页面私有请求入口或多格式 fallback。 +All resource APIs live in `common/apis//index.ts` and `type.ts`. Functions call `request({ url, method, data, params })` directly and return the complete `{ code, data, message }` envelope; callers explicitly read `.data`. Raw-text/download requests use `returnResponse: true` to preserve Axios responses and headers; 204 returns void. Event connections also belong to the resource API. Do not add page-private request entry points or fallbacks for multiple response formats. -公共组件放在 `common/components//index.vue`。SFC 使用 Composition API 与 TypeScript,其他文件使用 kebab-case。自动声明应修改生成配置后重新构建,不手工编辑。更完整的执行约束以根 [AGENTS.md](../AGENTS.md) 为准,不另维护一套规范。 +Shared components use `common/components//index.vue`. SFCs use the Composition API and TypeScript; other files use kebab-case. Change generation configuration and rebuild automatic declarations rather than editing them by hand. The root [AGENTS.md](../AGENTS.md), in Simplified Chinese, is the authoritative source of project instructions; do not maintain a second rule set. -## 验证 +## Verification ```sh pnpm -C web install --frozen-lockfile @@ -74,34 +76,34 @@ go test ./internal/... go test -tags dev ./web ``` -前端使用 Vitest,测试随模块存放;Go 使用标准 `testing`。生产构建需先生成 `web/dist`,Go 再将其嵌入二进制。`-tags dev` 保留无需嵌入产物的开发和对应测试边界。 +Frontend tests use Vitest and live alongside their modules. Go uses standard `testing`. Generate `web/dist` before a production Go build so it can be embedded in the binary. The `-tags dev` boundary allows development and corresponding tests without embedded build output. -按改动风险补充 Go race、Docker 构建及真实浏览器验收。认证与请求变更应覆盖验证码成功/失败/过期、401 与 403、服务端注销以及旧会话响应隔离;编辑器与文件操作应核对原文保存、版本冲突、附件及移动/导入/导出。不用空数据或模拟成功掩盖失败。 +Add Go race checks, Docker builds, and real-browser acceptance checks according to risk. Authentication and request changes should cover captcha success/failure/expiry, 401 versus 403, server-side logout, and isolation of old-session responses. Editor and file changes should verify original-text preservation, version conflicts, attachments, and move/import/export behavior. Do not hide failures behind empty data or simulated success. -## 主题、字体与插件 +## Themes, fonts, and plugins -内置默认、白色与黑色主题,保留独立明暗模式和 CSS 片段。自定义主题位于数据目录 `themes//`,包含 `manifest.json` 与 `theme.css`;清单包含 `id`、`name`、`version`,ID 与目录一致,不能覆盖保留的 `default`、`white`、`black`。在外观设置刷新列表后选择。 +The built-in themes are default, white, and black; separate light/dark mode and CSS snippets are retained. Custom themes live in the data directory's `themes//` with `manifest.json` and `theme.css`. The manifest includes `id`, `name`, and `version`; the ID must match the directory and cannot override reserved IDs `default`, `white`, or `black`. Refresh the theme list in appearance settings to select one. -默认字体资源已入库,常规构建无需下载或转换。重新生成时,在独立 Python 环境安装 `fonttools[woff]`,下载 [Maple v7.9 指定字体包](https://github.com/subframe7536/maple-font/releases/download/v7.9/MapleMonoNormalNL-NF-CN.zip),执行: +Default font resources are checked in; ordinary builds require no download or conversion. To regenerate them, install `fonttools[woff]` in an isolated Python environment, download the [specified Maple v7.9 archive](https://github.com/subframe7536/maple-font/releases/download/v7.9/MapleMonoNormalNL-NF-CN.zip), and run: ```sh python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip ``` -脚本生成 WOFF2 和 `maple-font.css`,保留上游许可。代码块使用 highlight.js 的固定 Atom One Dark,不能在更新主题时顺带恢复旧高亮实现或配色。 +The script generates WOFF2 files and `maple-font.css` while retaining the upstream license. Code blocks use highlight.js with fixed Atom One Dark; theme changes must not reintroduce previous highlighters or color schemes. -插件 SDK 类型见 [common/plugin-api/index.d.ts](../web/src/common/plugin-api/index.d.ts),加载与生命周期实现位于 `common/plugins/`。插件仅在前端运行,安装者必须信任其代码;命名空间、停用及资源版本管理不等于权限沙箱。编辑器扩展故障时保留正文并停用当前页相关扩展。 +Plugin SDK types are in [common/plugin-api/index.d.ts](../web/src/common/plugin-api/index.d.ts); loading and lifecycle code lives in `common/plugins/`. Plugins run only on the frontend, and installers must trust their code. Namespaces, disabling, and resource versioning do not constitute a permission sandbox. Editor-extension failures preserve document contents and disable the relevant extensions on the current page. -## 提交改动 +## Contributing changes -先阅读 [AGENTS.md](../AGENTS.md),聚焦实际需求,保留必要契约,不机械照搬模板示例。前端目录以 [vue3-starter 固定基线](https://github.com/eezd/vue3-starter/tree/fd41d62ce3f1c8338a478e0be3e2f31b53b7ea5e) 为参考,RawMark 的编辑器、主题和专用交互按业务保留。 +Read [AGENTS.md](../AGENTS.md) first. Focus on real requirements, preserve necessary contracts, and do not mechanically copy template examples. Frontend organization references the [fixed vue3-starter baseline](https://github.com/eezd/vue3-starter/tree/fd41d62ce3f1c8338a478e0be3e2f31b53b7ea5e); RawMark's editor, themes, and specialized interactions remain where the application needs them. -提交前运行适用检查,说明实际验证与未验证范围。Commit 使用清晰英文,按功能拆分;不要提交真实笔记、账户数据、密钥或本地交接文件。 +Run applicable checks before committing and state what was and was not verified. Write clear English commit messages and split commits by function. Do not commit real notes, account data, secrets, or local handoff files. -## 多语言 +## Internationalization -使用 Vue I18n Composition API,支持 `en` 与 `zh-CN`,默认英文。公共字典位于 `web/src/common/i18n/`,模块私有字典位于 `pages//locales/`,应用 `src/i18n.ts` 装配字典;不要用中文原文作为运行时查找键,也不要从公共模块导入页面字典。模板及命令、设置搜索等长期对象在使用时翻译;日期、数字和排序读取当前语言。 +Vue I18n Composition API supports `en` and `zh-CN`, defaulting to English. Shared dictionaries live in `web/src/common/i18n/`; module-private dictionaries live in `pages//locales/`. The application assembles them in `src/i18n.ts`. Do not use Chinese source text as runtime lookup keys or import page dictionaries from shared modules. Translate templates and long-lived objects such as commands and settings search entries at use time. Dates, numbers, and sorting use the current locale. -账户语言沿用 settings store 的串行写入、服务器设置与事件同步;登录页仅更新本机启动缓存,认证后读取服务器值。CodeMirror 使用 locale Compartment 更新控件,不替换文档、选区或撤销历史。用户正文、现有路径和插件提供的文字保持原样。 +Account language reuses the settings store's serial writes, server settings, and event synchronization. The sign-in page updates only the local startup cache; authentication loads the server value. CodeMirror updates controls through a locale Compartment without replacing the document, selection, or undo history. User content, existing paths, and plugin-supplied text remain unchanged. -后端 `internal/i18n/` 保存消息 ID 与参数,错误在 HTTP 边界根据 `Accept-Language` 渲染,并保留 `errors.Is/As` 及原始诊断;日志遵循原有语言。既有索引警告只在需要时重新分析源笔记生成译文,同时更新该条目的完整元数据和版本,不改索引格式;已删除或移动的过期条目跳过,其他读取错误明确返回。新增文案同时补充中英字典与参数,运行字典完整性、设置同步、编辑器和 API 的相应测试。 +Backend `internal/i18n/` preserves message IDs and parameters. Errors render at the HTTP boundary according to `Accept-Language`, retaining `errors.Is/As` and original diagnostics; logs keep their existing language. Existing index warnings are reanalyzed from source notes only when needed for translation, refreshing the entry's complete metadata and revision without changing the index format. Stale entries for deleted or moved notes are skipped; other read errors fail explicitly. Add both languages and interpolation parameters for new messages and run the corresponding dictionary-completeness, settings-sync, editor, and API tests. diff --git a/docs/development.zh-CN.md b/docs/development.zh-CN.md new file mode 100644 index 0000000..ea397d8 --- /dev/null +++ b/docs/development.zh-CN.md @@ -0,0 +1,109 @@ +# 开发指南 + +[English](development.md) · **简体中文** + +[返回项目首页](../README.zh-CN.md) · [API 与认证](api.zh-CN.md) · [项目约束](../AGENTS.md) + +## 工具链 + +- Go 1.27.2 或更高版本,具体要求见 [go.mod](../go.mod)。 +- Node.js 24+、pnpm **12.11.2**;前端依赖以 [pnpm-lock.yaml](../web/pnpm-lock.yaml) 为准。 +- Docker 仅在验证容器构建或部署时需要,本地开发不强制使用。 + +前端采用 Vue 3、TypeScript、Vite、Router、Pinia、Element Plus、Axios、SCSS 与 UnoCSS;编辑器使用 CodeMirror 6。服务端使用 Go 标准库 HTTP、SQLite 与文件系统;Markdown 文件是真源,搜索索引可以重建。 + +## 启动本地开发 + +```sh +corepack enable +corepack prepare pnpm@12.11.2 --activate +scripts/dev.sh +``` + +脚本使用锁文件安装前端依赖,同时启动 Go 开发服务和 Vite 热更新。打开 http://localhost:5173,首次访问初始化密码;Ctrl+C 同时退出。 + +```sh +scripts/dev.sh --host # Allow LAN devices to access the frontend +scripts/dev.sh --help +``` + +`RAWMARK_DATA` 默认指向仓库的 `.dev-data/`,`API_PORT` 默认 8080,`WEB_PORT` 默认 5173。可通过环境变量覆盖,例如 `API_PORT=18080 WEB_PORT=15173 scripts/dev.sh`。不要将开发服务指向生产数据;初始化、恢复、导入和删除测试只使用合成数据。 + +开发模式从源码统计可选字体,日志版本显示为 `dev`,不读取旧 `web/dist`。生产模式使用嵌入的前端构建、字体清单和构建版本。 + +## 目录职责 + +```text +cmd/rawmark/ 服务入口 +internal/ + api/ HTTP 路由、授权、协议及 OpenAPI + auth/ 密码、会话与 API 凭据 + store/ 应用 SQLite + vault/ 真源笔记、附件与文件操作 + search/ 可重建的派生索引 +web/ + src/ + pages/login/ 初始化与验证码登录 + pages/workspace/ 笔记、目录、编辑器与图片管理 + pages/settings/ 设置、账户与维护 + common/apis/ 全部资源 API + common/components/ 跨模块公共组件 + common/ 共享外观、Markdown、插件 SDK 与工具 + http/ Axios、认证头、统一响应与错误 + router/ 路由与守卫 + pinia/ 实例及共享认证、权限、设置状态 + types/auto/ 自动生成的导入声明 +deploy/ 容器与代理配置 +scripts/ 开发、资源生成及历史迁移脚本 +``` + +页面私有组件、类型、状态、组合函数和资源跟随 `pages//`,确实跨模块复用的能力才进入 `common`;公共代码不得依赖页面私有实现。模块内优先相对导入,跨职责用 `@`(`web/src`)与 `@@`(`web/src/common`)。 + +全部资源接口位于 `common/apis//index.ts` 和 `type.ts`。函数直接调用 `request({ url, method, data, params })`,返回完整 `{ code, data, message }`,调用方显式读取 `.data`。原文/下载设置 `returnResponse: true` 保留 Axios 响应与响应头,204 返回 void,事件连接也归资源 API。不增加页面私有请求入口或多格式 fallback。 + +公共组件放在 `common/components//index.vue`。SFC 使用 Composition API 与 TypeScript,其他文件使用 kebab-case。自动声明应修改生成配置后重新构建,不手工编辑。更完整的执行约束以根 [AGENTS.md](../AGENTS.md) 为准,不另维护一套规范。 + +## 验证 + +```sh +pnpm -C web install --frozen-lockfile +pnpm -C web lint:check +pnpm -C web typecheck +pnpm -C web test +pnpm -C web build +go vet -tags dev ./... +go test ./internal/... +go test -tags dev ./web +``` + +前端使用 Vitest,测试随模块存放;Go 使用标准 `testing`。生产构建需先生成 `web/dist`,Go 再将其嵌入二进制。`-tags dev` 保留无需嵌入产物的开发和对应测试边界。 + +按改动风险补充 Go race、Docker 构建及真实浏览器验收。认证与请求变更应覆盖验证码成功/失败/过期、401 与 403、服务端注销以及旧会话响应隔离;编辑器与文件操作应核对原文保存、版本冲突、附件及移动/导入/导出。不用空数据或模拟成功掩盖失败。 + +## 主题、字体与插件 + +内置默认、白色与黑色主题,保留独立明暗模式和 CSS 片段。自定义主题位于数据目录 `themes//`,包含 `manifest.json` 与 `theme.css`;清单包含 `id`、`name`、`version`,ID 与目录一致,不能覆盖保留的 `default`、`white`、`black`。在外观设置刷新列表后选择。 + +默认字体资源已入库,常规构建无需下载或转换。重新生成时,在独立 Python 环境安装 `fonttools[woff]`,下载 [Maple v7.9 指定字体包](https://github.com/subframe7536/maple-font/releases/download/v7.9/MapleMonoNormalNL-NF-CN.zip),执行: + +```sh +python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip +``` + +脚本生成 WOFF2 和 `maple-font.css`,保留上游许可。代码块使用 highlight.js 的固定 Atom One Dark,不能在更新主题时顺带恢复旧高亮实现或配色。 + +插件 SDK 类型见 [common/plugin-api/index.d.ts](../web/src/common/plugin-api/index.d.ts),加载与生命周期实现位于 `common/plugins/`。插件仅在前端运行,安装者必须信任其代码;命名空间、停用及资源版本管理不等于权限沙箱。编辑器扩展故障时保留正文并停用当前页相关扩展。 + +## 提交改动 + +先阅读 [AGENTS.md](../AGENTS.md),聚焦实际需求,保留必要契约,不机械照搬模板示例。前端目录以 [vue3-starter 固定基线](https://github.com/eezd/vue3-starter/tree/fd41d62ce3f1c8338a478e0be3e2f31b53b7ea5e) 为参考,RawMark 的编辑器、主题和专用交互按业务保留。 + +提交前运行适用检查,说明实际验证与未验证范围。Commit 使用清晰英文,按功能拆分;不要提交真实笔记、账户数据、密钥或本地交接文件。 + +## 多语言 + +使用 Vue I18n Composition API,支持 `en` 与 `zh-CN`,默认英文。公共字典位于 `web/src/common/i18n/`,模块私有字典位于 `pages//locales/`,应用 `src/i18n.ts` 装配字典;不要用中文原文作为运行时查找键,也不要从公共模块导入页面字典。模板及命令、设置搜索等长期对象在使用时翻译;日期、数字和排序读取当前语言。 + +账户语言沿用 settings store 的串行写入、服务器设置与事件同步;登录页仅更新本机启动缓存,认证后读取服务器值。CodeMirror 使用 locale Compartment 更新控件,不替换文档、选区或撤销历史。用户正文、现有路径和插件提供的文字保持原样。 + +后端 `internal/i18n/` 保存消息 ID 与参数,错误在 HTTP 边界根据 `Accept-Language` 渲染,并保留 `errors.Is/As` 及原始诊断;日志遵循原有语言。既有索引警告只在需要时重新分析源笔记生成译文,同时更新该条目的完整元数据和版本,不改索引格式;已删除或移动的过期条目跳过,其他读取错误明确返回。新增文案同时补充中英字典与参数,运行字典完整性、设置同步、编辑器和 API 的相应测试。 diff --git a/docs/guide.md b/docs/guide.md index edbcd63..2af2bcb 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1,71 +1,73 @@ -# 使用指南 +# User guide -[返回项目首页](../README.zh-CN.md) · [部署与维护](deployment.md) · [API 与认证](api.md) +**English** · [简体中文](guide.zh-CN.md) -RawMark 是单用户笔记工作台,笔记保存在服务器上。多个浏览器可以访问同一个实例;它不是多人协作系统,目前没有公开分享功能。 +[Project home](../README.md) · [Deployment and maintenance](deployment.md) · [API and authentication](api.md) -## 开始写作 +RawMark is a single-user workspace with notes stored on your server. Multiple browsers can access the same instance, but it is not a collaboration system and currently has no public sharing feature. -首次访问设置密码,之后通过密码和图片验证码登录。在侧栏「文件目录」标题或空白处右键,可以搜索、新建笔记、新建目录、导入文档、打开标签索引或管理图片;点击标题同样可以打开菜单,便于触屏使用。 +## Start writing -编辑器默认使用即时预览,点击右上角图标切换源码模式、打开大纲或进入专注写作。支持标准 Markdown、表格、任务列表、代码块、数学公式、Mermaid、脚注和 YAML 元数据。 +Set a password on first use. Subsequent sign-ins require your password and an image captcha. Right-click the **Files** heading or empty space in the sidebar to search, create notes or folders, import documents, open the tag index, or manage images. You can also click the heading to open the menu on a touchscreen. -正文修改自动保存,保存失败或版本冲突会明确提示。外部程序修改笔记时会同步到页面;出现冲突时请核对内容,不要直接覆盖仍需保留的版本。 +The editor opens in live preview mode. Use the icons at the top right to switch to source mode, open the outline, or enter focus mode. Supported syntax includes standard Markdown, tables, task lists, code blocks, math, Mermaid, footnotes, and YAML metadata. -选中文字后可使用外观、加粗、斜体、下划线、删除线、高亮与链接。局部外观和下划线以受限的标准 HTML 标签存入 Markdown,源码和导出会保留;其他阅读器是否呈现这些样式取决于其 HTML 支持。 +Changes save automatically. Save failures and version conflicts are reported explicitly. Changes made by external programs sync to the page; if a conflict occurs, compare the contents before overwriting a version you still need. -## 整理笔记 +Select text to apply appearance styles, bold, italic, underline, strikethrough, highlight, or links. Local appearance styles and underlines are stored as restricted standard HTML tags in Markdown and preserved in source mode and exports. Rendering in other readers depends on their HTML support. -- **文件与文件夹**:右键重命名、移动、删除或导出。拖到文件夹行会移入该文件夹,拖到笔记行会移入该笔记所在目录。 -- **多选**:Ctrl/Cmd 点击切换选择,Shift 点击选择可见范围,再通过右键菜单批量操作。部分操作失败会逐项报告。 -- **标签页**:支持固定和关闭其他标签;固定标签不会被「关闭其他标签页」关闭。打开的标签及固定状态在当前浏览器保存,刷新或重启后恢复。 -- **图标**:文件夹默认 📂、文件默认 📄,点击图标或右键「修改图标」选择表情或内置图标。 -- **标签和搜索**:正文 `#标签`、YAML `tags` 可用于归类;全文搜索用于查找笔记。文章标题生成右侧大纲。 +## Organize notes -应用内重命名和移动会维护笔记中的本地链接与图片引用。直接在文件系统中操作时,不保证链接、图标等元数据跟随;这些操作优先在 RawMark 内完成。 +- **Files and folders:** right-click to rename, move, delete, or export. Dropping onto a folder row moves the item into that folder; dropping onto a note row moves it into that note's parent folder. +- **Multiple selection:** Ctrl/Cmd-click toggles selection; Shift-click selects a visible range. Use the context menu for bulk actions. Partial failures are reported individually. +- **Tabs:** pin notes or close other tabs. Pinned tabs survive “Close other tabs.” Open tabs and their pinned state are stored in the current browser and restored after refreshes and restarts. +- **Icons:** folders default to 📂 and files to 📄. Click an icon or choose the change-icon context action to select an emoji or a built-in icon. +- **Tags and search:** use inline `#tags` or YAML `tags` to categorize notes, and full-text search to find them. Headings populate the document outline on the right. -## 导入与导出 +Renaming and moving items within RawMark updates local links and image references. Direct filesystem operations do not guarantee that links, icons, or other metadata follow; prefer performing these operations in RawMark. -在目录或文件夹菜单选择「导入」,支持多个 UTF-8 `.md` 文件或单个 ZIP,总上传上限为 100 MiB。 +## Import and export -ZIP 会导入到同名新文件夹,保留笔记层级。本地图片归入各笔记同级的 `assets/`,并改写引用;同名不同内容不会覆盖。缺失的本地链接会提示,远程图片不会自动下载。可以用思源笔记等工具导出的 Markdown ZIP 迁入,具体排版仍取决于其 Markdown 内容。 +Choose the import action from the directory or folder menu. Import supports multiple UTF-8 `.md` files or one ZIP archive, with a total upload limit of 100 MiB. -导出时,单篇无附件笔记直接下载 `.md`;其他情况下载保留层级、原文与本地附件的 ZIP,不下载远程资源。选中项存在缺失附件时会报错,便于先修复再导出。 +A ZIP imports into a new folder named after the archive, preserving the note hierarchy. Local images move into an `assets/` folder alongside each note, and references are rewritten. Files with the same name but different contents are not overwritten. Missing local links are reported; remote images are not downloaded automatically. Markdown ZIP exports from tools such as SiYuan can be imported, with formatting determined by their Markdown contents. -整个笔记库的普通 Markdown 导出在「设置 → 存储空间」,缺失附件记录于导出包的 `.rawmark-export/missing-assets.json`。这个报告不会在重新导入时成为笔记。 +Exporting a single note without attachments downloads an `.md` file. Other selections download a ZIP preserving the hierarchy, original text, and local attachments; remote resources are not downloaded. Missing attachments in a selection cause an error so you can repair them before exporting. -## 图片管理 +A plain Markdown export of the entire vault is available under **Settings → Storage**. Missing attachments are recorded in `.rawmark-export/missing-assets.json` inside the archive. This report does not become a note when reimported. -粘贴、上传和导入的图片默认保存在笔记同级的 `assets/`,同目录相同内容去重,正文使用相对路径。侧栏隐藏附件目录,右键文件夹选择「图片管理」即可查看,也可切换整个知识库。 +## Manage images -图片管理提供检索、排序、网格/列表、批量上传、预览、引用查看、重命名、移动和替换。重命名或移动会更新笔记引用;替换保留路径,要求相同扩展名、最大 50 MiB,旧图片不会进入回收站。 +Pasted, uploaded, and imported images default to the note's sibling `assets/` folder. Identical contents in the same folder are deduplicated, and notes use relative paths. Attachment folders are hidden from the sidebar. Right-click a folder to open its image manager; you can also switch to the entire vault. -笔记跨目录移动时图片跟随,仍被其他笔记引用的原图保留。**删除单篇笔记不会顺便删除共享图片**。清理孤立图片会先展示候选,再核对全库引用,将符合条件的图片移入回收站。旧版 `笔记名.assets/` 附件目录仍可读取、移动与恢复。 +The image manager provides search, sorting, grid/list views, bulk uploads, previews, reference lookup, renaming, moving, and replacement. Renaming or moving an image updates note references. Replacement keeps the path, requires the same extension, and has a 50 MiB limit; the old image is not sent to the recycle bin. -## 外观、字体与快捷键 +Images follow notes moved between folders. Originals still referenced by other notes are retained. **Deleting a single note does not also delete shared images.** Unreferenced-image cleanup shows candidates first, rechecks references across the vault, then sends eligible images to the recycle bin. Legacy `note-name.assets/` folders can still be read, moved, and restored. -在「设置 → 外观」切换明暗模式、主题,调整界面字体和字号、玻璃透明度或 CSS 片段;正文与代码字体、正文宽度、行高等位于编辑器设置。设置保存在服务器,多个设备共享;标签页等本机状态单独保存。 +## Appearance, fonts, and shortcuts -默认字体为 Maple Mono Normal NL NF CN v7.9,不可删除。界面、正文和代码可分别选择字体,各字体选择器支持导入 TTF、OTF、WOFF、WOFF2(单个不超过 30 MiB)及删除导入字体。页面等待所需字体加载后显示,失败会提示刷新。代码块固定使用 highlight.js 的 Atom One Dark,与页面明暗模式独立。 +Use **Settings → Appearance** to change light/dark mode, themes, interface font and size, glass opacity, or CSS snippets. Text and code fonts, text width, and line height are in the editor settings. Settings are stored on the server and shared across devices; local state such as tabs is stored separately. -快捷键默认参考 Typora 的 Windows/Linux 或 macOS 组合,无对应项及插件命令默认留空。可以搜索、添加、逐条删除、单项或全部重置,冲突绑定会被拒绝。Ctrl/Cmd+1~6 设置对应标题,再次触发同级标题恢复正文;浏览器保留键可自行改绑。 +The default font is Maple Mono Normal NL NF CN v7.9 and cannot be deleted. Interface, text, and code fonts can be selected independently. Each font selector supports importing TTF, OTF, WOFF, and WOFF2 files (up to 30 MiB each) and deleting imported fonts. The page waits for required fonts to load before appearing and offers a refresh action if loading fails. Code blocks always use highlight.js with Atom One Dark, independently of the page's light/dark mode. -手机设置采用分类和内容两级布局。应用安装入口位于「设置 → 关于」;PWA 缓存静态外壳,**不代表支持离线编辑与离线同步**。 +Default shortcuts follow Typora's Windows/Linux or macOS bindings. Actions without a corresponding binding and plugin commands default to unassigned. Search, add, remove individual bindings, or reset one action or all shortcuts; conflicting bindings are rejected. Ctrl/Cmd+1–6 sets the corresponding heading level; repeating the same level returns to body text. Browser-reserved shortcuts can be rebound. -## 回收站与存储空间 +On phones, settings use separate category and content views. App installation is available under **Settings → About**. The PWA caches the static shell; **this does not provide offline editing or offline synchronization**. -删除的内容进入回收站,支持恢复和清空全部。默认保留 30 天,设置为 0 则不自动删除;修改期限同样影响已有条目,清理在启动时及每小时执行。 +## Recycle bin and storage -「存储空间」自动展示数据目录、外置笔记库和可选字体占用;字体统计不含默认 Maple,附件与回收站计入笔记库。这里还可导出/导入完整实例、笔记和设置,优化/重建索引、清理临时文件、重置设置及导出日志。 +Deleted content goes to the recycle bin, which supports restoring items and emptying all contents. Retention defaults to 30 days; set it to 0 to disable automatic deletion. Changing the retention period also affects existing entries. Cleanup runs at startup and hourly. -完整实例恢复会替换当前业务数据并要求重新登录;替换前请先备份,具体范围、限制与恢复注意事项见[部署与维护](deployment.md#备份与恢复)。 +**Storage** automatically displays usage for the data directory, external vault, and optional fonts. Font totals exclude the default Maple font; attachments and the recycle bin count toward the vault. You can also export/import the full instance, notes, and settings; optimize/rebuild the index; clean temporary files; reset settings; and export logs. -## 插件与界面恢复 +A full-instance restore replaces current application data and requires signing in again. Back up first. See [Deployment and maintenance](deployment.md#backup-and-restore) for scope, limits, and restore precautions. -插件由实例管理者安装,属于有完整前端权限的扩展,请只安装可信来源;不提供沙箱隔离保证。开发入口见[开发指南](development.md#主题字体与插件)。 +## Plugins and interface recovery -插件导致界面异常时,可在地址后加 `?disable-plugins` 暂停插件并进入管理。主题或 CSS 片段导致界面不可用时,可加 `?reset-appearance` 临时停用自定义外观,再进入设置恢复默认。这些入口不自动删除笔记或丢弃编辑内容。 +Plugins are installed by the instance owner and have full frontend privileges. Only install trusted code; there is no sandbox isolation guarantee. See the [development guide](development.md#themes-fonts-and-plugins) for development details. -## 界面语言 +If a plugin breaks the interface, append `?disable-plugins` to the URL to pause plugins and access management. If a theme or CSS snippet makes the interface unusable, append `?reset-appearance` to temporarily disable custom appearance, then restore defaults in settings. These entry points do not automatically delete notes or discard edited content. -默认使用英文。在 **Settings → Appearance → Language** 选择 **简体中文**;中文界面对应「设置 → 外观 → 语言」。更改立即生效并保存在账户设置中,所有设备共用,不改写笔记内容或已有文件名。登录页也可以临时切换语言,登录后以账户偏好为准。 +## Interface language + +English is the default. Choose **简体中文** under **Settings → Appearance → Language**; in Chinese, the path is **设置 → 外观 → 语言**. Changes take effect immediately and are saved in account settings across devices without rewriting notes or existing filenames. The sign-in page also offers a temporary language choice; account preferences take precedence after sign-in. diff --git a/docs/guide.zh-CN.md b/docs/guide.zh-CN.md new file mode 100644 index 0000000..53c90d4 --- /dev/null +++ b/docs/guide.zh-CN.md @@ -0,0 +1,73 @@ +# 使用指南 + +[English](guide.md) · **简体中文** + +[返回项目首页](../README.zh-CN.md) · [部署与维护](deployment.zh-CN.md) · [API 与认证](api.zh-CN.md) + +RawMark 是单用户笔记工作台,笔记保存在服务器上。多个浏览器可以访问同一个实例;它不是多人协作系统,目前没有公开分享功能。 + +## 开始写作 + +首次访问设置密码,之后通过密码和图片验证码登录。在侧栏「文件目录」标题或空白处右键,可以搜索、新建笔记、新建目录、导入文档、打开标签索引或管理图片;点击标题同样可以打开菜单,便于触屏使用。 + +编辑器默认使用即时预览,点击右上角图标切换源码模式、打开大纲或进入专注写作。支持标准 Markdown、表格、任务列表、代码块、数学公式、Mermaid、脚注和 YAML 元数据。 + +正文修改自动保存,保存失败或版本冲突会明确提示。外部程序修改笔记时会同步到页面;出现冲突时请核对内容,不要直接覆盖仍需保留的版本。 + +选中文字后可使用外观、加粗、斜体、下划线、删除线、高亮与链接。局部外观和下划线以受限的标准 HTML 标签存入 Markdown,源码和导出会保留;其他阅读器是否呈现这些样式取决于其 HTML 支持。 + +## 整理笔记 + +- **文件与文件夹**:右键重命名、移动、删除或导出。拖到文件夹行会移入该文件夹,拖到笔记行会移入该笔记所在目录。 +- **多选**:Ctrl/Cmd 点击切换选择,Shift 点击选择可见范围,再通过右键菜单批量操作。部分操作失败会逐项报告。 +- **标签页**:支持固定和关闭其他标签;固定标签不会被「关闭其他标签页」关闭。打开的标签及固定状态在当前浏览器保存,刷新或重启后恢复。 +- **图标**:文件夹默认 📂、文件默认 📄,点击图标或右键「修改图标」选择表情或内置图标。 +- **标签和搜索**:正文 `#标签`、YAML `tags` 可用于归类;全文搜索用于查找笔记。文章标题生成右侧大纲。 + +应用内重命名和移动会维护笔记中的本地链接与图片引用。直接在文件系统中操作时,不保证链接、图标等元数据跟随;这些操作优先在 RawMark 内完成。 + +## 导入与导出 + +在目录或文件夹菜单选择「导入」,支持多个 UTF-8 `.md` 文件或单个 ZIP,总上传上限为 100 MiB。 + +ZIP 会导入到同名新文件夹,保留笔记层级。本地图片归入各笔记同级的 `assets/`,并改写引用;同名不同内容不会覆盖。缺失的本地链接会提示,远程图片不会自动下载。可以用思源笔记等工具导出的 Markdown ZIP 迁入,具体排版仍取决于其 Markdown 内容。 + +导出时,单篇无附件笔记直接下载 `.md`;其他情况下载保留层级、原文与本地附件的 ZIP,不下载远程资源。选中项存在缺失附件时会报错,便于先修复再导出。 + +整个笔记库的普通 Markdown 导出在「设置 → 存储空间」,缺失附件记录于导出包的 `.rawmark-export/missing-assets.json`。这个报告不会在重新导入时成为笔记。 + +## 图片管理 + +粘贴、上传和导入的图片默认保存在笔记同级的 `assets/`,同目录相同内容去重,正文使用相对路径。侧栏隐藏附件目录,右键文件夹选择「图片管理」即可查看,也可切换整个知识库。 + +图片管理提供检索、排序、网格/列表、批量上传、预览、引用查看、重命名、移动和替换。重命名或移动会更新笔记引用;替换保留路径,要求相同扩展名、最大 50 MiB,旧图片不会进入回收站。 + +笔记跨目录移动时图片跟随,仍被其他笔记引用的原图保留。**删除单篇笔记不会顺便删除共享图片**。清理孤立图片会先展示候选,再核对全库引用,将符合条件的图片移入回收站。旧版 `笔记名.assets/` 附件目录仍可读取、移动与恢复。 + +## 外观、字体与快捷键 + +在「设置 → 外观」切换明暗模式、主题,调整界面字体和字号、玻璃透明度或 CSS 片段;正文与代码字体、正文宽度、行高等位于编辑器设置。设置保存在服务器,多个设备共享;标签页等本机状态单独保存。 + +默认字体为 Maple Mono Normal NL NF CN v7.9,不可删除。界面、正文和代码可分别选择字体,各字体选择器支持导入 TTF、OTF、WOFF、WOFF2(单个不超过 30 MiB)及删除导入字体。页面等待所需字体加载后显示,失败会提示刷新。代码块固定使用 highlight.js 的 Atom One Dark,与页面明暗模式独立。 + +快捷键默认参考 Typora 的 Windows/Linux 或 macOS 组合,无对应项及插件命令默认留空。可以搜索、添加、逐条删除、单项或全部重置,冲突绑定会被拒绝。Ctrl/Cmd+1~6 设置对应标题,再次触发同级标题恢复正文;浏览器保留键可自行改绑。 + +手机设置采用分类和内容两级布局。应用安装入口位于「设置 → 关于」;PWA 缓存静态外壳,**不代表支持离线编辑与离线同步**。 + +## 回收站与存储空间 + +删除的内容进入回收站,支持恢复和清空全部。默认保留 30 天,设置为 0 则不自动删除;修改期限同样影响已有条目,清理在启动时及每小时执行。 + +「存储空间」自动展示数据目录、外置笔记库和可选字体占用;字体统计不含默认 Maple,附件与回收站计入笔记库。这里还可导出/导入完整实例、笔记和设置,优化/重建索引、清理临时文件、重置设置及导出日志。 + +完整实例恢复会替换当前业务数据并要求重新登录;替换前请先备份,具体范围、限制与恢复注意事项见[部署与维护](deployment.zh-CN.md#备份与恢复)。 + +## 插件与界面恢复 + +插件由实例管理者安装,属于有完整前端权限的扩展,请只安装可信来源;不提供沙箱隔离保证。开发入口见[开发指南](development.zh-CN.md#主题字体与插件)。 + +插件导致界面异常时,可在地址后加 `?disable-plugins` 暂停插件并进入管理。主题或 CSS 片段导致界面不可用时,可加 `?reset-appearance` 临时停用自定义外观,再进入设置恢复默认。这些入口不自动删除笔记或丢弃编辑内容。 + +## 界面语言 + +默认使用英文。在 **Settings → Appearance → Language** 选择 **简体中文**;中文界面对应「设置 → 外观 → 语言」。更改立即生效并保存在账户设置中,所有设备共用,不改写笔记内容或已有文件名。登录页也可以临时切换语言,登录后以账户偏好为准。 diff --git a/internal/api/openapi.json b/internal/api/openapi.json index 68dba55..4002932 100644 --- a/internal/api/openapi.json +++ b/internal/api/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "RawMark API", "version": "1", - "description": "RawMark单用户API。普通JSON统一{code,data,message},code=0成功;保留HTTP语义。登录会话固定30天;API令牌保留read/write权限。受保护API只接受Bearer,不接受Cookie。文件、下载、事件、204与OpenAPI文档不包装。 响应提示支持Accept-Language: en或zh-CN(按q权重选择,缺省英文),Content-Language返回实际语言;状态码与业务码不变。/settings的locale为账户语言,支持en、zh-CN,null删除覆盖。" + "description": "RawMark single-user API. Ordinary JSON uses {code,data,message}, with code=0 for success; HTTP semantics are preserved. Login sessions last a fixed 30 days; API tokens retain read/write permissions. Protected APIs accept Bearer authentication only, not Cookies. Files, downloads, events, 204 responses, and OpenAPI are not wrapped. Messages support Accept-Language: en or zh-CN (selected by q weight; default English), and Content-Language reports the selected language; HTTP statuses and business codes do not change. The /settings locale field stores account language (en or zh-CN); null removes the override." }, "servers": [ { @@ -18,16 +18,16 @@ "paths": { "/health": { "get": { - "summary": "健康检查", + "summary": "Health check", "tags": [ - "系统" + "System" ], "security": [ {} ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -57,16 +57,16 @@ }, "/openapi.json": { "get": { - "summary": "本文档", + "summary": "This document", "tags": [ - "系统" + "System" ], "security": [ {} ], "responses": { "200": { - "description": "OpenAPI 文档", + "description": "OpenAPI document", "content": { "application/json": { "schema": { @@ -80,16 +80,16 @@ }, "/auth/state": { "get": { - "summary": "是否已经初始化", + "summary": "Query initialization status", "tags": [ - "登录" + "Authentication" ], "security": [ {} ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -127,9 +127,9 @@ }, "/auth/setup": { "post": { - "summary": "首次设置密码并返回Bearer会话", + "summary": "Set the initial password and return a Bearer session", "tags": [ - "登录" + "Authentication" ], "security": [ {} @@ -154,7 +154,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -193,7 +193,7 @@ } }, "400": { - "description": "密码过短", + "description": "Password too short", "content": { "application/json": { "schema": { @@ -203,7 +203,7 @@ } }, "409": { - "description": "已初始化", + "description": "Already initialized", "content": { "application/json": { "schema": { @@ -213,14 +213,14 @@ } } }, - "description": "仅未初始化实例可设置密码;首次成功后自动登录,已初始化时返回 409。" + "description": "Only uninitialized instances may set a password. The first successful setup signs in automatically; initialized instances return 409." } }, "/auth/login": { "post": { - "summary": "验证码及密码登录,返回Bearer会话", + "summary": "Sign in with captcha and password; return a Bearer session", "tags": [ - "登录" + "Authentication" ], "security": [ {} @@ -253,7 +253,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -292,7 +292,7 @@ } }, "401": { - "description": "密码错误", + "description": "Incorrect password", "content": { "application/json": { "schema": { @@ -302,7 +302,7 @@ } }, "429": { - "description": "失败次数过多", + "description": "Too many failed attempts", "content": { "application/json": { "schema": { @@ -312,7 +312,7 @@ } }, "400": { - "description": "验证码错误、已使用或过期(40001/40002)", + "description": "Incorrect, used, or expired captcha (40001/40002)", "content": { "application/json": { "schema": { @@ -326,9 +326,9 @@ }, "/auth/logout": { "post": { - "summary": "退出登录", + "summary": "Sign out", "tags": [ - "登录" + "Authentication" ], "security": [ { @@ -337,16 +337,16 @@ ], "responses": { "204": { - "description": "服务端会话已撤销" + "description": "Server session revoked" } } } }, "/auth/password": { "post": { - "summary": "修改登录密码;当前会话保留,其他设备需重新登录", + "summary": "Change the login password; retain the current session and require other devices to sign in again", "tags": [ - "登录" + "Authentication" ], "security": [ { @@ -377,7 +377,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -403,7 +403,7 @@ } }, "400": { - "description": "新密码过短", + "description": "New password too short", "content": { "application/json": { "schema": { @@ -413,7 +413,7 @@ } }, "401": { - "description": "当前密码错误", + "description": "Incorrect current password", "content": { "application/json": { "schema": { @@ -423,7 +423,7 @@ } }, "429": { - "description": "失败次数过多", + "description": "Too many failed attempts", "content": { "application/json": { "schema": { @@ -437,13 +437,13 @@ }, "/settings": { "get": { - "summary": "应用设置(全部设备共用的一份 JSON 对象)", + "summary": "Application settings (one JSON object shared across devices)", "tags": [ - "设置" + "Settings" ], "responses": { "200": { - "description": "设置", + "description": "Settings", "content": { "application/json": { "schema": { @@ -472,16 +472,16 @@ } }, "patch": { - "summary": "合并修改设置", + "summary": "Merge settings changes", "tags": [ - "设置" + "Settings" ], "security": [ { "bearer": [] } ], - "description": "按顶层键合并:请求中的键覆盖原值,值为 null 的键被删除。成功后经 WebSocket 推送 settings 事件。 trashRetentionDays 为非负安全整数(默认30,0为永不自动删除),null恢复默认。期限适用于已有和新回收站条目,服务启动及每小时按原删除时间永久清理。", + "description": "Merge top-level keys: supplied values replace existing values, and null removes a key. On success, broadcast a settings event over WebSocket. trashRetentionDays is a nonnegative safe integer (default 30; 0 disables automatic deletion); null restores the default. Retention applies to existing and new recycle-bin entries. Startup and hourly cleanup permanently delete expired entries based on their original deletion times.", "requestBody": { "required": true, "content": { @@ -506,7 +506,7 @@ }, "responses": { "200": { - "description": "合并后的设置", + "description": "Merged settings", "content": { "application/json": { "schema": { @@ -533,7 +533,7 @@ } }, "400": { - "description": "不是 JSON 对象,或自动删除期限无效", + "description": "Not a JSON object, or invalid retention period", "content": { "application/json": { "schema": { @@ -543,7 +543,7 @@ } }, "413": { - "description": "超过 1 MB", + "description": "Exceeds 1 MB", "content": { "application/json": { "schema": { @@ -557,13 +557,13 @@ }, "/tree": { "get": { - "summary": "全部笔记、图片与文件夹(含 .assets,不含隐藏条目或符号链接)", + "summary": "All notes, images, and folders (including .assets, excluding hidden entries and symlinks)", "tags": [ - "笔记" + "Notes" ], "responses": { "200": { - "description": "目录树", + "description": "Directory tree", "content": { "application/json": { "schema": { @@ -605,23 +605,23 @@ "name": "path", "in": "path", "required": true, - "description": "仓库内的相对路径,如 `目录/笔记.md`(各段需 URL 编码)", + "description": "Vault-relative path, such as `folder/note.md` (URL-encode each segment)", "schema": { "type": "string" } } ], "get": { - "summary": "读取笔记", + "summary": "Read a note", "tags": [ - "笔记" + "Notes" ], "responses": { "200": { - "description": "Markdown 原文", + "description": "Original Markdown", "headers": { "ETag": { - "description": "内容版本(rev),带双引号", + "description": "Content revision (rev), enclosed in double quotes", "schema": { "type": "string" } @@ -636,7 +636,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -648,11 +648,11 @@ } }, "put": { - "summary": "写入笔记(不存在的上级目录会自动创建)", + "summary": "Write a note (missing parent folders are created automatically)", "tags": [ - "笔记" + "Notes" ], - "description": "请求体整体作为笔记内容。`If-Match: \"rev\"` 要求当前版本一致,否则返回 412 与最新 rev;`If-None-Match: *` 要求笔记尚不存在;都不带则直接覆盖。", + "description": "Use the entire request body as note content. `If-Match: \"rev\"` requires the current revision; otherwise return 412 with the latest rev. `If-None-Match: *` requires a new note. Omitting both overwrites unconditionally.", "parameters": [ { "name": "If-Match", @@ -684,7 +684,7 @@ }, "responses": { "200": { - "description": "已写入", + "description": "Written", "content": { "application/json": { "schema": { @@ -715,7 +715,7 @@ }, "headers": { "ETag": { - "description": "内容版本(rev),带双引号", + "description": "Content revision (rev), enclosed in double quotes", "schema": { "type": "string" } @@ -723,7 +723,7 @@ } }, "400": { - "description": "路径无效", + "description": "Invalid path", "content": { "application/json": { "schema": { @@ -733,7 +733,7 @@ } }, "409": { - "description": "If-None-Match 时已存在", + "description": "Already exists with If-None-Match", "content": { "application/json": { "schema": { @@ -743,7 +743,7 @@ } }, "412": { - "description": "版本冲突", + "description": "Revision conflict", "content": { "application/json": { "schema": { @@ -766,7 +766,7 @@ }, "rev": { "type": "string", - "description": "服务器上的当前版本;为空表示已不存在" + "description": "Current server revision; empty if the file no longer exists" } } }, @@ -779,7 +779,7 @@ } }, "413": { - "description": "超过 20 MB", + "description": "Exceeds 20 MB", "content": { "application/json": { "schema": { @@ -791,13 +791,13 @@ } }, "delete": { - "summary": "把笔记、图片或文件夹移入回收站(笔记连同 .assets)", + "summary": "Move a note, image, or folder to the recycle bin (notes include their .assets directory)", "tags": [ - "笔记" + "Notes" ], "responses": { "200": { - "description": "回收站条目", + "description": "Recycle-bin entry", "content": { "application/json": { "schema": { @@ -823,7 +823,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -837,11 +837,11 @@ }, "/move": { "post": { - "summary": "移动或重命名笔记、图片或文件夹", + "summary": "Move or rename a note, image, or folder", "tags": [ - "笔记" + "Notes" ], - "description": "笔记的 .assets 目录随之移动,改名时改写本篇图片目录链接;图片移动或改名保持扩展名,并更新仓库笔记中的本地引用。维护笔记中的本地入链及相对出链。", + "description": "A note's .assets directory follows its move; renaming rewrites links to that image directory. Image moves/renames preserve the extension and update local references in vault notes. Maintain local inbound links and relative outbound links.", "requestBody": { "required": true, "content": { @@ -866,7 +866,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -892,7 +892,7 @@ } }, "404": { - "description": "源不存在", + "description": "Source not found", "content": { "application/json": { "schema": { @@ -902,7 +902,7 @@ } }, "409": { - "description": "目标已存在", + "description": "Destination already exists", "content": { "application/json": { "schema": { @@ -916,9 +916,9 @@ }, "/folders": { "post": { - "summary": "新建文件夹", + "summary": "Create a folder", "tags": [ - "笔记" + "Notes" ], "requestBody": { "required": true, @@ -940,7 +940,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -966,7 +966,7 @@ } }, "409": { - "description": "已存在", + "description": "Already exists", "content": { "application/json": { "schema": { @@ -980,16 +980,16 @@ }, "/assets": { "post": { - "summary": "上传图片到笔记的 .assets 目录", + "summary": "Upload an image to the note's sibling assets directory", "tags": [ - "图片" + "Images" ], "parameters": [ { "name": "note", "in": "query", "required": false, - "description": "所属笔记路径", + "description": "Owning note path", "schema": { "type": "string" } @@ -997,7 +997,7 @@ { "name": "folder", "in": "query", - "description": "文件夹图片管理上传目标,空字符串表示根目录;与 note 互斥", + "description": "Folder upload destination for image management; an empty string means the root. Mutually exclusive with note.", "schema": { "type": "string" } @@ -1024,7 +1024,7 @@ }, "responses": { "200": { - "description": "相对笔记所在目录的链接,可直接写入 Markdown", + "description": "Link relative to the note's folder, ready for Markdown", "content": { "application/json": { "schema": { @@ -1055,7 +1055,7 @@ } }, "413": { - "description": "超过 50 MB", + "description": "Exceeds 50 MB", "content": { "application/json": { "schema": { @@ -1065,16 +1065,16 @@ } } }, - "description": "note 与 folder 必须且只能指定一个;note 上传到笔记附件目录,folder 上传到目录的 assets 子目录。" + "description": "Specify exactly one of note and folder. note uploads to the note's sibling assets directory; folder uploads to that folder's assets subdirectory." } }, "/assets/import": { "post": { - "summary": "下载公网图片到笔记的 .assets 目录", + "summary": "Download a public image to the note's sibling assets directory", "tags": [ - "图片" + "Images" ], - "description": "仅接受公网 HTTP/HTTPS 地址,实际连接及重定向均校验;不携带登录凭据或使用环境代理。重定向链最多 5 个请求、2 次下载尝试,单次超时 10 秒;仅超时、429 和 5xx 重试。支持 PNG/JPEG/GIF/WebP,按文件签名识别,最大 50 MB。", + "description": "Accept only public HTTP/HTTPS URLs; validate actual connections and redirects. Do not forward login credentials or use environment proxies. At most 5 requests per redirect chain, 2 download attempts, and a 10-second timeout per attempt; retry only timeouts, 429, and 5xx. Support PNG/JPEG/GIF/WebP, detected by file signature, up to 50 MB.", "parameters": [ { "name": "note", @@ -1106,7 +1106,7 @@ }, "responses": { "200": { - "description": "相对笔记所在目录的图片链接", + "description": "Image link relative to the note's folder", "content": { "application/json": { "schema": { @@ -1137,7 +1137,7 @@ } }, "400": { - "description": "来源地址或笔记路径无效,或目标不是公网", + "description": "Invalid source URL or note path, or nonpublic destination", "content": { "application/json": { "schema": { @@ -1147,7 +1147,7 @@ } }, "404": { - "description": "所属笔记不存在", + "description": "Owning note not found", "content": { "application/json": { "schema": { @@ -1157,7 +1157,7 @@ } }, "413": { - "description": "图片超过 50 MB", + "description": "Image exceeds 50 MB", "content": { "application/json": { "schema": { @@ -1167,7 +1167,7 @@ } }, "422": { - "description": "不支持的图片类型", + "description": "Unsupported image type", "content": { "application/json": { "schema": { @@ -1177,7 +1177,7 @@ } }, "502": { - "description": "来源请求失败", + "description": "Source request failed", "content": { "application/json": { "schema": { @@ -1195,20 +1195,20 @@ "name": "path", "in": "path", "required": true, - "description": "仓库内的相对路径,如 `目录/笔记.md`(各段需 URL 编码)", + "description": "Vault-relative path, such as `folder/note.md` (URL-encode each segment)", "schema": { "type": "string" } } ], "get": { - "summary": "读取仓库中的原始文件(图片等)", + "summary": "Read a raw vault file, such as an image", "tags": [ - "图片" + "Images" ], "responses": { "200": { - "description": "文件内容", + "description": "File contents", "content": { "application/octet-stream": { "schema": { @@ -1219,7 +1219,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -1233,11 +1233,11 @@ }, "/search": { "get": { - "summary": "全文搜索", + "summary": "Full-text search", "tags": [ - "笔记" + "Notes" ], - "description": "空格分隔多个词,全部命中才返回;最多 50 条。", + "description": "Space-separated terms must all match; return at most 50 results.", "parameters": [ { "name": "q", @@ -1250,7 +1250,7 @@ ], "responses": { "200": { - "description": "结果", + "description": "Results", "content": { "application/json": { "schema": { @@ -1288,13 +1288,13 @@ }, "/trash": { "get": { - "summary": "回收站列表", + "summary": "List the recycle bin", "tags": [ - "回收站" + "Recycle bin" ], "responses": { "200": { - "description": "条目", + "description": "Entries", "content": { "application/json": { "schema": { @@ -1330,14 +1330,14 @@ } }, "delete": { - "summary": "清空回收站全部条目", - "description": "永久删除,不可恢复;结果逐项报告,失败条目保留。只读令牌禁止。", + "summary": "Empty all recycle-bin entries", + "description": "Permanent and irreversible deletion. Report per-entry results and retain failed entries. Read-only tokens are forbidden.", "tags": [ - "回收站" + "Recycle bin" ], "responses": { "200": { - "description": "包含成功及失败的逐项结果", + "description": "Per-entry success and failure results", "content": { "application/json": { "schema": { @@ -1377,13 +1377,13 @@ } ], "post": { - "summary": "从回收站恢复", + "summary": "Restore from the recycle bin", "tags": [ - "回收站" + "Recycle bin" ], "responses": { "200": { - "description": "恢复的条目", + "description": "Restored entry", "content": { "application/json": { "schema": { @@ -1409,7 +1409,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -1419,7 +1419,7 @@ } }, "409": { - "description": "原位置已有同名文件", + "description": "A file already exists at the original path", "content": { "application/json": { "schema": { @@ -1443,13 +1443,13 @@ } ], "delete": { - "summary": "彻底删除回收站条目", + "summary": "Permanently delete a recycle-bin entry", "tags": [ - "回收站" + "Recycle bin" ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -1475,7 +1475,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -1489,23 +1489,23 @@ }, "/events": { "get": { - "summary": "WebSocket:仓库变更推送", + "summary": "WebSocket vault-change events", "tags": [ - "笔记" + "Notes" ], - "description": "升级为 WebSocket 后,服务器推送 JSON 数组,元素为 Event。客户端不发送消息。", + "description": "After upgrading to WebSocket, the server sends JSON arrays of Event objects. Clients send no messages.", "responses": { "101": { - "description": "切换为 WebSocket" + "description": "Upgrade to WebSocket" } } } }, "/tokens": { "get": { - "summary": "API 令牌列表", + "summary": "List API tokens", "tags": [ - "API 令牌" + "API tokens" ], "security": [ { @@ -1514,7 +1514,7 @@ ], "responses": { "200": { - "description": "令牌", + "description": "Tokens", "content": { "application/json": { "schema": { @@ -1550,9 +1550,9 @@ } }, "post": { - "summary": "创建 API 令牌", + "summary": "Create an API token", "tags": [ - "API 令牌" + "API tokens" ], "security": [ { @@ -1587,7 +1587,7 @@ }, "responses": { "200": { - "description": "token 明文只返回这一次", + "description": "Plaintext token is returned only once", "content": { "application/json": { "schema": { @@ -1621,7 +1621,7 @@ } }, "400": { - "description": "名称或范围无效", + "description": "Invalid name or scope", "content": { "application/json": { "schema": { @@ -1645,9 +1645,9 @@ } ], "delete": { - "summary": "吊销 API 令牌", + "summary": "Revoke an API token", "tags": [ - "API 令牌" + "API tokens" ], "security": [ { @@ -1656,7 +1656,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -1682,7 +1682,7 @@ } }, "404": { - "description": "不存在", + "description": "Not found", "content": { "application/json": { "schema": { @@ -1696,13 +1696,13 @@ }, "/themes": { "get": { - "summary": "列出主题(含内置默认主题)", + "summary": "List themes, including built-in defaults", "tags": [ - "外观" + "Appearance" ], "responses": { "200": { - "description": "主题列表", + "description": "Theme list", "content": { "application/json": { "schema": { @@ -1740,9 +1740,9 @@ }, "/themes/{id}": { "get": { - "summary": "读取主题 CSS", + "summary": "Read theme CSS", "tags": [ - "外观" + "Appearance" ], "parameters": [ { @@ -1756,7 +1756,7 @@ ], "responses": { "200": { - "description": "主题信息与 CSS", + "description": "Theme metadata and CSS", "content": { "application/json": { "schema": { @@ -1794,16 +1794,16 @@ } }, "404": { - "description": "主题不存在或文件不完整" + "description": "Theme not found or incomplete files" } } } }, "/plugins": { "get": { - "summary": "列出已安装插件及启用状态", + "summary": "List installed plugins and their enabled state", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -1812,7 +1812,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -1846,20 +1846,20 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" } } }, "post": { - "summary": "安装 ZIP 插件;安装后默认停用,同 id 拒绝覆盖", + "summary": "Install a ZIP plugin; disabled by default after installation; reject an existing id", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -1868,7 +1868,7 @@ ], "responses": { "201": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -1894,19 +1894,19 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "409": { - "description": "同名插件已安装" + "description": "A plugin with this name is already installed" }, "413": { - "description": "ZIP 超过 10 MiB" + "description": "ZIP exceeds 10 MiB" } }, "requestBody": { @@ -1932,9 +1932,9 @@ }, "/plugins/{id}": { "patch": { - "summary": "修改全设备插件启用偏好", + "summary": "Change plugin enablement preferences across devices", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -1943,7 +1943,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -1969,16 +1969,16 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "404": { - "description": "插件或指定版本的资源不存在" + "description": "Plugin or requested resource version not found" } }, "parameters": [ @@ -2011,9 +2011,9 @@ } }, "delete": { - "summary": "停用并卸载插件包,保留插件私有数据", + "summary": "Disable and uninstall a plugin package while retaining private plugin data", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -2022,7 +2022,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2048,16 +2048,16 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "404": { - "description": "插件或指定版本的资源不存在" + "description": "Plugin or requested resource version not found" } }, "parameters": [ @@ -2074,9 +2074,9 @@ }, "/plugins/{id}/data": { "get": { - "summary": "读取插件私有 JSON,无数据时为 null", + "summary": "Read private plugin JSON; null when no data exists", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -2085,7 +2085,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2117,16 +2117,16 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "404": { - "description": "插件或指定版本的资源不存在" + "description": "Plugin or requested resource version not found" } }, "parameters": [ @@ -2141,9 +2141,9 @@ ] }, "put": { - "summary": "替换插件私有 JSON,最多 1 MiB", + "summary": "Replace private plugin JSON, up to 1 MiB", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -2152,7 +2152,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2184,16 +2184,16 @@ } }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "404": { - "description": "插件或指定版本的资源不存在" + "description": "Plugin or requested resource version not found" } }, "parameters": [ @@ -2226,9 +2226,9 @@ }, "/plugins/{id}/assets/{revision}/{path}": { "get": { - "summary": "鉴权读取指定安装版本的包内资源", + "summary": "Read authenticated package resources for a specific installed version", "tags": [ - "插件" + "Plugins" ], "security": [ { @@ -2237,19 +2237,19 @@ ], "responses": { "200": { - "description": "成功" + "description": "Success" }, "401": { - "description": "需要登录会话" + "description": "Login session required" }, "400": { - "description": "请求或插件包无效" + "description": "Invalid request or plugin archive" }, "403": { - "description": "不接受 API 令牌" + "description": "API tokens are not accepted" }, "404": { - "description": "插件或指定版本的资源不存在" + "description": "Plugin or requested resource version not found" } }, "parameters": [ @@ -2282,11 +2282,11 @@ }, "/import": { "post": { - "summary": "导入 Markdown 文件或 ZIP 知识库", + "summary": "Import Markdown files or a ZIP vault", "tags": [ - "文件" + "Files" ], - "description": "多选 .md 或单个 ZIP,总上传不超过 100 MB;ZIP 必须指定新建文件夹名称。目标目录必须存在,冲突不覆盖。解压及整理后上限 512 MiB、10,000 项,单篇笔记 20 MiB、附件 50 MiB。Markdown 与 ZIP 文件名须为 UTF-8。ZIP 层级保留,本地图片生成笔记独立附件,原附件保留;外部链接不下载,缺失本地目标以警告返回。", + "description": "Accept multiple .md files or one ZIP, up to 100 MB total. ZIP imports require a new folder name. The destination directory must exist; conflicts do not overwrite. After extraction and organization: up to 512 MiB, 10,000 entries, 20 MiB per note, and 50 MiB per attachment. Markdown and ZIP filenames must be UTF-8. Preserve ZIP hierarchy; normalize local images into sibling assets directories and rewrite references. Remove migrated originals only when unreferenced; retain other attachments. Do not download external links; return warnings for missing local targets.", "requestBody": { "required": true, "content": { @@ -2299,11 +2299,11 @@ "properties": { "dir": { "type": "string", - "description": "目标目录,空字符串为根目录" + "description": "Destination folder; an empty string means the root" }, "folder": { "type": "string", - "description": "ZIP 的新建文件夹名称;普通 Markdown 留空" + "description": "New folder name for ZIP import; leave empty for plain Markdown" }, "files": { "type": "array", @@ -2319,7 +2319,7 @@ }, "responses": { "201": { - "description": "导入成功", + "description": "Import succeeded", "content": { "application/json": { "schema": { @@ -2353,7 +2353,7 @@ }, "assets": { "type": "integer", - "description": "附件文件总数,包含原始附件和独立副本" + "description": "Total retained attachment files, including shared images and other original attachments" }, "warnings": { "type": "array", @@ -2372,44 +2372,44 @@ } }, "400": { - "description": "格式、路径或编码无效" + "description": "Invalid format, path, or encoding" }, "401": { - "description": "未登录或令牌无效" + "description": "Not signed in or invalid token" }, "403": { - "description": "只读令牌不能导入" + "description": "Read-only tokens cannot import" }, "404": { - "description": "目标目录不存在" + "description": "Destination folder not found" }, "409": { - "description": "目标已存在,不覆盖" + "description": "Destination already exists; no overwrite" }, "413": { - "description": "超过上传、解压、文件数量或单文件大小限制" + "description": "Upload, expanded-size, entry-count, or per-file size limit exceeded" } } } }, "/assets/{path}": { "put": { - "summary": "原位替换已有图片,保留路径和引用", + "summary": "Replace an existing image in place, preserving its path and references", "tags": [ - "图片" + "Images" ], "parameters": [ { "name": "path", "in": "path", "required": true, - "description": "仓库中的图片路径", + "description": "Image path within the vault", "schema": { "type": "string" } } ], - "description": "multipart file 字段的扩展名须与目标图片一致;不创建新文件,最大 50 MB。旧图片不会进入回收站。", + "description": "The multipart file extension must match the target image. Do not create new files; maximum 50 MB. The old image is not sent to the recycle bin.", "requestBody": { "required": true, "content": { @@ -2431,7 +2431,7 @@ }, "responses": { "200": { - "description": "替换成功", + "description": "Replacement succeeded", "content": { "application/json": { "schema": { @@ -2457,23 +2457,23 @@ } }, "400": { - "description": "非法路径、空文件或扩展名不一致" + "description": "Invalid path, empty file, or mismatched extension" }, "404": { - "description": "目标图片不存在" + "description": "Target image not found" }, "413": { - "description": "超过 50 MB" + "description": "Exceeds 50 MB" } } } }, "/export": { "post": { - "summary": "导出原始 Markdown 和本地附件", - "description": "接收非空路径列表;单篇无附件返回 .md,目录、多选或含附件返回 ZIP。ZIP 保留仓库相对路径;不下载外部资源,不跟随其他未选中笔记。缺失附件或非法路径返回错误。此 POST 接口不修改数据,只读令牌可调用。", + "summary": "Export original Markdown and local attachments", + "description": "Accept a nonempty path list. Return .md for a single note without attachments; folders, multiple selections, or notes with attachments return ZIP. ZIP preserves vault-relative paths. Do not download external resources or follow unselected notes. Missing attachments or invalid paths return errors. This POST does not modify data and accepts read-only tokens.", "tags": [ - "文件" + "Files" ], "requestBody": { "required": true, @@ -2499,7 +2499,7 @@ }, "responses": { "200": { - "description": "原始 Markdown 或 ZIP 附件", + "description": "Original Markdown or ZIP attachment", "content": { "application/zip": { "schema": { @@ -2515,17 +2515,17 @@ } }, "400": { - "description": "路径无效" + "description": "Invalid path" }, "404": { - "description": "文件或附件不存在" + "description": "File or attachment not found" } } } }, "/metadata": { "get": { - "summary": "读取笔记关系、标签与资源引用索引", + "summary": "Read the note-relationship, tag, and resource-reference index", "security": [ { "bearer": [] @@ -2533,7 +2533,7 @@ ], "responses": { "200": { - "description": "索引就绪状态与元数据列表,不含笔记正文", + "description": "Index readiness and metadata list, excluding note contents", "content": { "application/json": { "schema": { @@ -2578,7 +2578,7 @@ }, "/assets/cleanup": { "post": { - "summary": "重新核对全库引用后,将孤立图片移入回收站", + "summary": "Recheck vault-wide references and move unreferenced images to the recycle bin", "requestBody": { "required": true, "content": { @@ -2604,7 +2604,7 @@ }, "responses": { "200": { - "description": "已移入回收站的图片及跳过原因", + "description": "Images moved to the recycle bin and reasons for skipped items", "content": { "application/json": { "schema": { @@ -2630,16 +2630,16 @@ } }, "400": { - "description": "请求无效,或笔记元数据异常,无法确认引用" + "description": "Invalid request or note metadata prevents reference verification" } } } }, "/tree-icon": { "put": { - "summary": "设置单个文件或目录的图标,null 恢复默认", + "summary": "Set a file or folder icon; null restores the default", "tags": [ - "文件" + "Files" ], "requestBody": { "required": true, @@ -2672,7 +2672,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2698,19 +2698,19 @@ } }, "400": { - "description": "非法路径或图标" + "description": "Invalid path or icon" }, "404": { - "description": "文件不存在" + "description": "File not found" } } } }, "/fonts": { "get": { - "summary": "列出已导入字体", + "summary": "List imported fonts", "tags": [ - "设置" + "Settings" ], "security": [ { @@ -2719,7 +2719,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2756,17 +2756,17 @@ } }, "401": { - "description": "未登录" + "description": "Not signed in" }, "403": { - "description": "只接受登录会话" + "description": "Login sessions only" } } }, "post": { - "summary": "导入字体,按内容去重(TTF/OTF/WOFF/WOFF2,上限30 MB)", + "summary": "Import a font with content deduplication (TTF/OTF/WOFF/WOFF2, up to 30 MB)", "tags": [ - "设置" + "Settings" ], "security": [ { @@ -2775,7 +2775,7 @@ ], "responses": { "201": { - "description": "新字体已导入", + "description": "New font imported", "content": { "application/json": { "schema": { @@ -2801,13 +2801,13 @@ } }, "401": { - "description": "未登录" + "description": "Not signed in" }, "403": { - "description": "只接受登录会话" + "description": "Login sessions only" }, "200": { - "description": "相同内容的字体已存在,返回原字体", + "description": "Identical font contents already exist; return the original font", "content": { "application/json": { "schema": { @@ -2866,9 +2866,9 @@ } ], "delete": { - "summary": "删除导入字体,将使用它的界面、正文和代码字体恢复Maple;Maple不可删除", + "summary": "Delete an imported font and reset interface, text, and code fonts using it to Maple; Maple cannot be deleted", "tags": [ - "设置" + "Settings" ], "security": [ { @@ -2877,7 +2877,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -2903,10 +2903,10 @@ } }, "401": { - "description": "未登录" + "description": "Not signed in" }, "403": { - "description": "只接受登录会话" + "description": "Login sessions only" } } } @@ -2923,9 +2923,9 @@ } ], "get": { - "summary": "读取导入字体文件", + "summary": "Read an imported font file", "tags": [ - "设置" + "Settings" ], "security": [ { @@ -2934,23 +2934,23 @@ ], "responses": { "200": { - "description": "成功" + "description": "Success" }, "401": { - "description": "未登录" + "description": "Not signed in" }, "403": { - "description": "只接受登录会话" + "description": "Login sessions only" } } } }, "/storage": { "get": { - "summary": "按需统计存储空间", - "description": "只接受登录会话。统计数据目录和实际笔记库的普通文件字节,嵌套去重,不跟随内部符号链接;附件/回收站是笔记库的子项,totalSize不重复累加。calculatedAt为统计完成时间(Unix毫秒)。扫描失败返回错误,不返回部分结果。", + "summary": "Calculate storage usage on demand", + "description": "Login sessions only. Count regular-file bytes in the data directory and actual vault, deduplicating nested roots and ignoring internal symlinks. Attachments/recycle bin are subsets of the vault and do not add to totalSize twice. calculatedAt is the completion time in Unix milliseconds. Scanning failures return errors, not partial results.", "tags": [ - "设置" + "Settings" ], "security": [ { @@ -2959,7 +2959,7 @@ ], "responses": { "200": { - "description": "统计结果", + "description": "Storage totals", "content": { "application/json": { "schema": { @@ -2985,19 +2985,19 @@ } }, "403": { - "description": "只接受登录会话" + "description": "Login sessions only" }, "500": { - "description": "扫描失败" + "description": "Scan failed" } } } }, "/maintenance/status": { "get": { - "summary": "维护状态", + "summary": "Maintenance status", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3006,7 +3006,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3032,7 +3032,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3042,7 +3042,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3056,9 +3056,9 @@ }, "/maintenance/export/{kind}": { "post": { - "summary": "导出完整实例或设置包", + "summary": "Export a full instance or settings archive", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3067,7 +3067,7 @@ ], "responses": { "200": { - "description": "ZIP下载", + "description": "ZIP download", "content": { "application/zip": { "schema": { @@ -3078,7 +3078,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3088,7 +3088,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3098,7 +3098,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3108,7 +3108,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3118,7 +3118,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3142,9 +3142,9 @@ }, "/maintenance/notes/export": { "post": { - "summary": "导出普通 Markdown 笔记库", + "summary": "Export a plain Markdown vault", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3153,7 +3153,7 @@ ], "responses": { "200": { - "description": "ZIP下载", + "description": "ZIP download", "content": { "application/zip": { "schema": { @@ -3164,7 +3164,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3174,7 +3174,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3184,7 +3184,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3194,7 +3194,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3204,7 +3204,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3218,9 +3218,9 @@ }, "/maintenance/uploads": { "post": { - "summary": "创建备份上传,压缩上限2GiB,分块上限8MiB", + "summary": "Create a backup upload, up to 2 GiB compressed and 8 MiB per chunk", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3229,7 +3229,7 @@ ], "responses": { "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3239,7 +3239,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3249,7 +3249,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3259,7 +3259,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3269,7 +3269,7 @@ } }, "201": { - "description": "返回上传id", + "description": "Return upload id", "content": { "application/json": { "schema": { @@ -3303,7 +3303,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3345,9 +3345,9 @@ }, "/maintenance/uploads/{id}": { "put": { - "summary": "顺序追加备份分块", + "summary": "Append backup chunks sequentially", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3356,7 +3356,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3390,7 +3390,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3400,7 +3400,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3410,7 +3410,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3420,7 +3420,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3430,7 +3430,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3472,9 +3472,9 @@ } }, "delete": { - "summary": "取消当前会话的上传", + "summary": "Cancel an upload owned by the current session", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3483,7 +3483,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3509,7 +3509,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3519,7 +3519,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3529,7 +3529,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3539,7 +3539,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3549,7 +3549,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3573,9 +3573,9 @@ }, "/maintenance/uploads/{id}/validate": { "post": { - "summary": "校验归档并生成摘要,展开上限8GiB/100000项", + "summary": "Validate an archive and produce a summary, up to 8 GiB expanded and 100,000 entries", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3584,7 +3584,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3610,7 +3610,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3620,7 +3620,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3630,7 +3630,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3640,7 +3640,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3650,7 +3650,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3674,9 +3674,9 @@ }, "/maintenance/settings/import": { "post": { - "summary": "确认后导入设置和资源,操作前备份", + "summary": "Import confirmed settings and resources after creating a backup", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3685,7 +3685,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3711,7 +3711,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3721,7 +3721,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3731,7 +3731,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3741,7 +3741,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3751,7 +3751,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3788,9 +3788,9 @@ }, "/maintenance/settings/reset": { "post": { - "summary": "确认后重置偏好,保留插件存储", + "summary": "Reset confirmed preferences while retaining plugin storage", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3799,7 +3799,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -3825,7 +3825,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3835,7 +3835,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3845,7 +3845,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3855,7 +3855,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3865,7 +3865,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -3898,9 +3898,9 @@ }, "/maintenance/restore": { "post": { - "summary": "确认后恢复完整实例,操作前备份并重新登录", + "summary": "Restore a confirmed full instance after backup; require signing in again", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -3909,7 +3909,7 @@ ], "responses": { "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -3919,7 +3919,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -3929,7 +3929,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -3939,7 +3939,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -3949,7 +3949,7 @@ } }, "202": { - "description": "已提交恢复;服务将短暂维护并要求重新登录", + "description": "Restore submitted; the service will briefly enter maintenance and require signing in again", "content": { "application/json": { "schema": { @@ -3983,7 +3983,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -4020,9 +4020,9 @@ }, "/maintenance/index/{operation}": { "post": { - "summary": "优化或重建索引", + "summary": "Optimize or rebuild the index", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -4031,7 +4031,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -4057,7 +4057,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -4067,7 +4067,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -4077,7 +4077,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -4087,7 +4087,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -4097,7 +4097,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -4121,9 +4121,9 @@ }, "/maintenance/temp/clear": { "post": { - "summary": "清理本程序未使用临时文件", + "summary": "Clean unused application temporary files", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -4132,7 +4132,7 @@ ], "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -4158,7 +4158,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -4168,7 +4168,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -4178,7 +4178,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -4188,7 +4188,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -4198,7 +4198,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -4212,9 +4212,9 @@ }, "/maintenance/logs/export": { "post": { - "summary": "导出应用日志和运行摘要", + "summary": "Export application logs and a runtime summary", "tags": [ - "维护" + "Maintenance" ], "security": [ { @@ -4223,7 +4223,7 @@ ], "responses": { "200": { - "description": "ZIP下载", + "description": "ZIP download", "content": { "application/zip": { "schema": { @@ -4234,7 +4234,7 @@ } }, "400": { - "description": "操作或输入无效", + "description": "Invalid operation or input", "content": { "application/json": { "schema": { @@ -4244,7 +4244,7 @@ } }, "401": { - "description": "未登录", + "description": "Not signed in", "content": { "application/json": { "schema": { @@ -4254,7 +4254,7 @@ } }, "403": { - "description": "不接受API令牌", + "description": "API tokens are not accepted", "content": { "application/json": { "schema": { @@ -4264,7 +4264,7 @@ } }, "503": { - "description": "正在维护、磁盘空间不足或当前服务不支持此操作", + "description": "Maintenance in progress, insufficient disk space, or operation unsupported by the current service", "content": { "application/json": { "schema": { @@ -4274,7 +4274,7 @@ } }, "500": { - "description": "文件系统、数据库或其他内部故障", + "description": "Filesystem, database, or other internal failure", "content": { "application/json": { "schema": { @@ -4288,10 +4288,10 @@ }, "/auth/me": { "get": { - "summary": "恢复登录会话身份与权限", + "summary": "Restore login-session identity and permissions", "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -4333,7 +4333,7 @@ } }, "401": { - "description": "登录凭据无效或过期", + "description": "Invalid or expired login credential", "content": { "application/json": { "schema": { @@ -4343,7 +4343,7 @@ } }, "403": { - "description": "需要登录会话,API令牌无权调用", + "description": "Login session required; API tokens cannot call this endpoint", "content": { "application/json": { "schema": { @@ -4357,7 +4357,7 @@ }, "/auth/captcha": { "post": { - "summary": "签发5分钟、单次登录验证码", + "summary": "Issue a single-use sign-in captcha valid for 5 minutes", "security": [ {} ], @@ -4379,7 +4379,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -4422,7 +4422,7 @@ } }, "429": { - "description": "验证码签发频率或容量限制", + "description": "Captcha issuance rate or capacity limit reached", "content": { "application/json": { "schema": { @@ -4436,7 +4436,7 @@ }, "/auth/tickets": { "post": { - "summary": "签发绑定当前会话的资源读取或一次性事件票据", + "summary": "Issue resource-read or one-use event tickets bound to the current session", "requestBody": { "required": true, "content": { @@ -4468,7 +4468,7 @@ }, "responses": { "200": { - "description": "成功", + "description": "Success", "content": { "application/json": { "schema": { @@ -4507,7 +4507,7 @@ } }, "401": { - "description": "登录凭据无效或过期", + "description": "Invalid or expired login credential", "content": { "application/json": { "schema": { @@ -4517,7 +4517,7 @@ } }, "403": { - "description": "需要登录会话,API令牌无权调用", + "description": "Login session required; API tokens cannot call this endpoint", "content": { "application/json": { "schema": { @@ -4531,7 +4531,7 @@ }, "/resources/{ticket}/{kind}/{path}": { "get": { - "summary": "受限原生资源;事件票据60秒一次性,其余跟随父会话到期,每次检查父会话", + "summary": "Restricted native resource. Event tickets are single-use and expire after 60 seconds; other tickets expire with the parent session. Check the parent session on every request.", "security": [ {} ], @@ -4563,10 +4563,10 @@ ], "responses": { "200": { - "description": "对应资源原始内容" + "description": "Raw resource contents" }, "101": { - "description": "WebSocket事件升级" + "description": "WebSocket event upgrade" } } } @@ -4577,7 +4577,7 @@ "bearer": { "type": "http", "scheme": "bearer", - "description": "API 令牌(rm_ 开头)" + "description": "API token (prefixed with rm_)" } }, "schemas": { @@ -4629,7 +4629,7 @@ }, "mtime": { "type": "integer", - "description": "修改时间(Unix 毫秒)" + "description": "Modification time (Unix milliseconds)" }, "icon": { "$ref": "#/components/schemas/TreeIcon" @@ -4644,18 +4644,18 @@ }, "path": { "type": "string", - "description": "原路径" + "description": "Original path" }, "dir": { "type": "boolean" }, "deletedAt": { "type": "integer", - "description": "Unix 毫秒" + "description": "Unix milliseconds" }, "assets": { "type": "string", - "description": "随条目保存的旧图片目录,可省略" + "description": "Legacy image directory retained with the entry; optional" } }, "required": [ @@ -4673,7 +4673,7 @@ }, "line": { "type": "integer", - "description": "第一处匹配所在行(从 1 开始);只有路径匹配时为 0" + "description": "First matching line, starting at 1; 0 when only the path matches" }, "snippet": { "type": "string" @@ -4682,7 +4682,7 @@ }, "Event": { "type": "object", - "description": "tree:目录结构变化;changed:内容变化(rev 为新版本);deleted:删除;moved:移动(from → path);settings:设置变化", + "description": "tree: directory structure changed; changed: contents changed (rev is the new revision); deleted: deletion; moved: move (from -> path); settings: settings changed", "properties": { "type": { "type": "string", @@ -4726,7 +4726,7 @@ }, "lastUsedAt": { "type": "integer", - "description": "Unix 毫秒,0 为从未使用" + "description": "Unix milliseconds; 0 means never used" } } }, @@ -4797,7 +4797,7 @@ }, "value": { "type": "string", - "description": "Emoji 字符或内置线性图标名称,最多64字节" + "description": "Emoji character or built-in outline icon name, up to 64 bytes" } } }, @@ -5097,20 +5097,20 @@ } }, "x-business-codes": { - "0": "成功", - "40000": "参数无效", - "40001": "验证码错误或已使用", - "40002": "验证码过期", - "40003": "当前密码错误", - "40100": "认证无效", - "40101": "登录密码错误", - "40300": "权限不足", - "40400": "资源不存在", - "40900": "资源冲突", - "41200": "文档版本冲突,data.rev为最新版本", - "41300": "超过大小上限", - "42900": "请求过于频繁", - "50000": "服务端错误", - "50300": "维护中" + "0": "Success", + "40000": "Invalid parameters", + "40001": "Incorrect or already-used captcha", + "40002": "Expired captcha", + "40003": "Incorrect current password", + "40100": "Invalid authentication", + "40101": "Incorrect sign-in password", + "40300": "Insufficient permissions", + "40400": "Resource not found", + "40900": "Resource conflict", + "41200": "Document revision conflict; data.rev is the latest revision", + "41300": "Size limit exceeded", + "42900": "Too many requests", + "50000": "Server error", + "50300": "Maintenance in progress" } }