diff --git a/client/src/components/Anonymous.jsx b/client/src/components/Anonymous.jsx index f8673a83..4bc9fe6b 100644 --- a/client/src/components/Anonymous.jsx +++ b/client/src/components/Anonymous.jsx @@ -262,20 +262,19 @@ const Anonymous = ({ onChatClosed }) => { - handleClose()} className='sm:w-[200px]'> -
- Close Chat - Ctrl + Shift + X -
-
- - handleClose(true)} className='sm:w-[200px]'> -
- Find a new buddy - Ctrl + Alt + N -
-
-
+ handleClose()} className='sm:w-[200px]'> +
+ Close Chat + Ctrl + Shift + X +
+
+ handleClose(true)} className='sm:w-[200px]'> +
+ Find a new buddy + Ctrl + Alt + N +
+
+
{ isediting: false, messageID: null, }); - const [isQuoteReply, setIsQuoteReply] = useState(false); const [message, setMessage] = useState(''); - const [quoteMessage, setQuoteMessage] = useState(null); // use the id so we can track what message's previousMessage is open const [openPreviousMessages, setOpenPreviousMessages] = useState(null); const [badwordChoices, setBadwordChoices] = useState({}); - const { messages: state, addMessage, updateMessage, removeMessage, receiveMessage } = useChat(); + const { messages: state, addMessage, updateMessage, removeMessage, receiveMessage, startReply, currentReplyMessageId, cancelReply } = useChat(); const { authState, dispatchAuth } = useAuth(); const { logout } = useKindeAuth(); const socket = useContext(SocketContext); const { sendMessage, editMessage } = useChatUtils(socket); - const { getMessage, handleResend } = chatHelper(state, app) + const { getMessage, handleResend, scrollToMessage } = chatHelper(state, app) const inputRef = useRef(''); @@ -105,14 +105,15 @@ const Chat = () => { [state, app.currentChatId] ); - const doSend = async ({ senderId, room, message, time, containsBadword }) => { + const doSend = async ({ senderId, room, message, time, containsBadword, replyTo = null }) => { try { const sentMessage = await sendMessage({ senderId, message, time, chatId: room, - containsBadword + containsBadword, + replyTo }); addMessage({ @@ -122,7 +123,8 @@ const Chat = () => { message, time, status: 'pending', - containsBadword + containsBadword, + replyTo }); try { @@ -140,7 +142,8 @@ const Chat = () => { message, time, status: 'failed', - containsBadword + containsBadword, + replyTo }); } catch { logOut(); @@ -158,25 +161,12 @@ const Chat = () => { socket.emit(NEW_EVENT_TYPING, { chatId: app.currentChatId, isTyping: false }); const d = new Date(); - let message = inputRef.current.value.trim(); // Trim the message to remove the extra spaces - - if (!isQuoteReply) { - const cleanedText = message.replace(/>+/g, ''); - message = cleanedText; - } + const message = inputRef.current.value.trim(); // Trim the message to remove the extra spaces if (message === '' || senderId === undefined || senderId === '123456') { return; } - if (isQuoteReply && message.trim() === quoteMessage.trim()) { - return; - } - - setIsQuoteReply(false); - setQuoteMessage(null); - - if (editing.isediting === true) { try { const messageObject = getMessage(editing.messageID, state, app) @@ -200,7 +190,8 @@ const Chat = () => { room: app.currentChatId, message, time: d.getTime(), - containsBadword: badwords.check(message) + containsBadword: badwords.check(message), + replyTo: currentReplyMessageId }); } @@ -209,6 +200,7 @@ const Chat = () => { setMessage(''); inputRef.current.focus(); } + cancelReply() }; const handleTypingStatus = throttle((e) => { @@ -311,6 +303,10 @@ const Chat = () => { } }, [sortedMessages]); + useEffect(() => { + inputRef.current.focus() + }, [currentReplyMessageId]) + return (
@@ -323,89 +319,143 @@ const Chat = () => { className="h-[100%] md:max-h-full overflow-y-auto w-full scroll-smooth" > {sortedMessages.map( - ({ senderId: sender, id, message, time, status, isEdited, oldMessages, containsBadword, isRead }) => { + ({ senderId: sender, id, message, time, status, isEdited, oldMessages, containsBadword, isRead, replyTo }) => { const isSender = sender.toString() === senderId.toString(); message = decryptMessage(message) + // original message this message is a reply to + const repliedMessage = replyTo ? (() => { + const messageObj = getMessage(replyTo) + if (!messageObj) { + return null + } + + return { + ...messageObj, + message: decryptMessage(messageObj.message) + } + })() : null + + // is this message currently being replied? + const hasActiveReply = currentReplyMessageId === id return ( -
+
+ {replyTo && (
scrollToMessage(replyTo)} > - {containsBadword && !isSender && !badwordChoices[id] ? ( -
-

Your buddy is trying to send you a bad word

-
- showBadword(id)} className='text-sm cursor-pointer'>See - hideBadword(id)} className='text-red text-sm cursor-pointer'>Hide -
-
- ) - : - <> -
- {typeof message === 'string' ? ( - - ) : ( - badwordChoices[id] === 'hide' ? badwords.filter(message) : badwordChoices[id] === 'show' ? message : message - )} - - + {repliedMessage ? ( + typeof repliedMessage.message === 'string' ? ( +
+ ) : ( + repliedMessage.message + ) + ) : ( +

+ Original Message Deleted +

+ )} +
+
+ {sender.toString() === senderId.toString() ? ( + + ) : ( + + )} +
+
+ )} +
+
+ {containsBadword && !isSender && !badwordChoices[id] ? ( +
+

Your buddy is trying to send you a bad word

+
+ showBadword(id)} className='text-sm cursor-pointer'>See + hideBadword(id)} className='text-red text-sm cursor-pointer'>Hide +
-
+ ) + : + <>
- handleResend(id, doSend, state, app)} + {typeof message === 'string' ? ( + + ) : ( + badwordChoices[id] === 'hide' ? badwords.filter(message) : badwordChoices[id] === 'show' ? message : message + )} + +
- -
- - } +
+
+ handleResend(id, doSend, state, app)} + /> +
+ +
+ + } +
+
-
); } )} diff --git a/client/src/components/Chat/DropDownOption.jsx b/client/src/components/Chat/DropDownOption.jsx index 195a17ad..f84567b3 100644 --- a/client/src/components/Chat/DropDownOption.jsx +++ b/client/src/components/Chat/DropDownOption.jsx @@ -19,8 +19,7 @@ const DropDownOptions = ({ inputRef, cancelEdit, setEditing, - setIsQuoteReply, - setQuoteMessage + setReplyId }) => { const { app } = useApp(); const socket = useContext(SocketContext); @@ -50,23 +49,6 @@ const DropDownOptions = ({ setEditing({ isediting: true, messageID: id }); }; - const handleQuoteReply = (id) => { - inputRef.current.focus(); - - const { message } = getMessage(id, state, app); - if (message.includes('Warning Message')) { - cancelEdit(); - return; - } - - const quotedMessage = `> ${message} - -`; - inputRef.current.value = quotedMessage; - setIsQuoteReply(true); - setQuoteMessage(quotedMessage); - }; - const handleDelete = async (id) => { if (!messageExists(id)) { return; @@ -126,8 +108,8 @@ const DropDownOptions = ({ onClick={() => handleCopyToClipBoard(id, state, app)}> Copy - handleQuoteReply(id)}> - Quote Reply + setReplyId(id)}> + Reply handleDelete(id)}>Delete @@ -145,8 +127,8 @@ const DropDownOptions = ({ onClick={() => handleCopyToClipBoard(id, state, app)}> Copy - handleQuoteReply(id)}> - Quote Reply + setReplyId(id)}> + Reply } else { @@ -162,6 +144,5 @@ DropDownOptions.propTypes = { isSender: PropTypes.bool.isRequired, cancelEdit: PropTypes.func.isRequired, setEditing: PropTypes.func.isRequired, - setIsQuoteReply: PropTypes.func.isRequired, - setQuoteMessage: PropTypes.func.isRequired + setReplyId: PropTypes.func.isRequired }; diff --git a/client/src/components/Chat/MessageInput.jsx b/client/src/components/Chat/MessageInput.jsx index c84d7071..a6c434a1 100644 --- a/client/src/components/Chat/MessageInput.jsx +++ b/client/src/components/Chat/MessageInput.jsx @@ -2,12 +2,15 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { ImCancelCircle } from 'react-icons/im'; import { IoSend } from 'react-icons/io5'; +import { IoIosArrowDropright } from 'react-icons/io' import EmojiPicker from '../EmojiPicker'; import useKeyPress, { ShortcutFlags } from 'src/hooks/useKeyPress'; import { socket } from 'context/Context'; import { NEW_EVENT_SEND_FAILED } from '../../../../constants.json'; +import { useChat } from 'src/context/ChatContext'; +import { useAuth } from 'src/context/AuthContext'; const MessageInput = ({ inputRef, @@ -18,7 +21,10 @@ const MessageInput = ({ cancelEdit, handleSubmit, }) => { - const [isTextAreaDisabled, setTextAreaDisabled] = useState(false); + const [isTextAreaDisabled, setTextAreaDisabled] = useState(false); + const { currentReplyMessage, currentReplyMessageId, scrollToMessage, cancelReply } = useChat() + const { authState } = useAuth() + const senderId = authState.loginId // Define the limitMessageHandler function function limitMessageHandler() { @@ -54,30 +60,55 @@ const MessageInput = ({ return ( <> -
-
-