自部署多端同步:服务端、实时协作、口令登录与油画加载动画

- 新增 Node 服务端(Hono + Hocuspocus + node:sqlite),数据保存在服务器
- 基于 Yjs 的多端实时同步,本机 IndexedDB 离线缓存,撤销只针对本机操作
- 空间(标签)有独立地址,增删改通过接口同步到所有设备
- 口令登录(签名 Cookie 会话、登录限流、同源校验)
- 图片上传到服务器,过大照片上传前自动压缩
- 历史版本(自动/手动保存、一键恢复)、跨空间搜索、在线设备
- 随机油画加载动画(Painting Loaders,本地收录 p5 / p5.brush)
- 选中样式与拖拽性能优化:拖动结束后才同步位置
- Docker / docker-compose 部署,README 与 CLAUDE.md 更新
This commit is contained in:
吴璨
2026-10-21 18:09:03 +08:00
parent c36123eb82
commit fad9760265
64 changed files with 6656 additions and 1103 deletions
+170 -8
View File
@@ -8,6 +8,18 @@ importers:
.:
dependencies:
'@hocuspocus/extension-database':
specifier: 4.7.1
version: 4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)
'@hocuspocus/provider':
specifier: 4.7.1
version: 4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)
'@hocuspocus/server':
specifier: 4.7.1
version: 4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)
'@hono/node-server':
specifier: 2.1.5
version: 2.1.5(hono@4.13.13)
'@lucide/vue':
specifier: ^1.31.0
version: 1.31.0(vue@3.5.41(typescript@6.0.3))
@@ -29,9 +41,12 @@ importers:
'@vueuse/core':
specifier: ^14.4.0
version: 14.4.0(vue@3.5.41(typescript@6.0.3))
dexie:
specifier: ^4.4.4
version: 4.4.4
crossws:
specifier: 0.4.15
version: 0.4.15
hono:
specifier: 4.13.13
version: 4.13.13
pinia:
specifier: ^4.0.2
version: 4.0.2(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3))
@@ -41,6 +56,12 @@ importers:
vue-router:
specifier: ^5.2.0
version: 5.2.0(@vue/compiler-sfc@3.5.41)(pinia@4.0.2(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.3)(vite@8.2.1(@types/node@24.13.3)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))
y-indexeddb:
specifier: 9.0.12
version: 9.0.12(yjs@13.6.33)
yjs:
specifier: 13.6.33
version: 13.6.33
devDependencies:
'@playwright/test':
specifier: ^1.61.1
@@ -381,6 +402,33 @@ packages:
'@noble/hashes':
optional: true
'@hocuspocus/common@4.7.1':
resolution: {integrity: sha512-+N4t8AdBIWBUK+yHsuBvCcBm96eV2E3quIewma9+TYomqVMXhrovAf3dA6XFbYHRM7ggtXklHt2mFqTcpiMdAQ==}
'@hocuspocus/extension-database@4.7.1':
resolution: {integrity: sha512-9qpwJAY6QHu/++KNpz7wWHEmo/4zxEYFR3G5UD7Td5crnGT8DcZ5fM4LsZ9Uy3KCFYAdCnidnsSXvjy+7gcWRQ==}
peerDependencies:
yjs: ^13.6.8
'@hocuspocus/provider@4.7.1':
resolution: {integrity: sha512-znulqyCwwuhq86aFmTF6izOomwRWlZGReCAf+SZpLgpvdfvBQJzjoj9HdtuXXfL9wkBM2Ydt86kaMs2rjaicRA==}
peerDependencies:
y-protocols: ^1.0.6
yjs: ^13.6.8
'@hocuspocus/server@4.7.1':
resolution: {integrity: sha512-f2TWNi5rFnju1ttOesxkR1kPIukFmMClHY4pAx0RlIBMzjAoUVlEgilETIAQlrwmw1UMxsy31fse0tmBzYEpwQ==}
engines: {node: '>=22'}
peerDependencies:
y-protocols: ^1.0.6
yjs: ^13.6.8
'@hono/node-server@2.1.5':
resolution: {integrity: sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==}
engines: {node: '>=20'}
peerDependencies:
hono: ^4 || ^5.0.0-0
'@humanfs/core@0.19.2':
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
engines: {node: '>=18.18.0'}
@@ -441,6 +489,9 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@lifeomic/attempt@3.1.0':
resolution: {integrity: sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==}
'@lucide/vue@1.31.0':
resolution: {integrity: sha512-NtjEHhcAa7umPh40wrRmlESJqNGdnpc7LziBKFnW3fmlrW0g2xMCDpdWU7WeEHRSl3xOpxbqiFTLKxoK3CoVCg==}
peerDependencies:
@@ -1148,6 +1199,9 @@ packages:
resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==}
engines: {node: '>=20.19.0'}
async-mutex@0.5.0:
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
balanced-match@1.0.0:
resolution: {integrity: sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==}
@@ -1234,6 +1288,14 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
crossws@0.4.15:
resolution: {integrity: sha512-NsUOpDZosz7nyI+qEIl05yaiXOWaXH2oTeM+XBWtiSWZtb5rzcIsl1AST/HzM2jzUn76eFuyrkn83E6iSBRUvw==}
peerDependencies:
srvx: '>=0.11.5'
peerDependenciesMeta:
srvx:
optional: true
css-tree@3.2.1:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -1319,9 +1381,6 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dexie@4.4.4:
resolution: {integrity: sha512-jIwsYI8Os2hgnqc6O49YwFDKGc5v5QjGx0wPVp543ip1F53VFAKMLthV2pQosQcVTv3eAskTWYspOx195PM0FQ==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -1541,6 +1600,10 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
hono@4.13.13:
resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==}
engines: {node: '>=16.9.0'}
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
@@ -1619,6 +1682,9 @@ packages:
resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==}
engines: {node: '>=20'}
isomorphic.js@0.2.5:
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
jackspeak@3.1.2:
resolution: {integrity: sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==}
engines: {node: '>=14'}
@@ -1673,6 +1739,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
@@ -1680,6 +1750,11 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lib0@0.2.119:
resolution: {integrity: sha512-vh+TiejVy8Xm+hCdnRfn9BthGNvNUZdEXltNjVZL3TzRIKP3s1lnQh8pWd/Q0wCgY0FPAGVVjFF4ZiHUV3LFgA==}
engines: {node: '>=16'}
hasBin: true
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -2235,6 +2310,9 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -2547,6 +2625,18 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
y-indexeddb@9.0.12:
resolution: {integrity: sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
peerDependencies:
yjs: ^13.0.0
y-protocols@1.0.7:
resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
peerDependencies:
yjs: ^13.0.0
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -2555,6 +2645,10 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yjs@13.6.33:
resolution: {integrity: sha512-q/UYvr1gOk4XII+Cu1CW2dINjYqJJFifTzogEaIARznrvX7uOlHsdTEXcnOBMuIsrCGEEKdNxi74kyMLZlKVfw==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
snapshots:
'@asamuzakjp/css-color@5.1.11':
@@ -2850,6 +2944,42 @@ snapshots:
'@exodus/bytes@1.15.1': {}
'@hocuspocus/common@4.7.1':
dependencies:
lib0: 0.2.119
'@hocuspocus/extension-database@4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)':
dependencies:
'@hocuspocus/server': 4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)
yjs: 13.6.33
transitivePeerDependencies:
- srvx
- y-protocols
'@hocuspocus/provider@4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)':
dependencies:
'@hocuspocus/common': 4.7.1
'@lifeomic/attempt': 3.1.0
lib0: 0.2.119
y-protocols: 1.0.7(yjs@13.6.33)
yjs: 13.6.33
'@hocuspocus/server@4.7.1(y-protocols@1.0.7(yjs@13.6.33))(yjs@13.6.33)':
dependencies:
'@hocuspocus/common': 4.7.1
async-mutex: 0.5.0
crossws: 0.4.15
kleur: 4.1.5
lib0: 0.2.119
y-protocols: 1.0.7(yjs@13.6.33)
yjs: 13.6.33
transitivePeerDependencies:
- srvx
'@hono/node-server@2.1.5(hono@4.13.13)':
dependencies:
hono: 4.13.13
'@humanfs/core@0.19.2':
dependencies:
'@humanfs/types': 0.15.0
@@ -2910,6 +3040,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.14
'@lifeomic/attempt@3.1.0': {}
'@lucide/vue@1.31.0(vue@3.5.41(typescript@6.0.3))':
dependencies:
vue: 3.5.41(typescript@6.0.3)
@@ -3559,6 +3691,10 @@ snapshots:
'@babel/types': 7.29.8
ast-kit: 2.2.0
async-mutex@0.5.0:
dependencies:
tslib: 2.8.1
balanced-match@1.0.0: {}
balanced-match@4.0.4: {}
@@ -3634,6 +3770,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.1
crossws@0.4.15: {}
css-tree@3.2.1:
dependencies:
mdn-data: 2.27.1
@@ -3705,8 +3843,6 @@ snapshots:
detect-libc@2.1.2: {}
dexie@4.4.4: {}
eastasianwidth@0.2.0: {}
editorconfig@1.0.7:
@@ -3929,6 +4065,8 @@ snapshots:
graceful-fs@4.2.11: {}
hono@4.13.13: {}
hookable@5.5.3: {}
html-encoding-sniffer@6.0.0:
@@ -3982,6 +4120,8 @@ snapshots:
isexe@4.0.0: {}
isomorphic.js@0.2.5: {}
jackspeak@3.1.2:
dependencies:
'@isaacs/cliui': 8.0.2
@@ -4043,6 +4183,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
kleur@4.1.5: {}
kolorist@1.8.0: {}
levn@0.4.1:
@@ -4050,6 +4192,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lib0@0.2.119:
dependencies:
isomorphic.js: 0.2.5
lightningcss-android-arm64@1.32.0:
optional: true
@@ -4523,6 +4669,8 @@ snapshots:
dependencies:
typescript: 6.0.3
tslib@2.8.1: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -4796,6 +4944,20 @@ snapshots:
xmlchars@2.2.0: {}
y-indexeddb@9.0.12(yjs@13.6.33):
dependencies:
lib0: 0.2.119
yjs: 13.6.33
y-protocols@1.0.7(yjs@13.6.33):
dependencies:
lib0: 0.2.119
yjs: 13.6.33
yallist@3.1.1: {}
yaml@2.9.0: {}
yjs@13.6.33:
dependencies:
lib0: 0.2.119