feat: add auth, real-time collaboration, sharing, font control, and UI fixes
Phase 1 - Bug fixes:
- Fix color labels not showing on active line in format preview
- Replace eye emoji with SVG icon showing clear preview/raw state
- Replace // button with comment icon + better tooltip
- Fix ThemePicker accent colors when using system theme
Phase 2 - Font:
- Load JetBrains Mono via Google Fonts with offline fallback
- Add font size control (A-/A+) with keyboard shortcuts
- Persist font size preference in localStorage
Phase 3 - Auth:
- Supabase-based email/password authentication
- Device session management with configurable password renewal TTL
- AuthModal, UserMenu, SecuritySettings components
Phase 4 - Cloud sync:
- Document metadata sync to Supabase PostgreSQL
- Legacy localStorage migration on first login
- IndexedDB persistence via y-indexeddb
Phase 5 - Real-time collaboration:
- Y.js CRDT integration with CodeMirror 6
- Hocuspocus WebSocket server with JWT auth
- Collaborative cursor awareness
- CollabIndicator component
Phase 6 - Sharing:
- Share links with view/edit permissions
- ShareDialog component with copy-to-clipboard
- Minimal client-side router for /s/{token} URLs
Infrastructure:
- Docker Compose with PostgreSQL, GoTrue, PostgREST, Hocuspocus
- Nginx reverse proxy for all backend services
- SQL migrations with RLS policies
- Production-ready Dockerfile with build args
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
86
calcpad-web/src/collab/useWebSocketProvider.ts
Normal file
86
calcpad-web/src/collab/useWebSocketProvider.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useRef, useEffect, useState } from 'react'
|
||||
import * as Y from 'yjs'
|
||||
import { WebsocketProvider } from 'y-websocket'
|
||||
import type { Awareness } from 'y-protocols/awareness'
|
||||
|
||||
const COLLAB_WS_URL = import.meta.env.VITE_COLLAB_WS_URL as string || 'ws://localhost:4000'
|
||||
|
||||
interface UseWebSocketProviderOptions {
|
||||
ydoc: Y.Doc | null
|
||||
roomName: string | null
|
||||
token: string | null
|
||||
userName: string
|
||||
userColor: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a Y.js WebSocket provider for real-time collaboration.
|
||||
* Connects to the Hocuspocus server.
|
||||
*/
|
||||
export function useWebSocketProvider({
|
||||
ydoc,
|
||||
roomName,
|
||||
token,
|
||||
userName,
|
||||
userColor,
|
||||
}: UseWebSocketProviderOptions) {
|
||||
const providerRef = useRef<WebsocketProvider | null>(null)
|
||||
const [awareness, setAwareness] = useState<Awareness | null>(null)
|
||||
const [connected, setConnected] = useState(false)
|
||||
const [peerCount, setPeerCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!ydoc || !roomName || !token) {
|
||||
setAwareness(null)
|
||||
setConnected(false)
|
||||
setPeerCount(0)
|
||||
return
|
||||
}
|
||||
|
||||
const provider = new WebsocketProvider(
|
||||
COLLAB_WS_URL,
|
||||
roomName,
|
||||
ydoc,
|
||||
{
|
||||
params: { token },
|
||||
connect: true,
|
||||
},
|
||||
)
|
||||
|
||||
providerRef.current = provider
|
||||
|
||||
// Set local user awareness
|
||||
provider.awareness.setLocalStateField('user', {
|
||||
name: userName,
|
||||
color: userColor,
|
||||
})
|
||||
|
||||
setAwareness(provider.awareness)
|
||||
|
||||
// Track connection status
|
||||
provider.on('status', (event: { status: string }) => {
|
||||
setConnected(event.status === 'connected')
|
||||
})
|
||||
|
||||
// Track peer count
|
||||
const updatePeerCount = () => {
|
||||
const states = provider.awareness.getStates()
|
||||
setPeerCount(Math.max(0, states.size - 1)) // Exclude self
|
||||
}
|
||||
|
||||
provider.awareness.on('change', updatePeerCount)
|
||||
updatePeerCount()
|
||||
|
||||
return () => {
|
||||
provider.awareness.off('change', updatePeerCount)
|
||||
provider.disconnect()
|
||||
provider.destroy()
|
||||
providerRef.current = null
|
||||
setAwareness(null)
|
||||
setConnected(false)
|
||||
setPeerCount(0)
|
||||
}
|
||||
}, [ydoc, roomName, token, userName, userColor])
|
||||
|
||||
return { awareness, connected, peerCount }
|
||||
}
|
||||
Reference in New Issue
Block a user