本地优先的桌面手账式笔记软件,把每一页笔记当作可自由排版的纸张画布。
文本、图片、贴纸、胶带笔迹和画笔笔迹都作为独立元素放置在画布上,支持拖拽、缩放、旋转、图层管理、多页编辑、阅读预览、`.tnote` 单文件保存和多人实时协作。
- 画布编辑:支持页面缩放、画布移动、元素拖拽、缩放、旋转、右键菜单、显式对齐线和页面/元素吸附。
- 多页笔记:左侧页面栏可新建、切换、删除、重命名和拖拽调整页面顺序。
- 多标签页:顶部标签页可同时打开多个笔记或阅读视图,并支持切换、关闭和右键重命名。
- 图层管理:右侧图层栏可查看真实缩略图、选择、排序、重命名和删除元素。
- 元素控制:右侧控制栏独立于图层栏,提供文本、图片、贴纸、画笔和胶带笔的属性设置。
- 文本能力:支持富文本编辑、字体选择、系统字体导入打包、字号、颜色、背景和边框样式。
- 图片能力:支持图片素材导入、剪切板粘贴导入、画布放置、元素级裁剪和背景图裁剪。
- 贴纸能力:贴纸库独立于普通素材库,支持内置贴纸和用户上传贴纸;贴纸裁剪只影响当前元素,不污染贴纸库。
- 绘制能力:画笔支持自由笔迹;胶带笔支持直线笔迹、宽度、颜色和图案样式。
- 协作模式:支持房主发起联机、邀请链接加入、房主审批、在线成员、远端鼠标、聊天、P2P 优先和服务器中转兜底。
- 本地日志:后端和前端关键事件写入同一个
timenotes.log,便于排查保存、打开、导入和 WebView 问题。
- 桌面框架:Go + Wails3
- 前端框架:React + TypeScript + Vite
- UI:Semi Design、Semi Icons、TailwindCSS
- 画布:DOM 元素层 + Konva 绘制层 + Moveable 变换控制
- 文本:Tiptap
- 协作:Yjs + WebSocket 信令 + WebRTC DataChannel,服务端为 TimeNotesServer
- 文件格式:
.tnoteZIP 单文件包
.
├── main.go # Wails 应用入口和服务注册
├── document_services.go # .tnote 新建、打开、保存、资源导入和导出服务
├── document_types.go # .tnote 文档、页面、元素、素材等 Go 数据结构
├── logger.go # 后端日志和前端日志桥接
├── frontend/
│ ├── src/
│ │ ├── components/ # 顶栏、画布、图层、素材、阅读器等组件
│ │ ├── providers/ # 文档状态和协作状态
│ │ ├── lib/ # 文件、字体、日志、ID 等工具
│ │ ├── data/ # 内置贴纸等静态数据
│ │ └── assets/ # Logo 和内置图片资源
│ ├── bindings/ # Wails 生成的 TypeScript 绑定
│ └── package.json # 前端依赖和构建脚本
├── build/ # Wails 平台构建配置
├── AGENTS.md # Codex/工程协作规则
└── README.md
需要本机已安装:
- Go
- Node.js / npm
- Wails3 CLI
- WebView2 Runtime,Windows 通常已自带或由系统安装
安装依赖时手动执行:
npm install
cd frontend
npm install前端构建:
cd frontend
npm run buildGo 测试:
go test ./...Go 构建:
go build .Wails 开发模式:
wails3 devWails 打包:
wails3 package修改导出的 Go 服务方法或类型后,需要重新生成前端绑定:
wails3 generate bindings -clean=true -ts前端开发服务器固定使用:
127.0.0.1:9245
如果启动 wails3 dev 时出现端口占用:
listen tcp 127.0.0.1:9245: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.
先检查端口:
Get-NetTCPConnection -LocalPort 9245 -ErrorAction SilentlyContinue
netstat -ano | Select-String ':9245'确认 PID 属于本项目此前启动的 Vite/Wails/Node 进程后再停止:
Stop-Process -Id <PID> -Force不要停止不属于本项目或用户仍在使用的进程。
.tnote 是 TimeNotes 的可编辑源文件格式,本质是 ZIP 包。保存后的文件不依赖本机绝对路径,适合复制到其他设备继续打开编辑。
包内主要内容:
manifest.json:格式版本、应用版本、资源索引和内部路径。document.json:页面、元素、素材引用、字体引用和样式快照。yjs/update.bin:Yjs 二进制状态。assets/:普通图片素材和背景图。stickers/:贴纸资源,独立于普通素材。fonts/:用户导入或系统字体打包后的字体文件。
当前格式版本由后端和前端共同维护,结构变化需要显式迁移逻辑。
应用启动时会创建 timenotes.log。优先写到可执行文件所在目录,例如:
D:\TimeNotes\TimeNotes\bin\timenotes.log
日志写入时机包括:
- 应用启动和日志系统就绪。
- 新建、打开、保存
.tnote。 - 导入素材、导入字体、枚举系统字体。
- 导出页面或 HTML 的后端事件。
- 前端关键业务事件和捕获到的错误。
- Wails 或 Go panic/error。
日志只保留一个主文件,超过 2MB 会轮转为 timenotes.log.1。临时排障可用环境变量指定日志目录:
$env:TIMENOTES_LOG_DIR = "D:\TimeNotes\logs"TimeNotes 的联机协作由桌面客户端和独立服务端共同完成。服务端项目地址:
https://github.com/algfwq/TimeNotesServer
协作流程:
- 房主在左侧“协作”标签页填写服务端地址,例如
http://127.0.0.1:8787或公网服务地址。 - 房主点击“发起联机”,客户端调用 TimeNotesServer 创建房间,并得到邀请链接。
- 邀请链接包含
roomId和roomKey。roomKey只用于鉴权,不写入文档和日志。 - 协作者通过邀请链接加入后,服务端先把连接置为待审批状态。
- 房主收到 Semi 弹窗,选择同意或拒绝。只有同意后,协作者才真正进入房间并开始同步。
- 房主可以在“在线状态”列表中右键某位协作者并将其踢出房间。
- 房主退出协作时,房间关闭,其他协作者会自动退出,旧邀请链接失效。
同步内容:
- 画布文档状态使用 Yjs update 同步,服务端使用 SQLite 持久化第一阶段房间状态。
- 鼠标位置、当前页面、选中元素、正在编辑元素属于 presence,只在线传输,不写入
.tnote。 - 聊天消息只在线转发,不落库。
- 客户端之间优先建立 WebRTC DataChannel 直连传输;P2P 失败、断开或启用“强制服务器中转”时,同一协议消息走服务端 relay。
本机测试建议:
- 启动 TimeNotesServer,并确认
GET /healthz返回正常。 - 打开两个 TimeNotes 客户端或两个独立浏览器窗口。
- 用户甲发起联机,复制邀请链接。
- 用户乙通过邀请链接加入,等待用户甲审批。
- 审批通过后测试画布元素创建、拖动、文本字体、远端鼠标、页面切换、聊天和退出通知。
- 勾选“强制服务器中转”后重复测试,确认 P2P 不可用时仍能协作。
跨设备或公网测试时,不要把服务端地址填成 127.0.0.1,应填写其他客户端可访问的局域网 IP、域名或 HTTPS 反向代理地址。
提交或交付前建议至少执行:
go test ./...
go build .
cd frontend
npm run build涉及画布、裁剪、拖拽、对齐线、贴纸或阅读视图的改动,需要用真实浏览器或 Wails WebView 验证,而不是只通过静态构建判断。
App.tsx只做应用组合入口,编辑器逻辑拆到组件和 hooks。- 页面坐标使用画布坐标,不保存屏幕缩放后的像素。
- 选择框、hover、右键菜单、缩放缓存等 UI 临时状态不写入文档模型。
- 贴纸库和普通素材库保持独立。
- 字体必须打包进
.tnote后才能保证其他设备正常展示。 - 打开
.tnote时后端必须校验 ZIP 内路径,不能信任压缩包条目路径。 - 不要把 WebView2 profile/cache 当作项目业务数据保存到仓库。