| /** | |
| * Collab user identity: shared types, color hashing and the localStorage | |
| * fallback used when the viewer is not authenticated yet. | |
| */ | |
| export interface CollabUser { | |
| name: string; | |
| color: string; | |
| avatarUrl?: string; | |
| } | |
| const COLORS = [ | |
| "#958DF1", "#F98181", "#FBBC88", "#FAF594", | |
| "#70CFF8", "#94FADB", "#B9F18D", "#C4B5FD", | |
| ]; | |
| /** Deterministic color from a name, used for cursors and author chips. */ | |
| export function colorFromName(name: string): string { | |
| let hash = 0; | |
| for (let i = 0; i < name.length; i++) hash = (hash * 31 + name.charCodeAt(i)) | 0; | |
| return COLORS[Math.abs(hash) % COLORS.length]; | |
| } | |
| const STORAGE_KEY = "collab-editor:fallback-user"; | |
| const FALLBACK_NAMES = ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Heidi"]; | |
| /** | |
| * Pick (and persist) a random pseudonymous user when the session is anonymous. | |
| * Stored in localStorage so a refresh keeps the same name/color. | |
| */ | |
| export function stableFallbackUser(): CollabUser { | |
| const stored = localStorage.getItem(STORAGE_KEY); | |
| if (stored) { | |
| try { return JSON.parse(stored); } catch { /* fall through */ } | |
| } | |
| const name = FALLBACK_NAMES[Math.floor(Math.random() * FALLBACK_NAMES.length)]; | |
| const user = { name, color: colorFromName(name) }; | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(user)); | |
| return user; | |
| } | |