"use client"; import { useState, useRef, useEffect } from 'react'; import { Button } from './ui/button'; import { Paperclip, Send, Smile, Mic, X } from 'lucide-react'; import { CSSTransition } from 'react-transition-group'; import { VoiceRecorder } from './voice-recorder'; import type { ReplyTo, Message, Group } from '@/lib/types'; import { useSettings } from '@/contexts/settings-context'; import { useAuth } from '@/contexts/auth-context'; import { useChatUtils } from '@/contexts/chat-utils-context'; import { useAppContext } from '@/contexts/app-context'; import { Skeleton } from './ui/skeleton'; // Helper function to extract plain text and emoji data from the contentEditable div const getMessageFromDiv = (div: HTMLDivElement): string => { let message = ''; div.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE) { message += node.textContent; } else if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === 'IMG') { const imgElement = node as HTMLImageElement; // Use a specific character or sequence to represent the emoji, // which can be parsed on the receiving end. // Here, we use the 'alt' attribute which should contain the native emoji character. message += imgElement.alt; } }); return message; }; interface MessageInputProps { chatId: string | null; disabled?: boolean; lastMessage: Message | null; isGroupChat?: boolean; currentGroup?: Group; onGetSuggestedReplies: (message: Message) => Promise; sendTextMessage: (text: string, replyTo?: ReplyTo | null) => void; onSelectMedia: (file: File) => void; onSendAudio: (data: { file: File, duration: number }) => void; onToggleEmojiPicker: () => void; editorRef: React.RefObject; } const QuotedMessagePreview = ({ replyTo, onCancel }: { replyTo: ReplyTo | null, onCancel: () => void }) => { const nodeRef = useRef(null); return (
{replyTo && ( <>

{replyTo.displayName}

{replyTo.text || (replyTo.imageKey ? 'Image' : replyTo.videoKey ? 'Video' : 'Voice Message')}

)}
); }; export function MessageInput({ sendTextMessage, onSelectMedia, onSendAudio, chatId, disabled, lastMessage, onGetSuggestedReplies, isGroupChat, currentGroup, onToggleEmojiPicker, editorRef, }: MessageInputProps) { const { currentUser } = useAuth(); const { setUserTyping } = useChatUtils(); const { replyTo, setReplyTo } = useAppContext(); const { playSound, t } = useSettings(); const [isRecording, setIsRecording] = useState(false); const [suggestedReplies, setSuggestedReplies] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); const typingTimeoutRef = useRef(null); const handleSend = () => { if (!editorRef.current) return; const message = getMessageFromDiv(editorRef.current); if (message.trim()) { playSound('send'); sendTextMessage(message, replyTo); editorRef.current.innerHTML = ''; setReplyTo(null); } }; useEffect(() => { const fetchSuggestions = async () => { if (lastMessage && lastMessage.sender !== currentUser?.uid && lastMessage.text) { setShowSuggestions(true); setSuggestedReplies([]); // Clear old suggestions immediately const replies = await onGetSuggestedReplies(lastMessage); setSuggestedReplies(replies); } else { setShowSuggestions(false); setSuggestedReplies([]); } }; // Only fetch suggestions if the last message changes if (lastMessage?.id) { fetchSuggestions(); } }, [lastMessage?.id, lastMessage?.sender, lastMessage?.text, currentUser?.uid, onGetSuggestedReplies]); const handleInputChange = (e: React.FormEvent) => { if (chatId) { if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current); else setUserTyping(chatId, true); typingTimeoutRef.current = setTimeout(() => { setUserTyping(chatId, false); typingTimeoutRef.current = null; }, 2000); } }; const handleSendSuggestion = (reply: string) => { sendTextMessage(reply, null); setShowSuggestions(false); } const handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); handleSend(); } }; const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { onSelectMedia(file); } // Reset the input value to allow selecting the same file again e.target.value = ''; }; if (disabled) { return (
{t('messagingDisabled')}
); } const sendingMode = currentGroup?.info?.settings?.sendingMode || 'everyone'; const isMuted = isGroupChat && currentGroup?.info.mutedMembers?.[currentUser?.uid || '']; const canSend = !isGroupChat || ( (sendingMode === 'everyone' && !isMuted) || (sendingMode === 'admins' && currentGroup?.admins[currentUser?.uid || '']) || (sendingMode === 'owner' && currentGroup?.info.createdBy === currentUser?.uid) ); if (!canSend) { return (

{isMuted ? t('youAreMuted') : t('adminsCanSend')}

); } if (isRecording) { return setIsRecording(false)} onSend={onSendAudio} />; } return (
setReplyTo(null)} /> {showSuggestions && (
{suggestedReplies.length > 0 ? ( suggestedReplies.map((reply, i) => ( )) ) : ( Array.from({ length: 3 }).map((_, i) => ) )}
)}
{editorRef.current && editorRef.current.textContent?.trim() ? ( ) : ( )}
); }