mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-05-17 23:26:58 +00:00
Translation for chat pages
This commit is contained in:
parent
4f35730db6
commit
cb336133c6
@ -1,4 +1,4 @@
|
||||
import React, {
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
@ -50,6 +50,8 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import { throttle } from 'lodash';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import { messageHasImage } from '../../utils/chat';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
const uidImages = new ShortUniqueId({ length: 12 });
|
||||
|
||||
@ -75,8 +77,8 @@ export const ChatGroup = ({
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isMoved, setIsMoved] = useState(false);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const hasInitialized = useRef(false);
|
||||
const [isFocusedParent, setIsFocusedParent] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState(null);
|
||||
@ -93,8 +95,8 @@ export const ChatGroup = ({
|
||||
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const lastReadTimestamp = useRef(null);
|
||||
|
||||
const handleUpdateRef = useRef(null);
|
||||
const { t } = useTranslation(['auth', 'core', 'group']);
|
||||
|
||||
const getTimestampEnterChat = async (selectedGroup) => {
|
||||
try {
|
||||
@ -129,7 +131,12 @@ export const ChatGroup = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -154,9 +161,6 @@ export const ChatGroup = ({
|
||||
return Array.from(uniqueMembers);
|
||||
}, [messages]);
|
||||
|
||||
const triggerRerender = () => {
|
||||
forceUpdate(); // Trigger re-render by updating the state
|
||||
};
|
||||
const setEditorRef = (editorInstance) => {
|
||||
editorRef.current = editorInstance;
|
||||
};
|
||||
@ -168,6 +172,7 @@ export const ChatGroup = ({
|
||||
}
|
||||
return [];
|
||||
}, [selectedGroup, queueChats]);
|
||||
|
||||
const tempChatReferences = useMemo(() => {
|
||||
if (!selectedGroup) return [];
|
||||
if (queueChats[selectedGroup]) {
|
||||
@ -184,12 +189,6 @@ export const ChatGroup = ({
|
||||
}
|
||||
}, [secretKey]);
|
||||
|
||||
// const getEncryptedSecretKey = useCallback(()=> {
|
||||
// const response = getResource()
|
||||
// const decryptResponse = decryptResource()
|
||||
// return
|
||||
// }, [])
|
||||
|
||||
const checkForFirstSecretKeyNotification = (messages) => {
|
||||
messages?.forEach((message) => {
|
||||
try {
|
||||
@ -250,7 +249,6 @@ export const ChatGroup = ({
|
||||
const dataRemovedBlock = responseData?.filter((item) => {
|
||||
return !isUserBlocked(item?.sender, item?.senderName);
|
||||
});
|
||||
|
||||
decryptMessages(dataRemovedBlock, false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -273,6 +271,7 @@ export const ChatGroup = ({
|
||||
const filterUIMessages = encryptedMessages.filter(
|
||||
(item) => !isExtMsg(item.data)
|
||||
);
|
||||
|
||||
const decodedUIMessages =
|
||||
decodeBase64ForUIChatMessages(filterUIMessages);
|
||||
|
||||
@ -280,6 +279,7 @@ export const ChatGroup = ({
|
||||
...decodedUIMessages,
|
||||
...response,
|
||||
];
|
||||
|
||||
const combineUIAndExtensionMsgs = processWithNewMessages(
|
||||
combineUIAndExtensionMsgsBefore.map((item) => ({
|
||||
...item,
|
||||
@ -287,16 +287,24 @@ export const ChatGroup = ({
|
||||
})),
|
||||
selectedGroup
|
||||
);
|
||||
|
||||
res(combineUIAndExtensionMsgs);
|
||||
|
||||
if (isInitiated) {
|
||||
const formatted = combineUIAndExtensionMsgs
|
||||
.filter((rawItem) => !rawItem?.chatReference)
|
||||
.map((item) => {
|
||||
const message = (
|
||||
<p>
|
||||
{t('group:message.generic.group_key_created', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
const additionalFields =
|
||||
item?.data === 'NDAwMQ=='
|
||||
item?.data === 'NDAwMQ==' // TODO put magic string somewhere in a file
|
||||
? {
|
||||
text: '<p>First group key created.</p>',
|
||||
text: message,
|
||||
}
|
||||
: {};
|
||||
return {
|
||||
@ -362,7 +370,9 @@ export const ChatGroup = ({
|
||||
!newTimestamp
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid content, sender, or timestamp in reaction data',
|
||||
t('group:message.generic.invalid_content', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
}),
|
||||
item
|
||||
);
|
||||
return;
|
||||
@ -435,10 +445,17 @@ export const ChatGroup = ({
|
||||
const formatted = combineUIAndExtensionMsgs
|
||||
.filter((rawItem) => !rawItem?.chatReference)
|
||||
.map((item) => {
|
||||
const message = (
|
||||
<p>
|
||||
{t('group:message.generic.group_key_created', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
const additionalFields =
|
||||
item?.data === 'NDAwMQ=='
|
||||
? {
|
||||
text: '<p>First group key created.</p>',
|
||||
text: message,
|
||||
}
|
||||
: {};
|
||||
const divide =
|
||||
@ -510,7 +527,9 @@ export const ChatGroup = ({
|
||||
!newTimestamp
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid content, sender, or timestamp in reaction data',
|
||||
t('group:message.generic.invalid_content', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
}),
|
||||
item
|
||||
);
|
||||
return;
|
||||
@ -583,7 +602,12 @@ export const ChatGroup = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -615,6 +639,7 @@ export const ChatGroup = ({
|
||||
console.error('Error during ping:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const initWebsocketMessageGroup = () => {
|
||||
let socketLink = `${getBaseApiReactSocket()}/websockets/chat/messages?txGroupId=${selectedGroup}&encoding=BASE64&limit=100`;
|
||||
socketRef.current = new WebSocket(socketLink);
|
||||
@ -709,7 +734,12 @@ export const ChatGroup = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -744,7 +774,12 @@ export const ChatGroup = ({
|
||||
rej(response.error);
|
||||
})
|
||||
.catch((error) => {
|
||||
rej(error.message || 'An error occurred');
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:message.error.generic', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -760,12 +795,22 @@ export const ChatGroup = ({
|
||||
|
||||
const sendMessage = async () => {
|
||||
try {
|
||||
if (messageSize > 4000) return;
|
||||
if (messageSize > 4000) return; // TODO magic number
|
||||
if (isPrivate === null)
|
||||
throw new Error('Unable to determine if group is private');
|
||||
throw new Error(
|
||||
t('group:message.error.unable_determine_group_private', {
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
if (isSending) return;
|
||||
if (+balance < 4)
|
||||
throw new Error('You need at least 4 QORT to send a message');
|
||||
// TODO magic number
|
||||
throw new Error(
|
||||
t('group:message.error.qortals_required', {
|
||||
quantity: 4,
|
||||
postProcess: 'capitalizeFirst',
|
||||
})
|
||||
);
|
||||
pauseAllQueues();
|
||||
if (editorRef.current) {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
@ -792,13 +837,16 @@ export const ChatGroup = ({
|
||||
const imagesToPublish = [];
|
||||
const deleteImage =
|
||||
onEditMessage && isDeleteImage && messageHasImage(onEditMessage);
|
||||
|
||||
if (deleteImage) {
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
// TODO translate
|
||||
await show({
|
||||
publishFee: fee.fee + ' QORT',
|
||||
message: 'Would you like to delete your previous chat image?',
|
||||
});
|
||||
|
||||
// TODO magic string
|
||||
await window.sendMessage('publishOnQDN', {
|
||||
data: 'RA==',
|
||||
identifier: onEditMessage?.images[0]?.identifier,
|
||||
@ -811,6 +859,7 @@ export const ChatGroup = ({
|
||||
const base64ToSave = isPrivate
|
||||
? await encryptChatMessage(imageToSave, secretKeyObject)
|
||||
: imageToSave;
|
||||
|
||||
// 1 represents public group, 0 is private
|
||||
const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`;
|
||||
imagesToPublish.push({
|
||||
@ -822,7 +871,6 @@ export const ChatGroup = ({
|
||||
|
||||
const res = await window.sendMessage(
|
||||
'PUBLISH_MULTIPLE_QDN_RESOURCES',
|
||||
|
||||
{
|
||||
resources: imagesToPublish,
|
||||
},
|
||||
@ -979,6 +1027,7 @@ export const ChatGroup = ({
|
||||
.setContent(message?.messageText || message?.text)
|
||||
.run();
|
||||
}, []);
|
||||
|
||||
const handleReaction = useCallback(
|
||||
async (reaction, chatMessage, reactionState = true) => {
|
||||
try {
|
||||
@ -1033,12 +1082,6 @@ export const ChatGroup = ({
|
||||
chatReference: chatMessage.signature,
|
||||
};
|
||||
addToQueue(sendMessageFunc, messageObj, 'chat-reaction', selectedGroup);
|
||||
// setTimeout(() => {
|
||||
// executeEvent("sent-new-message-group", {})
|
||||
// }, 150);
|
||||
// clearEditorContent()
|
||||
// setReplyMessage(null)
|
||||
|
||||
// send chat message
|
||||
} catch (error) {
|
||||
const errorMsg = error?.message || error;
|
||||
@ -1417,6 +1460,7 @@ export const ChatGroup = ({
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Manager</Typography>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setIsOpenQManager(false);
|
||||
@ -1429,7 +1473,9 @@ export const ChatGroup = ({
|
||||
/>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<AppViewerContainer
|
||||
customHeight="560px"
|
||||
app={{
|
||||
|
@ -202,11 +202,11 @@ export const ChatList = ({
|
||||
ref={parentRef}
|
||||
className="List"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
height: '0px',
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
height: '0px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@ -391,6 +391,7 @@ export const ChatList = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showScrollButton && (
|
||||
<button
|
||||
onClick={() => scrollToBottom()}
|
||||
|
@ -387,6 +387,7 @@ export const ChatOptions = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Box, Button, Typography, useTheme } from '@mui/material';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
@ -34,9 +34,9 @@ export const CreateCommonSecret = ({
|
||||
const { show } = useContext(MyContext);
|
||||
const setTxList = useSetAtom(txListAtom);
|
||||
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, {
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
@ -140,9 +142,9 @@ export const GroupAnnouncements = ({
|
||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
|
||||
const [isFocusedParent, setIsFocusedParent] = useState(false);
|
||||
|
||||
const { show } = React.useContext(MyContext);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const { show } = useContext(MyContext);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const hasInitialized = useRef(false);
|
||||
const hasInitializedWebsocket = useRef(false);
|
||||
const editorRef = useRef(null);
|
||||
@ -150,12 +152,13 @@ export const GroupAnnouncements = ({
|
||||
const setEditorRef = (editorInstance) => {
|
||||
editorRef.current = editorInstance;
|
||||
};
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const { t } = useTranslation(['auth', 'core', 'group']);
|
||||
|
||||
const triggerRerender = () => {
|
||||
forceUpdate(); // Trigger re-render by updating the state
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedGroup) return;
|
||||
(async () => {
|
||||
|
@ -77,8 +77,10 @@ export const GroupAvatar = ({
|
||||
try {
|
||||
if (!groupId) return;
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
if (+balance < +fee.fee)
|
||||
throw new Error(`Publishing an Avatar requires ${fee.fee}`);
|
||||
|
||||
await show({
|
||||
message: 'Would you like to publish an avatar?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
@ -132,6 +134,7 @@ export const GroupAvatar = ({
|
||||
>
|
||||
{myName?.charAt(0)}
|
||||
</Avatar>
|
||||
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
@ -142,6 +145,7 @@ export const GroupAvatar = ({
|
||||
change avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
@ -170,6 +174,7 @@ export const GroupAvatar = ({
|
||||
>
|
||||
{myName?.charAt(0)}
|
||||
</Avatar>
|
||||
|
||||
<ButtonBase onClick={handleChildClick}>
|
||||
<Typography
|
||||
sx={{
|
||||
@ -180,6 +185,7 @@ export const GroupAvatar = ({
|
||||
change avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
@ -208,6 +214,7 @@ export const GroupAvatar = ({
|
||||
set avatar
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
|
||||
<PopoverComp
|
||||
myName={myName}
|
||||
avatarFile={avatarFile}
|
||||
@ -258,11 +265,15 @@ const PopoverComp = ({
|
||||
>
|
||||
(500 KB max. for GIFS){' '}
|
||||
</Typography>
|
||||
|
||||
<ImageUploader onPick={(file) => setAvatarFile(file)}>
|
||||
<Button variant="contained">Choose Image</Button>
|
||||
</ImageUploader>
|
||||
|
||||
{avatarFile?.name}
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
{!myName && (
|
||||
<Box
|
||||
sx={{
|
||||
@ -283,6 +294,7 @@ const PopoverComp = ({
|
||||
)}
|
||||
|
||||
<Spacer height="25px" />
|
||||
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
disabled={!avatarFile || !myName}
|
||||
|
@ -171,8 +171,9 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//error
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
const res = extractComponents(url);
|
||||
if (res) {
|
||||
const { service, name, identifier, path } = res;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, {
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
@ -77,7 +78,7 @@ const getBadgeImg = (level) => {
|
||||
}
|
||||
};
|
||||
|
||||
const UserBadge = React.memo(({ userInfo }) => {
|
||||
const UserBadge = memo(({ userInfo }) => {
|
||||
return (
|
||||
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
|
||||
<img
|
||||
@ -92,7 +93,7 @@ const UserBadge = React.memo(({ userInfo }) => {
|
||||
);
|
||||
});
|
||||
|
||||
export const MessageItem = React.memo(
|
||||
export const MessageItem = memo(
|
||||
({
|
||||
message,
|
||||
onSeen,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { Color } from '@tiptap/extension-color';
|
||||
@ -42,7 +42,7 @@ function textMatcher(doc, from) {
|
||||
return { start, query };
|
||||
}
|
||||
|
||||
const MenuBar = React.memo(
|
||||
const MenuBar = memo(
|
||||
({
|
||||
setEditorRef,
|
||||
isChat,
|
||||
|
@ -61,6 +61,8 @@
|
||||
"descrypt_wallet": "decrypting wallet...",
|
||||
"encryption_key": "the group's first common encryption key is in the process of creation. Please wait a few minutes for it to be retrieved by the network. Checking every 2 minutes...",
|
||||
"group_invited_you": "{{group}} has invited you",
|
||||
"group_key_created": "first group key created.",
|
||||
"invalid_content": "invalid content, sender, or timestamp in reaction data",
|
||||
"invalid_data": "error loading content: Invalid Data",
|
||||
"latest_promotion": "only the latest promotion from the week will be shown for your group.",
|
||||
"loading_members": "loading member list with names... please wait.",
|
||||
@ -97,6 +99,7 @@
|
||||
"qortals_required": "you need at least {{ quantity }} QORT to send a message",
|
||||
"timeout_reward": "timeout waiting for reward share confirmation",
|
||||
"thread_id": "unable to locate thread Id",
|
||||
"unable_determine_group_private": "unable to determine if group is private",
|
||||
"unable_minting": "unable to start minting"
|
||||
},
|
||||
"success": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user