> [!NOTE] > 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。 > [English](./README.en.md) · [原始项目](https://github.com/zakirullin/files.md) · [上游 README](https://github.com/zakirullin/files.md/blob/HEAD/README.md) > 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。 Files.md icon # Files.md 一处私密、安静的思考空间。一款只为你 `.md` 文件而生的简单应用。

Files.md screenshot English · 简体中文

你的整段人生,都可以放在这里: - 📌 笔记 - 📝 文档、项目 - 💚 日记、习惯 - ✅ 清单、任务 全部以纯 `.md` 文件保存,本地优先,对 LLM 友好。**隐私无虞——任何数据都不会上传到服务器**。 > 让你的数据,以纯粹的本地文件形式属于你。 > 让打开这些文件的软件,也真正属于你。 > 用文件和你自己的大脑,去培育知识。 > 借助 LLM,去打磨文件周围的软件。 > **属于自己的纯文件与软件,才能穿越时代。** 线上体验:[app.files.md](https://app.files.md)(Beta)。主站:[files.md](https://files.md)。 你可以把它用作第二大脑、卡片盒笔记(Zettelkasten)、笔记、日记、任务、清单等等。 **这个项目我已经做了 5 年。** 欢迎 **[在 GitHub 上支持作者 💚](https://github.com/sponsors/zakirullin)**。 [随手记下想法](#随手记下想法) · [如何深入思考](#如何深入思考) · [第二大脑?](#第二大脑) · [日记、任务、清单](#日记) ## 又一个笔记应用? 也算是。但这一次: - 只保留必要功能,**约束孕育创造力** - 无需安装,一个浏览器就够了 - 离线可用 - **本地优先,文件不会离开你的设备** - 自由开源 - 代码极其简洁。**一个人或一个 LLM 能把整个项目装进脑子里** - 换句话说,这个项目具有[低认知负荷](https://github.com/zakirullin/cognitive-load) - 代码库为 LLM 留好了扩展空间,随你按需添砖加瓦 - 便携,没有任何构建系统,直接打开 `web/index.html` 即可 - 同步开箱即用,但完全可选 - 服务器就是一个二进制文件(也可以直接用 iCloud / Dropbox / Google Drive 同步) - Telegram 机器人,方便你在路上随时访问文件 ## 如何使用 - 打开 [app.files.md](https://app.files.md)(推荐 Chrome) - 点击地址栏右侧的 "Install files.md":
Install
- 打开一个本地文件夹,让改动持久化保存 - 偶尔按 `Cmd+Shift+R` 强制刷新一下,拿到最新更新 ## 如何同步 | 方案 | 文件存放在哪儿 | 多设备同步 | 是否需要服务器 | 适合的人 | |-------------------------------------------------------|--------------------------------|---------------|--------------------------|------------------------------------------------| | **本地优先**,`app.files.md` 完全不发送任何数据 | 你设备上的某个文件夹 | 否 | 不需要 | 追求极致隐私,数据完全留在本地 | | **云盘文件夹同步**(iCloud / Dropbox / Google Drive) | 你现有的云盘文件夹 | 是 | 不需要(由云服务商负责) | 不想自己跑服务器,也想多设备同步 | | **[自托管同步服务器](docs/your-own-server.md)** | 你自己(或局域网内)的服务器 | 是 | 一个 Go 二进制 | 想在自家或公司网络里多设备同步 | | **托管同步服务器** | 我们托管的服务器 | 是 | `api.files.md` | 想立刻上手,不折腾任何配置 | ## 随手记下想法 你可以打开聊天框,随手把脑子里冒出来的想法记下来。 Files.md screenshot 打开聊天框,发一条消息:
挑一个去处(也可以稍后再决定):
**这条流程足够让你顺手收下笔记、任务、日记和清单。** ## 在 Telegram 机器人里保存 打开聊天,输入点东西,按 `Enter`:
Saving things in bot
就这样,你的笔记已经保存到一个 `.md` 文件里了。 机器人的完整功能:
别担心——默认情况下它比看上去简单得多。 [Telegram 机器人入口](https://t.me/FilesMDBot)。**其他即时通讯工具也会陆续支持。** ## 如何深入思考 **让想法相连,让它们彼此叠加,并真正想透。** 1) 我用 [app.files.md](https://app.files.md) 来沉淀关于大脑和软件开发的认识 2) 我把新笔记放进 `brain` 或 `dev` 文件夹,一条笔记只承载一个想法 3) 在 Web 应用里,把彼此相关的笔记连接起来(按 `[` 即可) 4) 所有东西最终都连成一片,正如我们的大脑 5) **我会花时间在这些笔记之间穿行、反复琢磨** 6) 久而久之,有些 `brain` 里的笔记和 `dev` 里的笔记开始看起来意外地相关 7) 跨领域的这种连接,会激发出洞察 8) 我据此写了一篇文章:[软件开发中的认知负荷](https://github.com/zakirullin/cognitive-load) 这个过程帮助我: - **深入思考**(在 AI 时代尤其重要) - **系统性地思考,看见更完整的图景** - **写出更有洞见的文字** 要做到以上几点,**你必须用自己的脑子去想**,而不是依赖什么花哨模板或者 AI 工作流。 - 一开始什么结构都别建,0 个文件夹 - 一条笔记只承载一个想法 - 每条笔记要在脱离上下文时也能被读懂 - 学到新东西就立刻用上,别留给"未来的自己" - 把相关的笔记彼此链接起来 - **常回去翻翻,再想想** 我和我的朋友用这套朴素的方法已经五年了,效果一直很好。 ## 第二大脑? 我想引用 [I Deleted My Second Brain](https://web.archive.org/web/20260518143828/https://www.joanwestenberg.com/i-deleted-my-second-brain-692aa40d59d5f06dd5131e43/) 里的一段话: > Obsidian 是一款出色的软件,我深爱它。但任何东西失去节制都会变成陷阱。嵌套文件夹里的 Markdown 文件、追踪你生产力的插件、暗示某种全知感的关系图谱——看着自己的笔记编织成星座般的网络,会让人产生一种掌控的幻觉。但星座只是投影。它们讲故事,却并不等于理解。 > > 刚开始使用知识管理工具时,我以为自己是在解决"遗忘"这件事。后来,我以为自己解决的是"整合"。 > > **最终,我意识到我创造了一个新问题:拖延。系统越是壮大,我就越是把真正的思考工作推给某个未来的自己——那个会整理、贴标签、提炼、淘金的自己。** > > **而那个自己从未到来。** 第二大脑是一个迷人的概念。 那些进阶模板、插件、AI 工作流…… 让人忍不住想把整个互联网的智慧都打包收下。 这个整洁的系统看起来真美。每多一条笔记,多巴胺就涌出来一些。 _第二大脑越来越好。_ 可是,**第一大脑从来没有真正变得更聪明。** 这才是问题——在 AI 时代,你的第一大脑前所未有地宝贵。 用**你的脑子**去把笔记想透。 工具并不重要,思考才是。 新增一条笔记之前,先试着回答这两个问题: - 这则新知识能不能磨利我的判断、拓宽我的分类框架? - 拥有这份知识后,我能用不同的视角看世界吗? ## 笔记反而会阻碍体验 - 一边读一边记,很容易让我们误以为自己理解了文本 - 我们以为自己理解了,但其实**只是知道** - **而当"知道"变得足够多,我们甚至会觉得自己已经"做过了"(或者起码尝试过了)** 最糟糕的是,**我们因此不再让新的体验发生——因为我们"已经知道了"**。这就是一道知识的高墙。生活给了我们去亲身经历的机会,我们却拒绝,理由是"我已经知道了"。 ## 通过阅读和做笔记自我疗愈?🧘‍ **在情绪层面受到的伤害,必须在情绪层面治愈。** 不是靠脑力劳动和做笔记。 没有行动的阅读,是娱乐,是一种拖延的形式。 **再多自助类书籍也治不好情感的伤口。** 真正有帮助的,是心理治疗、改写练习与椅子工作。是冥想。 **疗愈来自感受。** ## 什么时候应该做笔记 如果你的目标是: - 对某件事建立更深入、更有结构的理解 - 做研究 - 写一篇文章或一本书 那么记笔记完全合适。 ## 日记 对什么事情感觉良好?发一条消息。
然后点 "To Journal"。 或者直接在消息末尾加上 ` jj` 或 ` жж`。 你的记录会被妥帖地存进 `journal/YYYY.MM Month.md`。 ## 任务 ✅ 你正心流满满地工作着。 同事突然让你发一份报告。 把这件事一直挂在脑子里很耗能。 丢一条消息,继续保持心流。
只添加小而可执行的事项。 不要写"规划一次假期"那种。 写下"反正要做的事情"的第一小步即可。 **写你愿意去做的,不是你只是想象会去做但还没动力的。** 你的任务列表不该带来负罪感。 要稍后再做的事情,按 "To Later"。 ## 清单 🛒 朋友推荐了一本书给你。 家里的黄油和面包没了。 这些事一直挂在脑子里,会让人心累。 丢进聊天框,再转到对应的清单里。 ## 文件结构 你不必费心想结构,它是预定义的。 当然,你也完全可以按自己的喜好来。 - 聊天:`Chat.md` - 笔记:`brain/Note.md`、`/*.md` - 清单:`Read.md`、`Watch.md`、`Shop.md`、`MyChecklist_.md` - 日记:`journal/2024.08 August.md` - 任务:`Later.md` - 习惯:`habits/Ate consciously.md`、`habits/*.md` - 图片:`media/*`(png、jpg、webp、gif) - 归档:`archive/*.md` - 配置:`config.json` 这份结构也可以在 [files.md/AGENTS.md](https://files.md/AGENTS.md) 取到。 把它作为 `AGENTS.md` 放进你的知识库——你的文件就成了 AI 可以参与的鲜活个人空间。 ## 快捷键 | 快捷键 | 操作 | |----------------------------------------|-------------------------------| | `[` | 插入到某个文件的链接 | | `Cmd+K` / `Ctrl+K` | 打开文件搜索框 | | `Cmd+N` / `Ctrl+N` | 新建文件 | | `Cmd+M` / `Ctrl+M` | 移动文件 | | `Cmd+D` / `Ctrl+D` | 删除文件 | | `Cmd+Enter` / `Ctrl+Enter` | 打开聊天 | | `Cmd+Shift+Enter` / `Ctrl+Shift+Enter` | 切换聊天弹窗 | | `Cmd+[` / `Ctrl+[` | 跳到上一个文件 | | `Cmd+]` / `Ctrl+]` | 跳到下一个文件 | | `Cmd+~` / `Ctrl+~` | 切换侧边栏 | | `Cmd+B` / `Ctrl+B` | 切换**粗体** | | `Cmd+I` / `Ctrl+I` | 切换*斜体* | | `Cmd+Y` / `Ctrl+Y` | 插入复选框 | | `Cmd/Ctrl` + `Click` | 复制纯文本 / 打开链接 | | `Ctrl+Cmd+Space` | 插入 emoji(macOS) | ## 一些好用的脚本 所有脚本都在 `cmd` 目录下,**请在你的笔记文件夹里运行**。请先安装 [Go](https://go.dev/doc/install)。 ### 把 Whoop 数据写进日记 ``` go run /abs/path/to/files.md/cmd/whoop/whoop.go ``` ### 把 wikilinks 转成 Markdown 链接 把 `[[wikilinks]]` 转成标准的 `[Name](/path.md)`(支持 `--dry-run`): ``` go run /abs/path/to/files.md/cmd/tomdlinks/tomdlinks.go . ``` ### 插入反向链接 在被引用的文件里加上回链(支持 `--dry-run`): ``` go run /abs/path/to/files.md/cmd/backlink/backlink.go ``` ### 调整日记时间戳 把日记文件中的时间戳整体平移 N 小时(换时区后很实用): ``` go run /abs/path/to/files.md/cmd/shifttime/shifttime.go ``` ## 文档 [在自己的服务器上部署](docs/your-own-server.md) [Telegram 机器人](docs/bot.md) [同步流程](docs/sync-flow.md) [集成测试](docs/integration-tests.md) ## 仓库结构 - `web` —— Web 应用(PWA),入口是 `index.html` - `web/lib` —— 前端依赖库 - `cmd/server` —— 服务端入口 - `cmd/*/` —— 处理 `.md` 文件的脚本 - `server/bot.go` —— 机器人 - `server/sync/` —— 同步 API 服务端 - `vendor` —— 后端依赖 - `tests` —— 端到端测试,会同时覆盖 Web 应用和服务端 ## 如何贡献 - 初级开发者也应能读懂代码 - 理想情况下,每个 PR 都应该删减或简化代码,而不是新增 - **代码越少,我们就越灵活** - 所有依赖都是我们自己的代码与责任,能不引就不引 - 代码应当自洽,所以 `vendor` 和 `web/lib` 都在仓库里 - **我们真的需要这个功能吗?它能帮我们做"真正的事",还是只为了一时的多巴胺?** 更完整的原则请参考 [这份指南](https://github.com/zakirullin/cognitive-load)。 ## 后端规范 - 我们写**测试** - 我们不用 `get*` 前缀命名方法 - 不要 panic,错误本身就是业务逻辑的一部分 - 如果要忽略某个 error,必须留下解释 WHY 的注释 - 我们一律包装 error,并补上方法层面的上下文 - 客户端代码里不使用迭代器 - 优先使用真实实现或至少 fake,而不是 mock / stub - import 只在与其他 import 冲突时才重命名 - **为了可移植性,所有数据都存在纯 `.md` 文件里** ## 前端规范 - 如果你就地修改了第三方库,请用 `PATCHED` 关键字标注 - **要是有一天我们能用自己的小实现替换掉 `CodeMirror`,那就太棒了** - 没有构建系统,**十年后我们打开 `/web/index.html`,它仍然要能直接跑** - 别忘了:在"检查锁"和"获取锁"之间出现的 `await` 会引入竞态 - 别写脆弱的 e2e 测试。先是带来负面情绪,然后我们就再也不跑测试了 - 大多数 bug 都源于异步流程中途被打断时的竞态条件 ## 术语表 - `filename` —— 带扩展名的文件名,例如 "note.md"(**用它作为 ID**) - `header` —— 去掉扩展名并首字母大写后的文件名,例如 "Note" - `body` —— 文件内容 - `dir` —— 用来按类别存放笔记的目录,例如 "happiness" - `userID` —— 即 chatID。大多数场景下我们就直接用 chatID(私聊机器人) - 文件的 `ctime` —— 数据块或元数据变更时间:所有权、位置、文件类型、权限变更都会触发。父目录改名不会触发,移动文件会触发,重命名会触发。我们需要它来追踪文件的位置变化,比如什么时候被移到归档、追踪任务的"愤怒度"等 - 文件的 `mtime` —— 文件内容最近一次被修改的时间。和 `ctime` 不同,所有权、权限、改名都不会影响它。我们用它来做同步 - 目录的 `ctime` —— 增删文件或子目录会触发(类似 `mtime`,再加上 inode 的变化,比如改名) 每个文件都可以通过文件名与所在目录唯一定位。我们只支持一层嵌套。 ## 性能 项目跑起来飞快 :) 如果你怕用文件或互斥锁会带来性能开销,看一眼这张对比: ``` Mutex 加 / 解锁 = 25 ns 从 SSD 随机读 4K = 150,000 ns 1 ms = 1,000,000 ns ``` ## ADR(架构决策记录) - `06.05.2026` 从 Today.md 切回 Chat.md。用户访谈发现"chat"这个概念用户更容易理解。而且 "open chat" 这句话在机器人和网页应用里语义都一致。 - `02.05.2026` hide-token 现在在每次变更时同步运行;之前的 100ms 防抖会让链接和格式化文本在逐字删除时出现抖动。 - `06.05.2026` 把 Inbox.md 和 Today.md 合并成了 Today.md。"Inbox"这个名字太抽象、太"生产力"和 GTD 味道。我想要的是从容和简洁。Today 更像"我活在聊天里的那一页"。 - `23.04.2026` 把 API_HOST、APP_HOST 改成了 API_URL、APP_URL。不同环境下,配置里能携带 schema 等更多信息总归更好。 - `22.04.2026` 机器人里的 Inbox 条目改用稳定的内容哈希识别(对去掉 `- [ ] ` / `- [x] ` 标记后的块取 `fs.Hash`),而不是位置序号。这样即便中间有条目被新增/删除/完成,按钮也仍指向正确的那一行。 - `06.05.2026` 同时存在 "to inbox" 和 "to chat" 两个按钮,心智上挺累。也很难轻松地把任务往 chat 里随手一丢——因为它会先落到 inbox,还要多点一次。这一次多余的点击,正是让"添加新任务"变得令人厌烦的原因。我放弃了两条不同的流程,现在一切都先进 inbox,每一项都是 Markdown 复选框。额外的好处:PWA 默认展示 chat 里的任务,特别顺手。也许 "inbox" 这个词心智负担太重,"chat" 听起来更轻松。再观察一下。 - `11.04.2026` 虽然我希望以纯 Markdown 链接形式存储链接,但视觉上我希望它们能像极简的 `[link]` 一样工作。所以当光标停在该行时,我会隐藏 `(...)` 那部分。仅对指向 md 文件的链接生效。 - `11.04.2026` 又把标准 Markdown 链接拉回来了。我希望这个知识库跨平台可用,它必须在 GitHub 上也能正常显示。 - `05.04.2026` 试过把 `web/*` 移到根目录以求简洁,结果发现是个坏主意——必须有一个显式的目录,作为服务器上的公开 DOCROOT。 - `19.04.2026` 改成了 `[link]` 形式的链接。`[link](full%20path)` 太重也太突兀,我们也不想处理路径变更。 - `21.09.2025` 移除了 WASM。我当时遇到一个 bug:从 Inbox.txt 删除一条消息后,按"move to file"没有把它写到目标文件里。我没复现出来,但发现了一大堆复杂度:JS -> Go(writeFile)-> Go 等待来自 JS 的 promise -> 中间的 JS Golang 运行时 -> JS(writeFile)-> Go(promise 返回)-> 把结果发回 JS。而且这一切还得跑在独立 goroutine 里,因为 WASM 和 JS 共用一个线程。再加上 Go 的 WASM 仍是实验性的。组件太多、不确定性太大。当时我不愿意用 JS 重写已经能用的逻辑,于是用了 WASM 撑了一段时间。现在该用 JS 把它重新实现,把这一整摞复杂度都甩掉。另外 `inbox.wasm` 大约 8MB,我希望整个应用尽可能小。 - `11.07.2025` 决定先用 OPFS 作为文件系统的初始驱动。浏览器兼容性更好,对用户更省心。应用默认以 OPFS 启动;用户需要时可以打开一个本地目录,切到 Local FileSystem API。DirHandle 会被保存到 IndexedDB,下次复用。 - `08.07.2025` 根目录现在是 `/` 而不是空字符串。Web 应用里所有文件按路径唯一标识,不再用 `dir + filename`,并限制为一层嵌套。 - `11.04.2026` Dropbox 会改动新创建文件的某些元数据,因此 `ctime` 会被改写。我考虑过把同步切到 `mtime`,但这样就检测不出重命名(虽然我们另有机制检测重命名),而 `mtime` 整体上更可靠:权限/所有权变更也不会触发同步。最终切换到了 `mtime`。`mtime` 用于内容级同步,`ctime` 用于追加式同步日志(重命名 / 删除)。另外 `mtime` 可以从 `.git/archive` 里恢复,`ctime` 不行。 - `30.06.2025` 决定把所有流程,包括待办列表,都迁移到 Chat.md。补充:这种流程不太方便处理多行任务,可能需要同时支持文件和索引。两种实现思路:参数统一编码,复用同一个命令处理器加 if 分支;或者用不同的命令处理器分别处理 chat / 文件。我选了后者。补充:如果走分流,"move to" 按钮的配置会更复杂。补充:也许在"move to file"时把文件移回 Chat.md,复用现有流程?补充:目前看来还不错。我们的 chat.md 充当一个仅追加日志。额外的好处是:即便某些流程(比如安排时间 / 移动)没走完,内容也保留在日志里,下次可以继续。 - `06.05.2026` 所有新进消息现在默认进 Chat.md。在此之前它们会被移到 `/chat` 并变成任务,这对简单的 to-do 列表很合适,但对其他用例不够灵活。我意识到开会时,我真正需要的就是一个简单的输入框,把脑子里乱七八糟的东西先丢进去,不必立即决定怎么处理,回头再整理就好。它们可能是任务、可能是日记、也可能就是文件。同时保持一条简单到无需理解的默认流程,对用户更友好——所有消息都丢进同一个文件,仅此而已。 - `27.06.2025` Chat 现在默认是"一个大文件"模式,也就是把所有消息都堆进同一个文件,不做更多事。还是那句话:从最简单的流程开始,不要一上来就把用户淹没。补充:如果选了完整模式,我们就得提前建好目录,"to habits"、"to read/shop" 之类才能工作。用户用不上的话,把目录删了我们就不再自动重建(不像"按需创建"模式那样)。所以"按需创建"并不适合所有场景。 - `26.06.2025` 之前是提前把所有目录都建好,现在改成按需创建。这样不会一上来就把用户的知识库塞得乱七八糟。 - `24.06.2025` 同步时改用微秒级别追踪文件变化。连续创建文件之间的间隔通常在 1000μs–5000μs,足够区分。没用纳秒是因为 JS 处理 int64 精度有问题。补充:Linux 用的是内核缓存时间,按 `CONFIG_HZ` 间隔更新(`grep CONFIG_HZ /boot/config-$(uname -r)`),我这里是 1000(1ms)。实际操作之间的间隔通常远大于 1ms:用户交互、网络延迟、磁盘 I/O 都会拉长间隔。只有在原生紧凑循环里更新文件才可能踩坑。 - `16.06.2025` 我觉得是时候让知识库真正跨平台了——禁掉文件名里的 `:?<>*` 等字符。这些字符在某些环境(Windows、PWA)里本来就不允许。 - `15.09.2025` 我想在浏览器里有类机器人的能力。又不想把已经被良好测试过的代码用 TypeScript 重写一遍,所以用了 wasm。它确实跑得挺好。 - `12.06.2025` 我们把 Telegram 机器人当作通往知识库的"无干扰、只写"入口。唯一的问题是它在欧美没那么流行。后来想到:把窗口缩小后,app.files.md 也可以直接呈现成一个聊天界面!这将是移动端的默认形态。 - `04.06.2025` 引入了用于同步的追加式日志。无状态同步实现起来很费劲——每次请求都要把所有文件发过去。既然服务器端只处理重命名,那我们只追踪重命名就好。 - `04.06.2025` 仅做内容同步(不含重命名 / 删除)时,服务端不存任何状态,只比较哈希和最近的 ctime。 - `11.11.2024` 移除了 Wikilinks 支持。只保留纯 Markdown 链接,我们的知识库必须保持互操作性。 - `26.10.2024` 更新现在按用户串行处理。原因是并发写文件出现过竞态。同时还遇到过转发消息乱序的问题,导致无法合并成一条。 - `06.10.2024` **移除了 fyne.io**。一开始我只是想找一个比 Electron 更轻量的方案,fyne.io 看起来是个理想候选。用了几天,机器人 80% 的功能已经搬过来了,体验也不错。但要把剩下 20% 落地,得花上巨大的工夫——滚动行为、emoji 渲染、文本选择、链接支持等等细枝末节。未来还要做图片上传、Markdown/HTML 渲染,在一个不基于 webview 的工具集里这些会非常痛苦。尽管我个人挺反感用 Web 技术做桌面应用,但似乎没得选。试试 wails.io。 - `09.09.2024` 我们用 vendoring 管理依赖。我希望所有的少量依赖都在仓库里,这样就不必担心依赖被屏蔽或下线。我们的仓库就是自给自足的"唯一可信源"。 - `09.06.2025` 我们在 db、journal、userconfig 里使用细粒度锁,而不是给每个用户上一把全局锁,以避免瓶颈。Workers 可能要调外部 API(如 ChatGPT),不希望长期持有用户锁。**已 PATCHED**——加入了按用户串行更新,`bot` 自身不会导致竞态,但 `bot` 和 `worker` 之间仍可能竞态,所以继续保留细粒度锁。 - `20.08.2024` 我们在每次访问时都从配置文件读取 userconfig 的每个字段。不需要在 `bot.Answer()` 前后整体加载/保存配置。修改时必须重新读取,避免写回过时数据。设想一下:如果只在 `bot.Answer()` 之前加载一次,而 `bot.Answer()` 中又有可观的网络延迟(比如对外部接口发请求要 2 秒),那这 2 秒内 `worker.MoveDueTasks()` 完全可能改写 `userconfig.Schedule`,引发数据竞争(机器人答完后写回的就成了过期数据),调度信息会丢失。 - `08.08.2024` 早期清洗(Sanitize Early)。我们放弃了在 `Path` 方法里做清洗——那种行为非常违反直觉,会把路径搞坏。东西一收进来就应该清洗。大多数命令都基于 md5 哈希工作,那种场景不需要清洗。 - `13.07.2024` 用 `gofumpt` 做更严格的格式化。`gofumpt` 接受的格式是 gofmt 的子集。在格式选项上越少做选择,越好。 - `13.07.2024` FS 的结构里应该带上 `userFS` 名字,明确它是按用户命名空间隔离的。 - `09.07.2024` "Note" 这个词太空泛。我们改用 "file",不再套用"笔记"这种高层抽象。 - `13.08.2024` 放弃了 AST 解析 / 渲染。走 AST 时遇到太多边界情况,代码也愈发复杂。Markdown 并不那么难解析,老老实实写直白的代码就好。最终代码量降到原来的 1/3,理解起来也轻松多了。MD->HTML 转换我们做了同样的事。Telegram 并不支持完整的 HTML 标签,自己写一个 md-to-html 转换器反而更容易。 - `08.07.2024` 遵循 Tolerant Reader 原则。解析时遇到乱码就跳过;但如果看到看起来合法的标记(比如 `###`)后面跟着无效数据,就直接 panic。TODO:在读写循环中保留这些乱码。 - `07.07.2024` 引入了 https://github.com/rivo/uniseg。在 Go 里,string 是只读的字节切片,可以通过 `for` 或 `[]rune(str)` 转成 Unicode 码点。但多个码点可能被组合成一个"用户感知"的字符,也就是 Unicode 规范里说的"字素簇"(grapheme cluster)。例如白色圆圈 "⚪" 由两个 rune 组成,却是一个字素簇。 - `07.09.2024` Markdown 转 HTML。用户笔记里可能有非法 Markdown,直接发给 TG API 会失败。所以我们先转义 HTML,再把用户的 Markdown 转成 HTML,最后通过 Telegram API 以 HTML 形式发出。 - `13.06.2023` 文件名哈希化。所有出现用户输入的地方都应该用 `fs.hash`,否则文件名会变得很长,而 Telegram 会返回 `INVALID_DATA`(callbackData 最大 64 字节)。 - `13.06.2023` 引入了 `db.go`。本来就需要把 Redis 抽象出来(不然测试不好写)。 - `13.03.2023` `db.go` 这个包里不存 `userID`(我们经常单独使用 `userID`)……真的吗?也许哪天我们会在与用户无关的场景下用它(比如全局 bot 统计)。补充:之后把 `userID` 挪进了类里。未来如果在用户作用域之外还要复用这个类再说,眼下先这样。 - `13.06.2023` 我们不能在 `fs.Put` 里给文件名做首字母大写——万一这是用户在外部自己建的文件(小写)就被改坏了。