diff --git a/src/App.tsx b/src/App.tsx index 3872eaa..b43d357 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -129,7 +129,7 @@ const defaultValues: MyContextInterface = { message: "", }, }; -export let isMobile = false +export let isMobile = true const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; diff --git a/src/background.ts b/src/background.ts index 86b1f11..053bb0e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1622,17 +1622,22 @@ async function sendChatDirect({ ...(otherData || {}) }; const messageStringified = JSON.stringify(finalJson); - const tx = await createTransaction(18, keyPair, { + console.log('chatReferencefinal', chatReference) + const txBody = { timestamp: Date.now(), recipient: recipientAddress, recipientPublicKey: recipientPublicKey, - hasChatReference: 0, + hasChatReference: chatReference ? 1 : 0, message: messageStringified, lastReference: reference, proofOfWorkNonce: 0, isEncrypted: 1, isText: 1, - }); + } + if(chatReference){ + txBody['chatReference'] = chatReference + } + const tx = await createTransaction(18, keyPair, txBody); // if (!hasEnoughBalance) { // throw new Error("Must have at least 4 QORT to send a chat message"); @@ -4000,7 +4005,7 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { address, otherData } = request.payload; - + console.log('chatReferencebg', chatReference) sendChatDirect({ directTo, chatReference, diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 5341fd9..1fe05ae 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -19,6 +19,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ShortUniqueId from "short-unique-id"; import { ReturnIcon } from '../../assets/Icons/ReturnIcon'; import { ExitIcon } from '../../assets/Icons/ExitIcon'; +import { MessageItem, ReplyPreview } from './MessageItem'; const uid = new ShortUniqueId({ length: 5 }); @@ -41,12 +42,12 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi const socketRef = useRef(null); const timeoutIdRef = useRef(null); const groupSocketTimeoutRef = useRef(null); - + const [replyMessage, setReplyMessage] = useState(null) const setEditorRef = (editorInstance) => { editorRef.current = editorInstance; }; const publicKeyOfRecipientRef = useRef(null) - + console.log({messages}) const getPublicKeyFunc = async (address)=> { try { const publicKey = await getPublicKey(address) @@ -219,6 +220,7 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi const sendChatDirect = async ({ chatReference = undefined, messageText, otherData}: any, address, publicKeyOfRecipient, isNewChatVar)=> { try { + console.log('chatReferencedirect', chatReference) const directTo = isNewChatVar ? directToValue : address if(!directTo) return @@ -282,6 +284,8 @@ const clearEditorContent = () => { const sendMessage = async ()=> { try { + + if(+balance < 4) throw new Error('You need at least 4 QORT to send a message') if(isSending) return if (editorRef.current) { @@ -297,12 +301,20 @@ const clearEditorContent = () => { await sendChatDirect({ messageText: htmlContent}, null, null, true) return } + let repliedTo = replyMessage?.signature + + if (replyMessage?.chatReference) { + repliedTo = replyMessage?.chatReference + } const otherData = { - specialId: uid.rnd() + specialId: uid.rnd(), + repliedTo } const sendMessageFunc = async () => { - await sendChatDirect({ messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false) + await sendChatDirect({ chatReference: undefined, messageText: htmlContent, otherData}, selectedDirect?.address, publicKeyOfRecipient, false) }; + + // Add the function to the queue const messageObj = { @@ -321,6 +333,7 @@ const clearEditorContent = () => { executeEvent("sent-new-message-group", {}) }, 150); clearEditorContent() + setReplyMessage(null) } // send chat message } catch (error) { @@ -337,6 +350,9 @@ const clearEditorContent = () => { } } + const onReply = useCallback((message)=> { + setReplyMessage(message) + }, []) return ( @@ -444,7 +460,7 @@ const clearEditorContent = () => { )} - +
{ flexGrow: isMobile && 1, overflow: !isMobile && "auto", }}> + {replyMessage && ( + + + { + setReplyMessage(null) + }} + > + + + + )}
diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 97c06ac..2ffad67 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -15,8 +15,10 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar' import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes' import { useMessageQueue } from '../../MessageQueueContext' import { executeEvent } from '../../utils/events' -import { Box } from '@mui/material' +import { Box, ButtonBase } from '@mui/material' import ShortUniqueId from "short-unique-id"; +import { ReplyPreview } from './MessageItem' +import { ExitIcon } from '../../assets/Icons/ExitIcon' const uid = new ShortUniqueId({ length: 5 }); @@ -34,6 +36,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, const [infoSnack, setInfoSnack] = React.useState(null); const hasInitialized = useRef(false) const [isFocusedParent, setIsFocusedParent] = useState(false); + const [replyMessage, setReplyMessage] = useState(null) const hasInitializedWebsocket = useRef(false) const socketRef = useRef(null); // WebSocket reference @@ -111,6 +114,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, ...item, id: item.signature, text: item?.decryptedData?.message || "", + repliedTo: item?.decryptedData?.repliedTo, unread: item?.sender === myAddress ? false : true } } ) @@ -121,6 +125,7 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, ...item, id: item.signature, text: item?.decryptedData?.message || "", + repliedTo: item?.decryptedData?.repliedTo, unread: false } } ) @@ -305,8 +310,15 @@ const clearEditorContent = () => { setIsSending(true) const message = htmlContent const secretKeyObject = await getSecretKey(false, true) + + let repliedTo = replyMessage?.signature + + if (replyMessage?.chatReference) { + repliedTo = replyMessage?.chatReference + } const otherData = { - specialId: uid.rnd() + specialId: uid.rnd(), + repliedTo } const objectMessage = { message, @@ -338,6 +350,7 @@ const clearEditorContent = () => { executeEvent("sent-new-message-group", {}) }, 150); clearEditorContent() + setReplyMessage(null) } // send chat message } catch (error) { @@ -362,6 +375,9 @@ const clearEditorContent = () => { } }, [hide]); + const onReply = useCallback((message)=> { + setReplyMessage(message) + }, []) return (
{ left: hide && '-100000px', }}> - +
{ flexGrow: isMobile && 1, overflow: !isMobile && "auto", }}> + {replyMessage && ( + + + { + setReplyMessage(null) + }} + > + + + + )} +
diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 73b35f4..4a4e4f4 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -8,7 +8,7 @@ import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; // defaultHeight: 50, // }); -export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId }) => { +export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onReply }) => { const hasLoadedInitialRef = useRef(false); const listRef = useRef(); @@ -18,7 +18,7 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId }) = fixedWidth: true, defaultHeight: 50, }), [chatId]); // Recreate cache when chatId changes - + console.log('messages2', messages) useEffect(() => { cache.clearAll(); }, []); @@ -68,6 +68,10 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId }) = } }; + const scrollToItem = useCallback((index) => { + listRef.current.scrollToRow(index); // This scrolls to the specific index + }, []); + const recomputeListHeights = () => { if (listRef.current) { listRef.current.recomputeRowHeights(); @@ -93,7 +97,18 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId }) = let message = messages[index]; const isLargeMessage = message.text?.length > 200; // Adjust based on your message size threshold + // const reply = message?.repliedTo ? messages.find((msg)=> msg?.signature === message?.repliedTo) : undefined + let replyIndex = messages.findIndex((msg)=> msg?.signature === message?.repliedTo) + let reply + if(message?.repliedTo && replyIndex !== -1){ + reply = messages[replyIndex] + } if(message?.message && message?.groupDirectId){ + replyIndex = messages.findIndex((msg)=> msg?.signature === message?.message?.repliedTo) + reply + if(message?.message?.repliedTo && replyIndex !== -1){ + reply = messages[replyIndex] + } message = { ...(message?.message || {}), isTemp: true, @@ -130,6 +145,10 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId }) = onSeen={handleMessageSeen} isTemp={!!message?.isTemp} myAddress={myAddress} + onReply={onReply} + reply={reply} + scrollToItem={scrollToItem} + replyIndex={replyIndex} />
diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index 885f986..f4cdce7 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import DOMPurify from 'dompurify'; import './styles.css'; // Ensure this CSS file is imported -export const MessageDisplay = ({ htmlContent }) => { +export const MessageDisplay = ({ htmlContent , isReply}) => { const linkify = (text) => { // Regular expression to find URLs starting with https://, http://, or www. @@ -53,7 +53,7 @@ export const MessageDisplay = ({ htmlContent }) => { }; return (
{ // Delegate click handling to the parent div diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index b3ac3d9..7df771c 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -2,18 +2,30 @@ import { Message } from "@chatscope/chat-ui-kit-react"; import React, { useEffect } from "react"; import { useInView } from "react-intersection-observer"; import { MessageDisplay } from "./MessageDisplay"; -import { Avatar, Box, Typography } from "@mui/material"; +import { Avatar, Box, ButtonBase, Typography } from "@mui/material"; import { formatTimestamp } from "../../utils/time"; import { getBaseApi } from "../../background"; import { getBaseApiReact } from "../../App"; import { generateHTML } from "@tiptap/react"; -import Highlight from '@tiptap/extension-highlight' -import StarterKit from '@tiptap/starter-kit' -import Underline from '@tiptap/extension-underline' +import Highlight from "@tiptap/extension-highlight"; +import StarterKit from "@tiptap/starter-kit"; +import Underline from "@tiptap/extension-underline"; import { executeEvent } from "../../utils/events"; import { WrapperUserAction } from "../WrapperUserAction"; -export const MessageItem = ({ message, onSeen, isLast, isTemp, myAddress }) => { +import ReplyIcon from "@mui/icons-material/Reply"; +export const MessageItem = ({ + message, + onSeen, + isLast, + isTemp, + myAddress, + onReply, + isShowingAsReply, + reply, + replyIndex, + scrollToItem +}) => { const { ref, inView } = useInView({ threshold: 0.7, // Fully visible triggerOnce: true, // Only trigger once when it becomes visible @@ -34,70 +46,169 @@ export const MessageItem = ({ message, onSeen, isLast, isTemp, myAddress }) => { borderRadius: "7px", width: "95%", display: "flex", - gap: '7px', - opacity: isTemp ? 0.5 : 1 + gap: "7px", + opacity: isTemp ? 0.5 : 1, }} + id={message?.signature} > - - - {message?.senderName?.charAt(0)} - - + {isShowingAsReply ? ( + + ) : ( + + + {message?.senderName?.charAt(0)} + + + )} + - - - {message?.senderName || message?.sender} - - + + + {message?.senderName || message?.sender} + + + {!isShowingAsReply && ( + { + onReply(message); + }} + > + + + )} + + {reply && ( + { + scrollToItem(replyIndex) + + + }} + > + + + Replied to {reply?.senderName || reply?.senderAddress} + {reply?.messageText && ( + + )} + {reply?.text?.type === "notification" ? ( + + ) : ( + + )} + + + )} {message?.messageText && ( - + )} {message?.text?.type === "notification" ? ( ) : ( )} - + {isTemp ? ( - Sending... - ): ( - {formatTimestamp(message.timestamp)} - ) } - + + Sending... + + ) : ( + + {formatTimestamp(message.timestamp)} + + )} @@ -115,3 +226,51 @@ export const MessageItem = ({ message, onSeen, isLast, isTemp, myAddress }) => {
); }; + + +export const ReplyPreview = ({message})=> { + + return ( + + + + Replied to {message?.senderName || message?.senderAddress} + {message?.messageText && ( + + )} + {message?.text?.type === "notification" ? ( + + ) : ( + + )} + + + ) +} \ No newline at end of file diff --git a/src/components/Chat/styles.css b/src/components/Chat/styles.css index 8766c40..7c65eb0 100644 --- a/src/components/Chat/styles.css +++ b/src/components/Chat/styles.css @@ -120,3 +120,6 @@ max-width: 100%; } +.isReply p { + font-size: 12px !important; +} diff --git a/src/components/Mobile/MobileFooter.tsx b/src/components/Mobile/MobileFooter.tsx index 778cac3..95bf553 100644 --- a/src/components/Mobile/MobileFooter.tsx +++ b/src/components/Mobile/MobileFooter.tsx @@ -31,6 +31,7 @@ const IconWrapper = ({ children, label, color }) => { fontSize: "12px", fontWeight: 500, color: color, + wordBreak: 'normal' }} > {label}