/** * Change Highlighting Component * Displays visual indicators for recent changes in the editor */ import { Component, createEffect, createSignal, onMount, For } from 'solid-js'; import { Doc, Text } from 'yjs'; import { ChangeTracker, DocumentChange } from '../../lib/collaboration/change-tracker'; export interface ChangeHighlight { id: string; position: number; length: number; type: 'insert' | 'delete' | 'format' | 'move'; userId: string; userName: string; timestamp: Date; color: string; accepted: boolean; } export interface ChangeHighlightingProps { doc: Doc; changeTracker: ChangeTracker; userId: string; showAccepted?: boolean; showRejected?: boolean; highlightDurationMs?: number; } export const ChangeHighlighting: Component = (props) => { const [highlights, setHighlights] = createSignal([]); const [showHighlights, setShowHighlights] = createSignal(true); const showAccepted = props.showAccepted ?? true; const showRejected = props.showRejected ?? false; const highlightDurationMs = props.highlightDurationMs ?? 30000; // 30 seconds default // Color map for users const userColors = new Map(); const colors = [ 'rgba(239, 68, 68, 0.3)', // red 'rgba(59, 130, 246, 0.3)', // blue 'rgba(34, 197, 94, 0.3)', // green 'rgba(234, 179, 8, 0.3)', // yellow 'rgba(168, 85, 247, 0.3)', // purple 'rgba(236, 72, 153, 0.3)', // pink ]; const getUserColor = (userId: string): string => { if (!userColors.has(userId)) { let hash = 0; for (let i = 0; i < userId.length; i++) { hash = userId.charCodeAt(i) + ((hash << 5) - hash); } userColors.set(userId, colors[Math.abs(hash) % colors.length]!); } return userColors.get(userId)!; }; onMount(() => { // Subscribe to change events props.changeTracker.onChange((change) => { const highlight: ChangeHighlight = { id: change.id, position: change.position, length: change.length, type: change.type, userId: change.userId, userName: change.userName, timestamp: change.timestamp, color: getUserColor(change.userId), accepted: change.accepted, }; setHighlights((prev) => [...prev, highlight]); // Auto-remove highlight after duration setTimeout(() => { setHighlights((prev) => prev.filter((h) => h.id !== change.id)); }, highlightDurationMs); }); }); // Filter highlights based on acceptance status const filteredHighlights = () => { return highlights().filter((h) => { if (h.accepted && !showAccepted) return false; if (!h.accepted && !showRejected) return false; return true; }); }; // Get highlight style for a position const getHighlightStyle = (position: number): string => { const highlight = filteredHighlights().find( (h) => position >= h.position && position < h.position + h.length ); if (highlight) { return `background-color: ${highlight.color};`; } return ''; }; // Toggle highlight visibility const toggleHighlights = () => { setShowHighlights(!showHighlights()); }; return (
{filteredHighlights().length} active changes
{showHighlights() && (
{(highlight) => (
)}
)}
); }; export default ChangeHighlighting;