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>
87 lines
2.1 KiB
TypeScript
87 lines
2.1 KiB
TypeScript
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 }
|
|
}
|