fad9760265
- 新增 Node 服务端(Hono + Hocuspocus + node:sqlite),数据保存在服务器 - 基于 Yjs 的多端实时同步,本机 IndexedDB 离线缓存,撤销只针对本机操作 - 空间(标签)有独立地址,增删改通过接口同步到所有设备 - 口令登录(签名 Cookie 会话、登录限流、同源校验) - 图片上传到服务器,过大照片上传前自动压缩 - 历史版本(自动/手动保存、一键恢复)、跨空间搜索、在线设备 - 随机油画加载动画(Painting Loaders,本地收录 p5 / p5.brush) - 选中样式与拖拽性能优化:拖动结束后才同步位置 - Docker / docker-compose 部署,README 与 CLAUDE.md 更新
98 lines
4.3 KiB
Markdown
98 lines
4.3 KiB
Markdown
# 无界笺 Boundless
|
||
|
||
一张可以无限延伸的灵感平面,自部署在自己的服务器上。所有内容保存在服务器里,手机、平板、电脑打开的都是同一张画布;在一台设备上修改,其他打开着同一空间的设备会立刻看到变化。
|
||
|
||
## 功能
|
||
|
||
- 无限画布:平移、缩放、小地图、框选、对齐与均分
|
||
- 文字便笺与图片卡片:创建、编辑、移动、缩放、复制、换色、连线
|
||
- 多个空间(标签),每个空间有独立的地址 `/s/<空间 id>`
|
||
- **多端实时同步**:基于 Yjs CRDT,多台设备同时编辑也不会互相覆盖
|
||
- **断网可用**:离线时的修改保存在本机,联网后自动同步
|
||
- **撤销只针对本机**:不会把其他设备的修改一起撤掉
|
||
- **历史版本**:编辑时每 10 分钟自动存一个版本,也可以手动保存,一键恢复
|
||
- **全局搜索**:搜索当前空间并跳转,同时列出其他空间里的匹配卡片
|
||
- 在线设备:查看哪些设备在线、各自打开的是哪个空间
|
||
- 口令登录:单用户,修改口令后所有设备自动退出
|
||
- 图片上传到服务器;过大的照片会在上传前自动压缩
|
||
- 随机油画加载动画(见下方致谢)
|
||
|
||
## 技术栈
|
||
|
||
- 前端:Vue 3 + TypeScript、Vite 8、Vue Flow、Pinia、Tailwind CSS 4
|
||
- 同步:Yjs、Hocuspocus(WebSocket)、y-indexeddb(本机离线缓存)
|
||
- 服务端:Node.js(直接运行 TypeScript)、Hono、node:sqlite
|
||
- 测试:Vitest、Playwright
|
||
|
||
## 本地开发
|
||
|
||
需要 Node.js 22.18+ 或 24.12+,推荐 Node.js 24 LTS。
|
||
|
||
```sh
|
||
pnpm install
|
||
cp .env.example .env # 可选;不设置时开发口令为 boundless
|
||
pnpm dev # 同时启动服务端(3000)与 Vite(5173)
|
||
```
|
||
|
||
浏览器打开 <http://localhost:5173>。服务端数据默认存放在 `./data/`。
|
||
|
||
## 检查项目
|
||
|
||
```sh
|
||
pnpm build # 类型检查 + 构建
|
||
pnpm test:unit -- --run # 单元测试
|
||
pnpm exec playwright install chromium # 首次运行 e2e 前
|
||
pnpm exec playwright test --project=chromium # 端到端测试(会自动构建并启动服务端)
|
||
pnpm lint
|
||
```
|
||
|
||
## 部署(Docker)
|
||
|
||
```sh
|
||
echo "BOUNDLESS_PASSWORD=换成你的口令" > .env
|
||
docker compose up -d --build
|
||
```
|
||
|
||
服务监听 `127.0.0.1:3000`,数据保存在 `./data/`(SQLite 数据库、图片和会话密钥)。**备份整个 `data/` 目录即可完整备份。**
|
||
|
||
前面需要一个 HTTPS 反向代理,并且要支持 WebSocket。Caddy 最简单(自动申请证书,并会自动转发 WebSocket):
|
||
|
||
```caddyfile
|
||
board.example.com {
|
||
reverse_proxy 127.0.0.1:3000
|
||
}
|
||
```
|
||
|
||
Nginx 需要显式转发 WebSocket 和 Host:
|
||
|
||
```nginx
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 1d;
|
||
client_max_body_size 25m;
|
||
}
|
||
```
|
||
|
||
### 环境变量
|
||
|
||
| 变量 | 说明 | 默认值 |
|
||
| -------------------- | ------------------------------------------- | ------------------- |
|
||
| `BOUNDLESS_PASSWORD` | 登录口令(生产环境必填) | 开发时为 `boundless` |
|
||
| `BOUNDLESS_SECRET` | 会话签名密钥 | 自动生成并存入数据目录 |
|
||
| `DATA_DIR` | 数据目录 | `./data` |
|
||
| `PORT` / `HOST` | 监听端口与地址 | `3000` / `0.0.0.0` |
|
||
| `SESSION_DAYS` | 登录有效天数 | `90` |
|
||
| `MAX_IMAGE_MB` | 单张图片大小上限 | `20` |
|
||
|
||
不用 Docker 时:`pnpm install && pnpm build-only && BOUNDLESS_PASSWORD=... pnpm start`。
|
||
|
||
## 致谢
|
||
|
||
加载动画来自 [Painting Loaders](https://painterly.design-tools.workers.dev)(作者 [Sasha](https://x.com/aleksksaa)),12 幅画作的绘制代码原样收录在 `public/painterly/`,基于 [p5.js](https://p5js.org)(LGPL-2.1)与 [p5.brush](https://github.com/acamposuribe/p5.brush)(MIT)。
|