feat(web): add Vue workspace with file tree, tabs, autosave and live sync
- Login and first-run setup screens - File tree with inline create/rename, context menu, drag-and-drop move and trash with undo; all operations update the UI first and roll back on failure - Tabs keep a CodeMirror state each; edits autosave after 500 ms with If-Match revisions, unsent edits persist in IndexedDB, and conflicts or remote deletions surface as banners with explicit choices - WebSocket events sync external edits, moves and deletions into open tabs; reconnects trigger a full resync - Pasted or dropped images show instantly and upload to note.assets/ - Responsive layout with a swipe-in sidebar drawer on phones - Switch to TypeScript 6: vue-tsc does not support TypeScript 7 yet
This commit is contained in:
1 parent
c4d2bd4636
commit
02d904056c
28 files changed
+2231
-398
No files matched your search
@@ -0,0 +1,48 @@
|
||||
// 订阅服务端的仓库变更事件;断线后指数退避重连,重连成功时触发全量同步
|
||||
|
||||
export interface VaultEvent {
|
||||
type: 'tree' | 'changed' | 'deleted' | 'moved'
|
||||
path?: string
|
||||
from?: string
|
||||
rev?: string
|
||||
}
|
||||
|
||||
export function connectEvents(handlers: { onEvents: (evs: VaultEvent[]) => void; onResync: () => void }): () => void {
|
||||
let ws: WebSocket | undefined
|
||||
let delay = 1000
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let stopped = false
|
||||
let connectedBefore = false
|
||||
|
||||
const connect = () => {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
ws = new WebSocket(`${proto}//${location.host}/api/v1/events`)
|
||||
ws.onopen = () => {
|
||||
delay = 1000
|
||||
// 首次连接前的状态由初始化加载,之后每次重连都可能错过事件
|
||||
if (connectedBefore) handlers.onResync()
|
||||
connectedBefore = true
|
||||
}
|
||||
ws.onmessage = (e) => handlers.onEvents(JSON.parse(e.data))
|
||||
ws.onclose = () => {
|
||||
if (stopped) return
|
||||
timer = setTimeout(connect, delay)
|
||||
delay = Math.min(delay * 2, 30000)
|
||||
}
|
||||
}
|
||||
connect()
|
||||
// 页面从后台恢复时立即重连,不等退避计时
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState === 'visible' && ws?.readyState === WebSocket.CLOSED) {
|
||||
clearTimeout(timer)
|
||||
connect()
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', onVisible)
|
||||
return () => {
|
||||
stopped = true
|
||||
clearTimeout(timer)
|
||||
document.removeEventListener('visibilitychange', onVisible)
|
||||
ws?.close()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user