import React, { useState, useRef, useEffect } from 'react' import ReactMarkdown from 'react-markdown' const QUICK_REACTIONS = ['๐Ÿ‘', 'โค๏ธ', '๐Ÿ”ฅ', '๐Ÿ˜‚', '๐Ÿ‘', '๐Ÿ˜ฎ'] /** * Individual message bubble with: * - Markdown rendering (no raw HTML allowed) * - Hover reaction picker + unreact on click * - Inline edit for own messages * - Soft-delete display */ export default function MessageBubble({ message, isMine, showAvatar, onReact, onUnreact, onEdit, onReport = null, seenText = null }) { const [showPicker, setShowPicker] = useState(false) const [editing, setEditing] = useState(false) const [editBody, setEditBody] = useState(message.body ?? '') const [savingEdit, setSavingEdit] = useState(false) const editRef = useRef(null) const isDeleted = !!message.deleted_at const isEdited = !!message.edited_at const username = message.sender?.username ?? 'Unknown' const time = formatTime(message.created_at) // Focus textarea when entering edit mode useEffect(() => { if (editing) { editRef.current?.focus() editRef.current?.setSelectionRange(editBody.length, editBody.length) } }, [editing]) const reactionGroups = groupReactions(message.reactions ?? []) const handleSaveEdit = async () => { const trimmed = editBody.trim() if (!trimmed || trimmed === message.body || savingEdit) return setSavingEdit(true) try { await onEdit(message.id, trimmed) setEditing(false) } finally { setSavingEdit(false) } } const handleEditKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSaveEdit() } if (e.key === 'Escape') { setEditing(false); setEditBody(message.body) } } return (
!isDeleted && !editing && setShowPicker(true)} onMouseLeave={() => setShowPicker(false)} > {/* Avatar */}
{username.charAt(0).toUpperCase()}
{/* Sender name & time */} {showAvatar && (
{username} {time}
)} {/* Bubble */}
{editing ? (