mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-20 10:05:56 +00:00
chat portion of unencrypted open groups
This commit is contained in:
parent
50955941e4
commit
4cafd57abe
@ -664,8 +664,7 @@ const handleNotification = async (groups) => {
|
||||
const data = groups.filter(
|
||||
(group) =>
|
||||
group?.sender !== address &&
|
||||
!mutedGroups.includes(group.groupId) &&
|
||||
!isUpdateMsg(group?.data)
|
||||
!mutedGroups.includes(group.groupId)
|
||||
);
|
||||
const dataWithUpdates = groups.filter(
|
||||
(group) => group?.sender !== address && !mutedGroups.includes(group.groupId)
|
||||
@ -716,8 +715,7 @@ const handleNotification = async (groups) => {
|
||||
Date.now() - lastGroupNotification >= 120000
|
||||
) {
|
||||
if (
|
||||
!newestLatestTimestamp?.data ||
|
||||
!isExtMsg(newestLatestTimestamp?.data)
|
||||
!newestLatestTimestamp?.data
|
||||
)
|
||||
return;
|
||||
|
||||
|
@ -27,7 +27,7 @@ import { throttle } from 'lodash'
|
||||
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
||||
export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent, hideView}) => {
|
||||
export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent, hideView, isPrivate}) => {
|
||||
const [messages, setMessages] = useState([])
|
||||
const [chatReferences, setChatReferences] = useState({})
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
@ -223,7 +223,7 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
setChatReferences((prev) => {
|
||||
const organizedChatReferences = { ...prev };
|
||||
combineUIAndExtensionMsgs
|
||||
.filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit"))
|
||||
.filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.type === "reaction"))
|
||||
.forEach((item) => {
|
||||
try {
|
||||
if(item.decryptedData?.type === "edit"){
|
||||
@ -231,11 +231,16 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
...(organizedChatReferences[item.chatReference] || {}),
|
||||
edit: item.decryptedData,
|
||||
};
|
||||
} else if(item?.type === "edit"){
|
||||
organizedChatReferences[item.chatReference] = {
|
||||
...(organizedChatReferences[item.chatReference] || {}),
|
||||
edit: item,
|
||||
};
|
||||
} else {
|
||||
const content = item.decryptedData?.content;
|
||||
const content = item?.content || item.decryptedData?.content;
|
||||
const sender = item.sender;
|
||||
const newTimestamp = item.timestamp;
|
||||
const contentState = item.decryptedData?.contentState;
|
||||
const contentState = item?.contentState || item.decryptedData?.contentState;
|
||||
|
||||
if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) {
|
||||
console.warn("Invalid content, sender, or timestamp in reaction data", item);
|
||||
@ -306,7 +311,7 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
const organizedChatReferences = { ...prev };
|
||||
|
||||
combineUIAndExtensionMsgs
|
||||
.filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit"))
|
||||
.filter((rawItem) => rawItem && rawItem.chatReference && (rawItem.decryptedData?.type === "reaction" || rawItem.decryptedData?.type === "edit" || rawItem?.type === "edit" || rawItem?.type === "reaction"))
|
||||
.forEach((item) => {
|
||||
try {
|
||||
if(item.decryptedData?.type === "edit"){
|
||||
@ -314,11 +319,16 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
...(organizedChatReferences[item.chatReference] || {}),
|
||||
edit: item.decryptedData,
|
||||
};
|
||||
} else if(item?.type === "edit"){
|
||||
organizedChatReferences[item.chatReference] = {
|
||||
...(organizedChatReferences[item.chatReference] || {}),
|
||||
edit: item,
|
||||
};
|
||||
} else {
|
||||
const content = item.decryptedData?.content;
|
||||
const content = item?.content || item.decryptedData?.content;
|
||||
const sender = item.sender;
|
||||
const newTimestamp = item.timestamp;
|
||||
const contentState = item.decryptedData?.contentState;
|
||||
const contentState = item?.contentState || item.decryptedData?.contentState;
|
||||
|
||||
if (!content || typeof content !== "string" || !sender || typeof sender !== "string" || !newTimestamp) {
|
||||
console.warn("Invalid content, sender, or timestamp in reaction data", item);
|
||||
@ -453,10 +463,11 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
setIsLoading(true)
|
||||
initWebsocketMessageGroup()
|
||||
}
|
||||
}, [triedToFetchSecretKey, secretKey])
|
||||
}, [triedToFetchSecretKey, secretKey, isPrivate])
|
||||
|
||||
useEffect(()=> {
|
||||
if(!secretKey || hasInitializedWebsocket.current) return
|
||||
if(isPrivate === null) return
|
||||
if(isPrivate === false || !secretKey || hasInitializedWebsocket.current) return
|
||||
forceCloseWebSocket()
|
||||
setMessages([])
|
||||
setIsLoading(true)
|
||||
@ -466,7 +477,7 @@ const [messageSize, setMessageSize] = useState(0)
|
||||
}, 6000);
|
||||
initWebsocketMessageGroup()
|
||||
hasInitializedWebsocket.current = true
|
||||
}, [secretKey])
|
||||
}, [secretKey, isPrivate])
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
@ -551,6 +562,7 @@ const clearEditorContent = () => {
|
||||
|
||||
const sendMessage = async ()=> {
|
||||
try {
|
||||
if(isPrivate === null) throw new Error('Unable to determine if group is private')
|
||||
if(isSending) return
|
||||
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
|
||||
pauseAllQueues()
|
||||
@ -558,8 +570,10 @@ const clearEditorContent = () => {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
|
||||
if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
|
||||
|
||||
|
||||
setIsSending(true)
|
||||
const message = htmlContent
|
||||
const message = isPrivate === false ? editorRef.current.getJSON() : htmlContent
|
||||
const secretKeyObject = await getSecretKey(false, true)
|
||||
|
||||
let repliedTo = replyMessage?.signature
|
||||
@ -569,19 +583,24 @@ const clearEditorContent = () => {
|
||||
}
|
||||
let chatReference = onEditMessage?.signature
|
||||
|
||||
const publicData = isPrivate ? {} : {
|
||||
isEdited : chatReference ? true : false,
|
||||
}
|
||||
const otherData = {
|
||||
repliedTo,
|
||||
...(onEditMessage?.decryptedData || {}),
|
||||
type: chatReference ? 'edit' : '',
|
||||
specialId: uid.rnd(),
|
||||
...publicData
|
||||
}
|
||||
const objectMessage = {
|
||||
...(otherData || {}),
|
||||
message
|
||||
[isPrivate ? 'message' : 'messageText']: message,
|
||||
version: 3
|
||||
}
|
||||
const message64: any = await objectToBase64(objectMessage)
|
||||
|
||||
const encryptSingle = await encryptChatMessage(message64, secretKeyObject)
|
||||
const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject)
|
||||
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
|
||||
|
||||
const sendMessageFunc = async () => {
|
||||
@ -591,7 +610,7 @@ const clearEditorContent = () => {
|
||||
// Add the function to the queue
|
||||
const messageObj = {
|
||||
message: {
|
||||
text: message,
|
||||
text: htmlContent,
|
||||
timestamp: Date.now(),
|
||||
senderName: myName,
|
||||
sender: myAddress,
|
||||
@ -668,7 +687,7 @@ const clearEditorContent = () => {
|
||||
const onEdit = useCallback((message)=> {
|
||||
setOnEditMessage(message)
|
||||
setReplyMessage(null)
|
||||
editorRef.current.chain().focus().setContent(message?.text).run();
|
||||
editorRef.current.chain().focus().setContent(message?.messageText || message?.text).run();
|
||||
|
||||
}, [])
|
||||
const handleReaction = useCallback(async (reaction, chatMessage, reactionState = true)=> {
|
||||
@ -696,7 +715,7 @@ const clearEditorContent = () => {
|
||||
}
|
||||
const message64: any = await objectToBase64(objectMessage)
|
||||
const reactiontypeNumber = RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS
|
||||
const encryptSingle = await encryptChatMessage(message64, secretKeyObject, reactiontypeNumber)
|
||||
const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject, reactiontypeNumber)
|
||||
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
|
||||
|
||||
const sendMessageFunc = async () => {
|
||||
@ -752,9 +771,9 @@ const clearEditorContent = () => {
|
||||
left: hide && '-100000px',
|
||||
}}>
|
||||
|
||||
<ChatList hasSecretKey={!!secretKey} openQManager={openQManager} enableMentions onReply={onReply} onEdit={onEdit} chatId={selectedGroup} initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages} handleReaction={handleReaction} chatReferences={chatReferences} tempChatReferences={tempChatReferences} members={members} myName={myName} selectedGroup={selectedGroup} />
|
||||
<ChatList isPrivate={isPrivate} hasSecretKey={!!secretKey} openQManager={openQManager} enableMentions onReply={onReply} onEdit={onEdit} chatId={selectedGroup} initialMessages={messages} myAddress={myAddress} tempMessages={tempMessages} handleReaction={handleReaction} chatReferences={chatReferences} tempChatReferences={tempChatReferences} members={members} myName={myName} selectedGroup={selectedGroup} />
|
||||
|
||||
{!!secretKey && (
|
||||
{(!!secretKey || isPrivate === false) && (
|
||||
<div style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
|
@ -28,13 +28,13 @@ export const ChatList = ({
|
||||
selectedGroup,
|
||||
enableMentions,
|
||||
openQManager,
|
||||
hasSecretKey
|
||||
hasSecretKey,
|
||||
isPrivate
|
||||
}) => {
|
||||
const parentRef = useRef();
|
||||
const [messages, setMessages] = useState(initialMessages);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const [showScrollDownButton, setShowScrollDownButton] = useState(false);
|
||||
|
||||
const hasLoadedInitialRef = useRef(false);
|
||||
const scrollingIntervalRef = useRef(null);
|
||||
const lastSeenUnreadMessageTimestamp = useRef(null);
|
||||
@ -272,7 +272,10 @@ export const ChatList = ({
|
||||
message.text = chatReferences[message.signature]?.edit?.message;
|
||||
message.isEdit = true
|
||||
}
|
||||
|
||||
if (chatReferences[message.signature]?.edit?.messageText && message?.messageText) {
|
||||
message.messageText = chatReferences[message.signature]?.edit?.messageText;
|
||||
message.isEdit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -315,7 +318,6 @@ export const ChatList = ({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
data-index={virtualRow.index} //needed for dynamic row height measurement
|
||||
@ -357,6 +359,7 @@ export const ChatList = ({
|
||||
handleReaction={handleReaction}
|
||||
reactions={reactions}
|
||||
isUpdating={isUpdating}
|
||||
isPrivate={isPrivate}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@ -408,7 +411,7 @@ export const ChatList = ({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{enableMentions && hasSecretKey && (
|
||||
{enableMentions && (hasSecretKey || isPrivate === false) && (
|
||||
<ChatOptions
|
||||
openQManager={openQManager}
|
||||
messages={messages}
|
||||
@ -416,6 +419,7 @@ export const ChatList = ({
|
||||
members={members}
|
||||
myName={myName}
|
||||
selectedGroup={selectedGroup}
|
||||
isPrivate={isPrivate}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -13,6 +13,10 @@ import { Spacer } from "../../common/Spacer";
|
||||
import AlternateEmailIcon from "@mui/icons-material/AlternateEmail";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import InsertLinkIcon from '@mui/icons-material/InsertLink';
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import Mention from "@tiptap/extension-mention";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import {
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
@ -32,6 +36,8 @@ import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { formatTimestamp } from "../../utils/time";
|
||||
import { ContextMenuMentions } from "../ContextMenuMentions";
|
||||
import { convert } from 'html-to-text';
|
||||
import { generateHTML } from "@tiptap/react";
|
||||
import ErrorBoundary from "../../common/ErrorBoundary";
|
||||
|
||||
const extractTextFromHTML = (htmlString = '') => {
|
||||
return convert(htmlString, {
|
||||
@ -43,7 +49,7 @@ const cache = new CellMeasurerCache({
|
||||
defaultHeight: 50,
|
||||
});
|
||||
|
||||
export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGroup, openQManager }) => {
|
||||
export const ChatOptions = ({ messages : untransformedMessages, goToMessage, members, myName, selectedGroup, openQManager, isPrivate }) => {
|
||||
const [mode, setMode] = useState("default");
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [selectedMember, setSelectedMember] = useState(0);
|
||||
@ -52,7 +58,27 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
const parentRefMentions = useRef();
|
||||
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null)
|
||||
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
||||
|
||||
const messages = useMemo(()=> {
|
||||
return untransformedMessages?.map((item)=> {
|
||||
if(item?.messageText){
|
||||
let transformedMessage = item?.messageText
|
||||
try {
|
||||
transformedMessage = generateHTML(item?.messageText, [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight,
|
||||
Mention
|
||||
])
|
||||
return {
|
||||
...item,
|
||||
messageText: transformedMessage
|
||||
}
|
||||
} catch (error) {
|
||||
// error
|
||||
}
|
||||
} else return item
|
||||
})
|
||||
}, [untransformedMessages])
|
||||
const getTimestampMention = async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
@ -124,7 +150,7 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
.filter(
|
||||
(message) =>
|
||||
message?.senderName === selectedMember &&
|
||||
extractTextFromHTML(message?.decryptedData?.message)?.includes(
|
||||
extractTextFromHTML(isPrivate ? message?.messageText : message?.decryptedData?.message)?.includes(
|
||||
debouncedValue.toLowerCase()
|
||||
)
|
||||
)
|
||||
@ -132,20 +158,27 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
}
|
||||
return messages
|
||||
.filter((message) =>
|
||||
extractTextFromHTML(message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase())
|
||||
extractTextFromHTML(isPrivate === false ? message?.messageText : message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase())
|
||||
)
|
||||
?.sort((a, b) => b?.timestamp - a?.timestamp);
|
||||
}, [debouncedValue, messages, selectedMember]);
|
||||
}, [debouncedValue, messages, selectedMember, isPrivate]);
|
||||
|
||||
const mentionList = useMemo(() => {
|
||||
if(!messages || messages.length === 0 || !myName) return []
|
||||
|
||||
if(isPrivate === false){
|
||||
return messages
|
||||
.filter((message) =>
|
||||
extractTextFromHTML(message?.messageText)?.includes(`@${myName}`)
|
||||
)
|
||||
?.sort((a, b) => b?.timestamp - a?.timestamp);
|
||||
|
||||
}
|
||||
return messages
|
||||
.filter((message) =>
|
||||
extractTextFromHTML(message?.decryptedData?.message)?.includes(`@${myName}`)
|
||||
)
|
||||
?.sort((a, b) => b?.timestamp - a?.timestamp);
|
||||
}, [messages, myName]);
|
||||
}, [messages, myName, isPrivate]);
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: searchedList.length,
|
||||
@ -291,7 +324,8 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
<ShowMessage messages={messages} goToMessage={goToMessage} message={message} />
|
||||
{/* <Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@ -363,7 +397,7 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box> */}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@ -544,6 +578,7 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
const index = virtualRow.index;
|
||||
let message = searchedList[index];
|
||||
return (
|
||||
|
||||
<div
|
||||
data-index={virtualRow.index} //needed for dynamic row height measurement
|
||||
ref={rowVirtualizer.measureElement} //measure dynamic row height
|
||||
@ -562,80 +597,17 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: "#27282c",
|
||||
color: "white",
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
}}
|
||||
alt={message?.senderName}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
message?.senderName
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.senderName?.charAt(0)}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{message?.senderName}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Spacer height="5px" />
|
||||
<Typography sx={{
|
||||
fontSize: '12px'
|
||||
}}>{formatTimestamp(message.timestamp)}</Typography>
|
||||
<Box
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
const findMsgIndex = messages.findIndex(
|
||||
(item) =>
|
||||
item?.signature === message?.signature
|
||||
);
|
||||
if (findMsgIndex !== -1) {
|
||||
goToMessage(findMsgIndex);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MessageDisplay
|
||||
htmlContent={
|
||||
message?.decryptedData?.message || "<p></p>"
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<Typography>
|
||||
Error loading content: Invalid Data
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<ShowMessage message={message} goToMessage={goToMessage} messages={messages} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@ -705,3 +677,91 @@ export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGr
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const ShowMessage = ({message, goToMessage, messages})=> {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
padding: "0px 20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: "#27282c",
|
||||
color: "white",
|
||||
height: "25px",
|
||||
width: "25px",
|
||||
}}
|
||||
alt={message?.senderName}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
message?.senderName
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.senderName?.charAt(0)}
|
||||
</Avatar>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
}}
|
||||
>
|
||||
{message?.senderName}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Spacer height="5px" />
|
||||
<Typography sx={{
|
||||
fontSize: '12px'
|
||||
}}>{formatTimestamp(message.timestamp)}</Typography>
|
||||
<Box
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => {
|
||||
const findMsgIndex = messages.findIndex(
|
||||
(item) =>
|
||||
item?.signature === message?.signature
|
||||
);
|
||||
if (findMsgIndex !== -1) {
|
||||
goToMessage(findMsgIndex);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message?.messageText && (
|
||||
<MessageDisplay
|
||||
htmlContent={message?.messageText}
|
||||
/>
|
||||
)}
|
||||
{message?.decryptedData?.message && (
|
||||
<MessageDisplay
|
||||
htmlContent={
|
||||
message?.decryptedData?.message || "<p></p>"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -106,7 +106,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const embedLink = htmlContent.match(/qortal:\/\/use-embed\/[^\s<>]+/);
|
||||
const embedLink = htmlContent?.match(/qortal:\/\/use-embed\/[^\s<>]+/);
|
||||
|
||||
let embedData = null;
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { getBaseApi } from "../../background";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import { generateHTML } from "@tiptap/react";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import Mention from "@tiptap/extension-mention";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
@ -33,13 +34,15 @@ export const MessageItem = ({
|
||||
reactions,
|
||||
isUpdating,
|
||||
lastSignature,
|
||||
onEdit
|
||||
onEdit,
|
||||
isPrivate
|
||||
}) => {
|
||||
const { ref, inView } = useInView({
|
||||
threshold: 0.7, // Fully visible
|
||||
triggerOnce: false, // Only trigger once when it becomes visible
|
||||
});
|
||||
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [selectedReaction, setSelectedReaction] = useState(null);
|
||||
|
||||
@ -136,7 +139,7 @@ export const MessageItem = ({
|
||||
gap: '10px',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{message?.sender === myAddress && !message?.isNotEncrypted && (
|
||||
{message?.sender === myAddress && (!message?.isNotEncrypted || isPrivate === false) && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
onEdit(message);
|
||||
@ -205,6 +208,7 @@ export const MessageItem = ({
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight,
|
||||
Mention
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
@ -223,6 +227,7 @@ export const MessageItem = ({
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight,
|
||||
Mention
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
@ -341,7 +346,7 @@ export const MessageItem = ({
|
||||
alignItems: 'center',
|
||||
gap: '15px'
|
||||
}}>
|
||||
{message?.isNotEncrypted && (
|
||||
{message?.isNotEncrypted && isPrivate && (
|
||||
<KeyOffIcon sx={{
|
||||
color: 'white',
|
||||
marginLeft: '10px'
|
||||
@ -456,6 +461,7 @@ export const ReplyPreview = ({message, isEdit})=> {
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight,
|
||||
Mention
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
|
@ -125,7 +125,7 @@
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.tiptap .mention {
|
||||
.tiptap [data-type="mention"] {
|
||||
box-decoration-break: clone;
|
||||
color: lightblue;
|
||||
padding: 0.1rem 0.3rem;
|
||||
|
@ -19,6 +19,8 @@ import { ChatIcon } from "../../assets/Icons/ChatIcon";
|
||||
import { ThreadsIcon } from "../../assets/Icons/ThreadsIcon";
|
||||
import { MembersIcon } from "../../assets/Icons/MembersIcon";
|
||||
import { AdminsIcon } from "../../assets/Icons/AdminsIcon";
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
|
||||
const IconWrapper = ({ children, label, color, selected, selectColor, customHeight }) => {
|
||||
return (
|
||||
@ -80,7 +82,8 @@ export const DesktopHeader = ({
|
||||
hasUnreadChat,
|
||||
isChat,
|
||||
isForum,
|
||||
setGroupSection
|
||||
setGroupSection,
|
||||
isPrivate
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
return (
|
||||
@ -95,7 +98,20 @@ export const DesktopHeader = ({
|
||||
padding: "10px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: '10px'
|
||||
}}>
|
||||
{isPrivate && (
|
||||
<LockIcon sx={{
|
||||
color: 'var(--green)'
|
||||
}} />
|
||||
)}
|
||||
{isPrivate === false && (
|
||||
<NoEncryptionGmailerrorredIcon sx={{
|
||||
color: 'var(--unread)'
|
||||
}} />
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -77,8 +78,8 @@ import { useSetRecoilState } from "recoil";
|
||||
import { selectedGroupIdAtom } from "../../atoms/global";
|
||||
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
||||
|
||||
|
||||
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
|
||||
|
||||
export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
@ -347,6 +348,19 @@ export const getNamesForAdmins = async (admins) => {
|
||||
return members;
|
||||
};
|
||||
|
||||
function areKeysEqual(array1, array2) {
|
||||
// If lengths differ, the arrays cannot be equal
|
||||
if (array1?.length !== array2?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort both arrays and compare their elements
|
||||
const sortedArray1 = [...array1].sort();
|
||||
const sortedArray2 = [...array2].sort();
|
||||
|
||||
return sortedArray1.every((key, index) => key === sortedArray2[index]);
|
||||
}
|
||||
|
||||
export const Group = ({
|
||||
myAddress,
|
||||
isFocused,
|
||||
@ -418,6 +432,17 @@ export const Group = ({
|
||||
const [appsModeDev, setAppsModeDev] = useState('home')
|
||||
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false)
|
||||
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false)
|
||||
|
||||
|
||||
const [groupsProperties, setGroupsProperties] = useState({})
|
||||
|
||||
const isPrivate = useMemo(()=> {
|
||||
if(!selectedGroup?.groupId || !groupsProperties[selectedGroup?.groupId]) return null
|
||||
if(groupsProperties[selectedGroup?.groupId]?.isOpen === true) return false
|
||||
if(groupsProperties[selectedGroup?.groupId]?.isOpen === false) return true
|
||||
return null
|
||||
}, [selectedGroup])
|
||||
|
||||
const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom)
|
||||
const toggleSideViewDirects = ()=> {
|
||||
if(isOpenSideViewGroups){
|
||||
@ -569,9 +594,8 @@ export const Group = ({
|
||||
|
||||
if (
|
||||
group?.data &&
|
||||
isExtMsg(group?.data) &&
|
||||
group?.sender !== myAddress &&
|
||||
group?.timestamp && (!isUpdateMsg(group?.data) || groupChatTimestamps[group?.groupId]) &&
|
||||
group?.timestamp && groupChatTimestamps[group?.groupId] &&
|
||||
((!timestampEnterData[group?.groupId] &&
|
||||
Date.now() - group?.timestamp < timeDifferenceForNotificationChats) ||
|
||||
timestampEnterData[group?.groupId] < group?.timestamp)
|
||||
@ -706,12 +730,19 @@ export const Group = ({
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedGroup) {
|
||||
setTriedToFetchSecretKey(false);
|
||||
getSecretKey(true);
|
||||
if (selectedGroup && isPrivate !== null) {
|
||||
if(isPrivate){
|
||||
setTriedToFetchSecretKey(false);
|
||||
getSecretKey(true);
|
||||
}
|
||||
|
||||
getGroupOwner(selectedGroup?.groupId);
|
||||
}
|
||||
}, [selectedGroup]);
|
||||
if(isPrivate === false){
|
||||
setTriedToFetchSecretKey(true);
|
||||
|
||||
}
|
||||
}, [selectedGroup, isPrivate]);
|
||||
|
||||
|
||||
|
||||
@ -735,9 +766,8 @@ export const Group = ({
|
||||
const groupData = {}
|
||||
|
||||
const getGroupData = groups.map(async(group)=> {
|
||||
const isUpdate = isUpdateMsg(group?.data)
|
||||
if(!group.groupId || !group?.timestamp) return null
|
||||
if(isUpdate && (!groupData[group.groupId] || groupData[group.groupId] < group.timestamp)){
|
||||
if((!groupData[group.groupId] || groupData[group.groupId] < group.timestamp)){
|
||||
const hasMoreRecentMsg = await getCountNewMesg(group.groupId, timestampEnterDataRef.current[group?.groupId] || Date.now() - 24 * 60 * 60 * 1000)
|
||||
if(hasMoreRecentMsg){
|
||||
groupData[group.groupId] = hasMoreRecentMsg
|
||||
@ -754,6 +784,32 @@ export const Group = ({
|
||||
}
|
||||
}
|
||||
|
||||
const getGroupsProperties = useCallback(async(address)=> {
|
||||
try {
|
||||
const url = `${getBaseApiReact()}/groups/member/${address}`;
|
||||
const response = await fetch(url);
|
||||
if(!response.ok) throw new Error('Cannot get group properties')
|
||||
let data = await response.json();
|
||||
const transformToObject = data.reduce((result, item) => {
|
||||
|
||||
result[item.groupId] = item
|
||||
return result;
|
||||
}, {});
|
||||
setGroupsProperties(transformToObject)
|
||||
} catch (error) {
|
||||
// error
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
if(!myAddress) return
|
||||
if(areKeysEqual(groups?.map((grp)=> grp?.groupId), Object.keys(groupsProperties))){
|
||||
} else {
|
||||
getGroupsProperties(myAddress)
|
||||
}
|
||||
}, [groups, myAddress])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -941,9 +997,9 @@ export const Group = ({
|
||||
.filter((group) => group?.sender !== myAddress)
|
||||
.find((gr) => gr?.groupId === selectedGroup?.groupId);
|
||||
if (!findGroup) return false;
|
||||
if (!findGroup?.data || !isExtMsg(findGroup?.data)) return false;
|
||||
if (!findGroup?.data) return false;
|
||||
return (
|
||||
findGroup?.timestamp && (!isUpdateMsg(findGroup?.data) || groupChatTimestamps[findGroup?.groupId]) &&
|
||||
findGroup?.timestamp && groupChatTimestamps[findGroup?.groupId] &&
|
||||
((!timestampEnterData[selectedGroup?.groupId] &&
|
||||
Date.now() - findGroup?.timestamp <
|
||||
timeDifferenceForNotificationChats) ||
|
||||
@ -1657,6 +1713,7 @@ export const Group = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const renderGroups = () => {
|
||||
return (
|
||||
<div
|
||||
@ -1805,15 +1862,45 @@ export const Group = ({
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
{groupsProperties[group?.groupId]?.isOpen === false ? (
|
||||
<Box sx={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: "#232428",
|
||||
color: "white",
|
||||
}}
|
||||
alt={group?.groupName}
|
||||
>
|
||||
{group.groupName?.charAt(0)}
|
||||
</Avatar>
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<LockIcon sx={{
|
||||
color: 'var(--green)'
|
||||
}} />
|
||||
</Box>
|
||||
): (
|
||||
<Box sx={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
background: "#232428",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<NoEncryptionGmailerrorredIcon sx={{
|
||||
color: 'var(--unread)'
|
||||
}} />
|
||||
</Box>
|
||||
// <Avatar
|
||||
// sx={{
|
||||
// background: "#232428",
|
||||
// color: "white",
|
||||
// }}
|
||||
// alt={group?.groupName}
|
||||
// >
|
||||
// {group.groupName?.charAt(0)}
|
||||
// </Avatar>
|
||||
)}
|
||||
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={group.groupName}
|
||||
@ -1849,7 +1936,7 @@ export const Group = ({
|
||||
/>
|
||||
)}
|
||||
{group?.data &&
|
||||
isExtMsg(group?.data) && (!isUpdateMsg(group?.data) || groupChatTimestamps[group?.groupId]) &&
|
||||
groupChatTimestamps[group?.groupId] &&
|
||||
group?.sender !== myAddress &&
|
||||
group?.timestamp &&
|
||||
((!timestampEnterData[group?.groupId] &&
|
||||
@ -2085,6 +2172,7 @@ export const Group = ({
|
||||
{!isMobile && (
|
||||
|
||||
<DesktopHeader
|
||||
isPrivate={isPrivate}
|
||||
selectedGroup={selectedGroup}
|
||||
groupSection={groupSection}
|
||||
isUnread={isUnread}
|
||||
@ -2138,6 +2226,7 @@ export const Group = ({
|
||||
selectedGroup={selectedGroup?.groupId}
|
||||
getSecretKey={getSecretKey}
|
||||
secretKey={secretKey}
|
||||
isPrivate={isPrivate}
|
||||
setSecretKey={setSecretKey}
|
||||
handleNewEncryptionNotification={
|
||||
setNewEncryptionNotification
|
||||
@ -2153,7 +2242,7 @@ export const Group = ({
|
||||
getTimestampEnterChatParent={getTimestampEnterChat}
|
||||
/>
|
||||
)}
|
||||
{firstSecretKeyInCreation &&
|
||||
{isPrivate &&firstSecretKeyInCreation &&
|
||||
triedToFetchSecretKey &&
|
||||
!secretKeyPublishDate && (
|
||||
<div
|
||||
@ -2174,7 +2263,7 @@ export const Group = ({
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
{!admins.includes(myAddress) &&
|
||||
{isPrivate && !admins.includes(myAddress) &&
|
||||
!secretKey &&
|
||||
triedToFetchSecretKey ? (
|
||||
<>
|
||||
@ -2231,7 +2320,7 @@ export const Group = ({
|
||||
) : null}
|
||||
</>
|
||||
) : admins.includes(myAddress) &&
|
||||
!secretKey &&
|
||||
(!secretKey && isPrivate) &&
|
||||
triedToFetchSecretKey ? null : !triedToFetchSecretKey ? null : (
|
||||
<>
|
||||
<GroupAnnouncements
|
||||
@ -2273,7 +2362,7 @@ export const Group = ({
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
{admins.includes(myAddress) &&
|
||||
{isPrivate && admins.includes(myAddress) &&
|
||||
shouldReEncrypt &&
|
||||
triedToFetchSecretKey &&
|
||||
!firstSecretKeyInCreation &&
|
||||
|
@ -217,12 +217,12 @@ export const decodeBase64ForUIChatMessages = (messages)=> {
|
||||
try {
|
||||
const decoded = atob(msg?.data);
|
||||
const parseDecoded =JSON.parse(decodeURIComponent(escape(decoded)))
|
||||
if(parseDecoded?.messageText){
|
||||
|
||||
msgs.push({
|
||||
...msg,
|
||||
...parseDecoded
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user