mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-28 22:07:52 +00:00
Add theme to all chat pages
This commit is contained in:
parent
cf335a6d0a
commit
905cddf29a
@ -4,7 +4,7 @@ import {
|
||||
AuthenticatedContainerInnerTop,
|
||||
CustomButton,
|
||||
} from '../../styles/App-styles';
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import { Box, CircularProgress, useTheme } from '@mui/material';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
@ -39,6 +39,7 @@ export const AnnouncementDiscussion = ({
|
||||
myName,
|
||||
isPrivate,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isFocusedParent, setIsFocusedParent] = useState(false);
|
||||
@ -212,6 +213,7 @@ export const AnnouncementDiscussion = ({
|
||||
getData({ name: data.name, identifier: data.identifier }, isPrivate);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
||||
@ -274,19 +276,19 @@ export const AnnouncementDiscussion = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: isMobile ? '100%' : '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: isMobile ? '100%' : '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AuthenticatedContainerInnerTop
|
||||
@ -301,6 +303,7 @@ export const AnnouncementDiscussion = ({
|
||||
}}
|
||||
/>
|
||||
</AuthenticatedContainerInnerTop>
|
||||
|
||||
<Spacer height="20px" />
|
||||
</div>
|
||||
<AnnouncementList
|
||||
@ -314,30 +317,27 @@ export const AnnouncementDiscussion = ({
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
maxHeight: isMobile ? 'auto' : '400px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
maxHeight: '400px',
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// height: '100%',
|
||||
flexGrow: isMobile && 1,
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
@ -353,11 +353,11 @@ export const AnnouncementDiscussion = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100&',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100&',
|
||||
}}
|
||||
>
|
||||
{isFocusedParent && (
|
||||
@ -369,13 +369,13 @@ export const AnnouncementDiscussion = ({
|
||||
// Unfocus the editor
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: 'red',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
background: 'red',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{` Close`}
|
||||
@ -387,25 +387,25 @@ export const AnnouncementDiscussion = ({
|
||||
publishComment();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: theme.palette.background.default,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,173 +1,206 @@
|
||||
import { Message } from "@chatscope/chat-ui-kit-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
import { MessageDisplay } from "./MessageDisplay";
|
||||
import { Avatar, Box, Typography } from "@mui/material";
|
||||
import { formatTimestamp } from "../../utils/time";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { MessageDisplay } from './MessageDisplay';
|
||||
import { Avatar, Box, Typography, useTheme } from '@mui/material';
|
||||
import { formatTimestamp } from '../../utils/time';
|
||||
import ChatBubbleIcon from '@mui/icons-material/ChatBubble';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import { getBaseApi } from "../../background";
|
||||
import { requestQueueCommentCount } from "./GroupAnnouncements";
|
||||
import { CustomLoader } from "../../common/CustomLoader";
|
||||
import { getArbitraryEndpointReact, getBaseApiReact } from "../../App";
|
||||
import { WrapperUserAction } from "../WrapperUserAction";
|
||||
export const AnnouncementItem = ({ message, messageData, setSelectedAnnouncement, disableComment, myName }) => {
|
||||
import { getBaseApi } from '../../background';
|
||||
import { requestQueueCommentCount } from './GroupAnnouncements';
|
||||
import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { getArbitraryEndpointReact, getBaseApiReact } from '../../App';
|
||||
import { WrapperUserAction } from '../WrapperUserAction';
|
||||
|
||||
const [commentLength, setCommentLength] = useState(0)
|
||||
const getNumberOfComments = React.useCallback(
|
||||
async () => {
|
||||
try {
|
||||
const offset = 0;
|
||||
export const AnnouncementItem = ({
|
||||
message,
|
||||
messageData,
|
||||
setSelectedAnnouncement,
|
||||
disableComment,
|
||||
myName,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [commentLength, setCommentLength] = useState(0);
|
||||
const getNumberOfComments = React.useCallback(async () => {
|
||||
try {
|
||||
const offset = 0;
|
||||
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const identifier = `cm-${message.identifier}`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
|
||||
|
||||
const response = await requestQueueCommentCount.enqueue(() => {
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
})
|
||||
const responseData = await response.json();
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const identifier = `cm-${message.identifier}`;
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=0&includemetadata=false&offset=${offset}&reverse=true&prefix=true`;
|
||||
|
||||
const response = await requestQueueCommentCount.enqueue(() => {
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
const responseData = await response.json();
|
||||
|
||||
setCommentLength(responseData?.length);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (disableComment) return;
|
||||
getNumberOfComments();
|
||||
}, []);
|
||||
|
||||
setCommentLength(responseData?.length);
|
||||
|
||||
} catch (error) {
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
useEffect(()=> {
|
||||
if(disableComment) return
|
||||
getNumberOfComments()
|
||||
}, [])
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "10px",
|
||||
backgroundColor: "#232428",
|
||||
borderRadius: "7px",
|
||||
width: "95%",
|
||||
display: "flex",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderRadius: '7px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '7px',
|
||||
flexDirection: 'column'
|
||||
padding: '10px',
|
||||
width: '95%',
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: '#27282c',
|
||||
color: 'white'
|
||||
}}
|
||||
alt={message?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "7px",
|
||||
width: '100%'
|
||||
display: 'flex',
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
<WrapperUserAction disabled={myName === message?.name} address={undefined} name={message?.name}>
|
||||
<Typography
|
||||
<WrapperUserAction
|
||||
disabled={myName === message?.name}
|
||||
address={undefined}
|
||||
name={message?.name}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
alt={message?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${message?.name}/qortal_avatar?async=true`}
|
||||
>
|
||||
{message?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
<Box
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: "Inter",
|
||||
color: "cadetBlue",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '7px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{message?.name}
|
||||
</Typography>
|
||||
</WrapperUserAction>
|
||||
{!messageData?.decryptedData && (
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{messageData?.decryptedData?.message && (
|
||||
<>
|
||||
{messageData?.type === "notification" ? (
|
||||
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
|
||||
) : (
|
||||
<MessageDisplay htmlContent={messageData?.decryptedData?.message} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<WrapperUserAction
|
||||
disabled={myName === message?.name}
|
||||
address={undefined}
|
||||
name={message?.name}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWight: 600,
|
||||
fontFamily: 'Inter',
|
||||
color: 'cadetBlue',
|
||||
}}
|
||||
>
|
||||
{message?.name}
|
||||
</Typography>
|
||||
</WrapperUserAction>
|
||||
{!messageData?.decryptedData && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CustomLoader />
|
||||
</Box>
|
||||
)}
|
||||
{messageData?.decryptedData?.message && (
|
||||
<>
|
||||
{messageData?.type === 'notification' ? (
|
||||
<MessageDisplay
|
||||
htmlContent={messageData?.decryptedData?.message}
|
||||
/>
|
||||
) : (
|
||||
<MessageDisplay
|
||||
htmlContent={messageData?.decryptedData?.message}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%'
|
||||
}}>
|
||||
<Typography sx={{
|
||||
fontSize: '14px',
|
||||
color: 'gray',
|
||||
fontFamily: 'Inter'
|
||||
}}>{formatTimestamp(message.created)}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.secondary,
|
||||
fontFamily: 'Inter',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
{formatTimestamp(message.created)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{!disableComment && (
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '20px',
|
||||
cursor: 'pointer',
|
||||
opacity: 0.4,
|
||||
borderTop: '1px solid white',
|
||||
|
||||
}} onClick={()=> setSelectedAnnouncement(message)}>
|
||||
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
gap: '25px',
|
||||
alignItems: 'center',
|
||||
|
||||
}}>
|
||||
<ChatBubbleIcon sx={{
|
||||
fontSize: '20px'
|
||||
}} />
|
||||
{commentLength ? (
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
|
||||
) : (
|
||||
<Typography sx={{
|
||||
fontSize: '14px'
|
||||
}}>Leave comment</Typography>
|
||||
)}
|
||||
|
||||
{!disableComment && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
borderTop: '1px solid white',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
opacity: 0.4,
|
||||
padding: '20px',
|
||||
width: '100%',
|
||||
}}
|
||||
onClick={() => setSelectedAnnouncement(message)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ChatBubbleIcon
|
||||
sx={{
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
{commentLength ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>{`${commentLength > 1 ? `${commentLength} comments` : `${commentLength} comment`}`}</Typography>
|
||||
) : (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
Leave comment
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<ArrowForwardIosIcon
|
||||
sx={{
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<ArrowForwardIosIcon sx={{
|
||||
fontSize: '20px'
|
||||
}} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,5 @@
|
||||
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
List,
|
||||
AutoSizer,
|
||||
CellMeasurerCache,
|
||||
CellMeasurer,
|
||||
} from 'react-virtualized';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { CellMeasurerCache } from 'react-virtualized';
|
||||
import { AnnouncementItem } from './AnnouncementItem';
|
||||
import { Box } from '@mui/material';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
@ -37,12 +32,12 @@ export const AnnouncementList = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
flexGrow: 1,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
@ -57,11 +52,11 @@ export const AnnouncementList = ({
|
||||
<div
|
||||
key={message?.identifier}
|
||||
style={{
|
||||
marginBottom: '10px',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginBottom: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AnnouncementItem
|
||||
@ -89,10 +84,10 @@ export const AnnouncementList = ({
|
||||
</AutoSizer> */}
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginTop: '25px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{showLoadMore && (
|
||||
|
@ -7,15 +7,10 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CreateCommonSecret } from './CreateCommonSecret';
|
||||
import { reusableGet } from '../../qdn/publish/pubish';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
decodeBase64ForUIChatMessages,
|
||||
objectToBase64,
|
||||
} from '../../qdn/encryption/group-encryption';
|
||||
import { ChatContainerComp } from './ChatContainer';
|
||||
import { ChatList } from './ChatList';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
import Tiptap from './TipTap';
|
||||
@ -38,7 +33,7 @@ import {
|
||||
subscribeToEvent,
|
||||
unsubscribeFromEvent,
|
||||
} from '../../utils/events';
|
||||
import { Box, ButtonBase, Divider, Typography } from '@mui/material';
|
||||
import { Box, ButtonBase, Divider, Typography, useTheme } from '@mui/material';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { ReplyPreview } from './MessageItem';
|
||||
import { ExitIcon } from '../../assets/Icons/ExitIcon';
|
||||
@ -1001,16 +996,18 @@ export const ChatGroup = ({
|
||||
setIsOpenQManager(true);
|
||||
}, []);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: isMobile ? '100%' : '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
left: hide && '-100000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'absolute' : 'relative',
|
||||
left: hide && '-100000px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ChatList
|
||||
@ -1035,40 +1032,38 @@ export const ChatGroup = ({
|
||||
{(!!secretKey || isPrivate === false) && (
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
overflow: !isMobile && 'auto',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
width: 'calc(100% - 100px)',
|
||||
justifyContent: 'flex-end',
|
||||
overflow: 'auto',
|
||||
width: 'calc(100% - 100px)',
|
||||
}}
|
||||
>
|
||||
{replyMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@ -1088,9 +1083,9 @@ export const ChatGroup = ({
|
||||
{onEditMessage && (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@ -1123,9 +1118,9 @@ export const ChatGroup = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -1141,11 +1136,11 @@ export const ChatGroup = ({
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100px',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100px',
|
||||
}}
|
||||
>
|
||||
<CustomButton
|
||||
@ -1154,26 +1149,28 @@ export const ChatGroup = ({
|
||||
sendMessage();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: isSending
|
||||
? theme.palette.background.default
|
||||
: theme.palette.background.paper,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
marginTop: 'auto',
|
||||
minWidth: 'auto',
|
||||
padding: '5px',
|
||||
width: '100px',
|
||||
minWidth: 'auto',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -1185,25 +1182,24 @@ export const ChatGroup = ({
|
||||
{isOpenQManager !== null && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
height: '600px',
|
||||
|
||||
maxHeight: '100vh',
|
||||
width: '400px',
|
||||
maxWidth: '100vw',
|
||||
backgroundColor: '#27282c',
|
||||
zIndex: 100,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
bottom: 0,
|
||||
boxShadow: 4,
|
||||
display: hideView
|
||||
? 'none'
|
||||
: isOpenQManager === true
|
||||
? 'block'
|
||||
: 'none',
|
||||
boxShadow: 4,
|
||||
height: '600px',
|
||||
maxHeight: '100vh',
|
||||
maxWidth: '100vw',
|
||||
overflow: 'hidden',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
width: '400px',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -1214,12 +1210,11 @@ export const ChatGroup = ({
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
justifyContent: 'space-between',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Manager</Typography>
|
||||
@ -1251,12 +1246,14 @@ export const ChatGroup = ({
|
||||
)}
|
||||
|
||||
{/* <ChatContainerComp messages={formatMessages} /> */}
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: 'Loading chat... please wait.',
|
||||
}}
|
||||
/>
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
|
@ -1,35 +1,56 @@
|
||||
import { Box, Button, Typography } from '@mui/material'
|
||||
import React, { useContext } from 'react'
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Button, Typography, useTheme } from '@mui/material';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { MyContext, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues } from '../../App';
|
||||
import {
|
||||
MyContext,
|
||||
getArbitraryEndpointReact,
|
||||
getBaseApiReact,
|
||||
pauseAllQueues,
|
||||
} from '../../App';
|
||||
import { getFee } from '../../background';
|
||||
import { decryptResource, getGroupAdmins, validateSecretKey } from '../Group/Group';
|
||||
import {
|
||||
decryptResource,
|
||||
getGroupAdmins,
|
||||
validateSecretKey,
|
||||
} from '../Group/Group';
|
||||
import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
|
||||
export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, secretKeyDetails, userInfo, noSecretKey, setHideCommonKeyPopup, setIsForceShowCreationKeyPopup, isForceShowCreationKeyPopup}) => {
|
||||
export const CreateCommonSecret = ({
|
||||
groupId,
|
||||
secretKey,
|
||||
isOwner,
|
||||
myAddress,
|
||||
secretKeyDetails,
|
||||
userInfo,
|
||||
noSecretKey,
|
||||
setHideCommonKeyPopup,
|
||||
setIsForceShowCreationKeyPopup,
|
||||
isForceShowCreationKeyPopup,
|
||||
}) => {
|
||||
const { show, setTxList } = useContext(MyContext);
|
||||
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const getPublishesFromAdmins = async (admins: string[]) => {
|
||||
// const validApi = await findUsableApi();
|
||||
const queryString = admins.map((name) => `name=${name}`).join("&");
|
||||
const queryString = admins.map((name) => `name=${name}`).join('&');
|
||||
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=symmetric-qchat-group-${
|
||||
groupId
|
||||
}&exactmatchnames=true&limit=0&reverse=true&${queryString}&prefix=true`;
|
||||
const response = await fetch(url);
|
||||
if(!response.ok){
|
||||
throw new Error('network error')
|
||||
if (!response.ok) {
|
||||
throw new Error('network error');
|
||||
}
|
||||
const adminData = await response.json();
|
||||
|
||||
|
||||
const filterId = adminData.filter(
|
||||
(data: any) =>
|
||||
data.identifier === `symmetric-qchat-group-${groupId}`
|
||||
(data: any) => data.identifier === `symmetric-qchat-group-${groupId}`
|
||||
);
|
||||
if (filterId?.length === 0) {
|
||||
return false;
|
||||
@ -38,149 +59,182 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
|
||||
// Get the most recent date for both a and b
|
||||
const dateA = a.updated ? new Date(a.updated) : new Date(a.created);
|
||||
const dateB = b.updated ? new Date(b.updated) : new Date(b.created);
|
||||
|
||||
|
||||
// Sort by most recent
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
|
||||
|
||||
return sortedData[0];
|
||||
};
|
||||
const getSecretKey = async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => {
|
||||
|
||||
const getSecretKey = async (
|
||||
loadingGroupParam?: boolean,
|
||||
secretKeyToPublish?: boolean
|
||||
) => {
|
||||
try {
|
||||
pauseAllQueues()
|
||||
|
||||
|
||||
|
||||
const {names} = await getGroupAdmins(groupId);
|
||||
if(!names.length){
|
||||
throw new Error('Network error')
|
||||
pauseAllQueues();
|
||||
|
||||
const { names } = await getGroupAdmins(groupId);
|
||||
if (!names.length) {
|
||||
throw new Error('Network error');
|
||||
}
|
||||
const publish = await getPublishesFromAdmins(names);
|
||||
|
||||
|
||||
|
||||
if (publish === false) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const res = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
||||
publish.identifier
|
||||
}?encoding=base64&rebuild=true`
|
||||
);
|
||||
const data = await res.text();
|
||||
|
||||
|
||||
const decryptedKey: any = await decryptResource(data);
|
||||
|
||||
|
||||
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||
|
||||
|
||||
if (!validateSecretKey(decryptedKeyToObject))
|
||||
throw new Error("SecretKey is not valid");
|
||||
|
||||
throw new Error('SecretKey is not valid');
|
||||
|
||||
if (decryptedKeyToObject) {
|
||||
|
||||
return decryptedKeyToObject;
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
|
||||
} finally {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const createCommonSecret = async ()=> {
|
||||
try {
|
||||
const fee = await getFee('ARBITRARY')
|
||||
await show({
|
||||
message: "Would you like to perform an ARBITRARY transaction?" ,
|
||||
publishFee: fee.fee + ' QORT'
|
||||
})
|
||||
setIsLoading(true)
|
||||
const createCommonSecret = async () => {
|
||||
try {
|
||||
const fee = await getFee('ARBITRARY');
|
||||
await show({
|
||||
message: 'Would you like to perform an ARBITRARY transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoading(true);
|
||||
|
||||
const secretKey2 = await getSecretKey()
|
||||
if((!secretKey2 && secretKey2 !== false)) throw new Error('invalid secret key')
|
||||
if (secretKey2 && !validateSecretKey(secretKey2)) throw new Error('invalid secret key')
|
||||
const secretKey2 = await getSecretKey();
|
||||
if (!secretKey2 && secretKey2 !== false)
|
||||
throw new Error('invalid secret key');
|
||||
if (secretKey2 && !validateSecretKey(secretKey2))
|
||||
throw new Error('invalid secret key');
|
||||
|
||||
const secretKeyToSend = !secretKey2 ? null : secretKey2
|
||||
|
||||
|
||||
window.sendMessage("encryptAndPublishSymmetricKeyGroupChat", {
|
||||
groupId: groupId,
|
||||
previousData: secretKeyToSend,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message: "Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.",
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-common-secret',
|
||||
label: `Published secret key for group ${groupId}: awaiting confirmation`,
|
||||
labelDone: `Published secret key for group ${groupId}: success!`,
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsForceShowCreationKeyPopup(false)
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to encrypt and publish symmetric key for group chat:", error.message || "An error occurred");
|
||||
setIsLoading(false);
|
||||
const secretKeyToSend = !secretKey2 ? null : secretKey2;
|
||||
|
||||
window
|
||||
.sendMessage('encryptAndPublishSymmetricKeyGroupChat', {
|
||||
groupId: groupId,
|
||||
previousData: secretKeyToSend,
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully re-encrypted secret key. It may take a couple of minutes for the changes to propagate. Refresh the group in 5 mins.',
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-common-secret',
|
||||
label: `Published secret key for group ${groupId}: awaiting confirmation`,
|
||||
labelDone: `Published secret key for group ${groupId}: success!`,
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setTimeout(() => {
|
||||
setIsForceShowCreationKeyPopup(false);
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
'Failed to encrypt and publish symmetric key for group chat:',
|
||||
error.message || 'An error occurred'
|
||||
);
|
||||
setIsLoading(false);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
padding: '25px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
maxWidth: '350px',
|
||||
background: '#444444'
|
||||
}}>
|
||||
<LoadingButton loading={isLoading} loadingPosition="start" color="warning" variant='contained' onClick={createCommonSecret}>Re-encrypt key</LoadingButton>
|
||||
{noSecretKey ? (
|
||||
<Box>
|
||||
<Typography>There is no group secret key. Be the first admin to publish one!</Typography>
|
||||
</Box>
|
||||
) : isOwner && secretKeyDetails && userInfo?.name && userInfo.name !== secretKeyDetails?.name ? (
|
||||
<Box>
|
||||
<Typography>The latest group secret key was published by a non-owner. As the owner of the group please re-encrypt the key as a safeguard</Typography>
|
||||
</Box>
|
||||
): isForceShowCreationKeyPopup ? null : (
|
||||
<Box>
|
||||
<Typography>The group member list has changed. Please re-encrypt the secret key.</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{
|
||||
<Box
|
||||
sx={{
|
||||
background: theme.palette.background.default,
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-end'
|
||||
}}>
|
||||
<Button onClick={()=> {
|
||||
setHideCommonKeyPopup(true)
|
||||
setIsForceShowCreationKeyPopup(false)
|
||||
}} size='small'>Hide</Button>
|
||||
flexDirection: 'column',
|
||||
gap: '25px',
|
||||
maxWidth: '350px',
|
||||
padding: '25px',
|
||||
}}
|
||||
>
|
||||
<LoadingButton
|
||||
loading={isLoading}
|
||||
loadingPosition="start"
|
||||
color="warning"
|
||||
variant="contained"
|
||||
onClick={createCommonSecret}
|
||||
>
|
||||
Re-encrypt key
|
||||
</LoadingButton>
|
||||
|
||||
{noSecretKey ? (
|
||||
<Box>
|
||||
<Typography>
|
||||
There is no group secret key. Be the first admin to publish one!
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isOwner &&
|
||||
secretKeyDetails &&
|
||||
userInfo?.name &&
|
||||
userInfo.name !== secretKeyDetails?.name ? (
|
||||
<Box>
|
||||
<Typography>
|
||||
The latest group secret key was published by a non-owner. As the
|
||||
owner of the group please re-encrypt the key as a safeguard
|
||||
</Typography>
|
||||
</Box>
|
||||
) : isForceShowCreationKeyPopup ? null : (
|
||||
<Box>
|
||||
<Typography>
|
||||
The group member list has changed. Please re-encrypt the secret key.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setHideCommonKeyPopup(true);
|
||||
setIsForceShowCreationKeyPopup(false);
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
Hide
|
||||
</Button>
|
||||
</Box>
|
||||
<CustomizedSnackbars open={openSnack} setOpen={setOpenSnack} info={infoSnack} setInfo={setInfoSnack} />
|
||||
|
||||
<CustomizedSnackbars
|
||||
open={openSnack}
|
||||
setOpen={setOpenSnack}
|
||||
info={infoSnack}
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -5,31 +5,22 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CreateCommonSecret } from './CreateCommonSecret';
|
||||
import { reusableGet } from '../../qdn/publish/pubish';
|
||||
import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
|
||||
import {
|
||||
base64ToUint8Array,
|
||||
objectToBase64,
|
||||
} from '../../qdn/encryption/group-encryption';
|
||||
import { ChatContainerComp } from './ChatContainer';
|
||||
import { ChatList } from './ChatList';
|
||||
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
|
||||
import Tiptap from './TipTap';
|
||||
import {
|
||||
AuthenticatedContainerInnerTop,
|
||||
CustomButton,
|
||||
} from '../../styles/App-styles';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import { AnnouncementList } from './AnnouncementList';
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { AnnouncementDiscussion } from './AnnouncementDiscussion';
|
||||
import {
|
||||
MyContext,
|
||||
@ -42,9 +33,11 @@ import {
|
||||
import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group/Group';
|
||||
import { getRootHeight } from '../../utils/mobile/mobileUtils';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const requestQueueCommentCount = new RequestQueueWithPromise(3);
|
||||
|
||||
export const requestQueuePublishedAccouncements = new RequestQueueWithPromise(
|
||||
3
|
||||
);
|
||||
@ -125,6 +118,7 @@ export const handleUnencryptedPublishes = (publishes) => {
|
||||
});
|
||||
return publishesData;
|
||||
};
|
||||
|
||||
export const GroupAnnouncements = ({
|
||||
selectedGroup,
|
||||
secretKey,
|
||||
@ -264,6 +258,7 @@ export const GroupAnnouncements = ({
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clearEditorContent = () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.chain().focus().clearContent().run();
|
||||
@ -301,10 +296,12 @@ export const GroupAnnouncements = ({
|
||||
try {
|
||||
pauseAllQueues();
|
||||
const fee = await getFee('ARBITRARY');
|
||||
|
||||
await show({
|
||||
message: 'Would you like to perform a ARBITRARY transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
if (isSending) return;
|
||||
if (editorRef.current) {
|
||||
const htmlContent = editorRef.current.getHTML();
|
||||
@ -387,8 +384,7 @@ export const GroupAnnouncements = ({
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
[secretKey]
|
||||
@ -437,6 +433,8 @@ export const GroupAnnouncements = ({
|
||||
|
||||
const interval = useRef<any>(null);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const checkNewMessages = React.useCallback(async () => {
|
||||
try {
|
||||
const identifier = `grp-${selectedGroup}-anc-`;
|
||||
@ -485,7 +483,7 @@ export const GroupAnnouncements = ({
|
||||
}
|
||||
setAnnouncements((prev) => [...newArray, ...prev]);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
console.log(error);
|
||||
}
|
||||
}, [announcements, secretKey, selectedGroup]);
|
||||
|
||||
@ -537,10 +535,10 @@ export const GroupAnnouncements = ({
|
||||
: 'calc(100vh - 70px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
visibility: hide && 'hidden',
|
||||
position: hide && 'fixed',
|
||||
left: hide && '-1000px',
|
||||
position: hide && 'fixed',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<AnnouncementDiscussion
|
||||
@ -560,54 +558,54 @@ export const GroupAnnouncements = ({
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
visibility: hide && 'hidden',
|
||||
position: hide && 'fixed',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
position: hide && 'fixed',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: isMobile ? '8px' : '25px',
|
||||
fontSize: isMobile ? '16px' : '20px',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
fontSize: '20px',
|
||||
gap: '20px',
|
||||
justifyContent: 'center',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
fontSize: isMobile ? '16px' : '30px',
|
||||
fontSize: '30px',
|
||||
}}
|
||||
/>
|
||||
Group Announcements
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Spacer height={isMobile ? '0px' : '25px'} />
|
||||
<Spacer height={'25px'} />
|
||||
</div>
|
||||
|
||||
{!isLoading && combinedListTempAndReal?.length === 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -634,31 +632,28 @@ export const GroupAnnouncements = ({
|
||||
{isAdmin && (
|
||||
<div
|
||||
style={{
|
||||
// position: 'fixed',
|
||||
// bottom: '0px',
|
||||
backgroundColor: '#232428',
|
||||
minHeight: isMobile ? '0px' : '150px',
|
||||
maxHeight: isMobile ? 'auto' : '400px',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
padding: isMobile ? '10px' : '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
bottom: isFocusedParent ? '0px' : 'unset',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
flexShrink: 0,
|
||||
maxHeight: '400px',
|
||||
minHeight: '150px',
|
||||
overflow: 'hidden',
|
||||
padding: '20px',
|
||||
position: isFocusedParent ? 'fixed' : 'relative',
|
||||
top: isFocusedParent ? '0px' : 'unset',
|
||||
width: '100%',
|
||||
zIndex: isFocusedParent ? 5 : 'unset',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: isMobile && 1,
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
// height: '100%',
|
||||
}}
|
||||
>
|
||||
<Tiptap
|
||||
@ -670,14 +665,15 @@ export const GroupAnnouncements = ({
|
||||
setIsFocusedParent={setIsFocusedParent}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
width: '100&',
|
||||
flexShrink: 0,
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
width: '100&',
|
||||
}}
|
||||
>
|
||||
{isFocusedParent && (
|
||||
@ -692,43 +688,46 @@ export const GroupAnnouncements = ({
|
||||
// Unfocus the editor
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: 'var(--danger)',
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{` Close`}
|
||||
</CustomButton>
|
||||
)}
|
||||
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
if (isSending) return;
|
||||
publishAnnouncement();
|
||||
}}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
alignSelf: 'center',
|
||||
background: isSending
|
||||
? theme.palette.background.default
|
||||
: theme.palette.background.paper,
|
||||
cursor: isSending ? 'default' : 'pointer',
|
||||
background: isSending && 'rgba(0, 0, 0, 0.8)',
|
||||
flexShrink: 0,
|
||||
padding: isMobile && '5px',
|
||||
fontSize: isMobile && '14px',
|
||||
fontSize: '14px',
|
||||
marginTop: 'auto',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
{isSending && (
|
||||
<CircularProgress
|
||||
size={18}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
left: '50%',
|
||||
marginLeft: '-12px',
|
||||
marginTop: '-12px',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
marginTop: '-12px',
|
||||
marginLeft: '-12px',
|
||||
color: 'white',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,19 +1,6 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { GroupMail } from "../Group/Forum/GroupMail";
|
||||
import { MyContext, isMobile } from "../../App";
|
||||
import { getRootHeight } from "../../utils/mobile/mobileUtils";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { GroupMail } from '../Group/Forum/GroupMail';
|
||||
import { MyContext, isMobile } from '../../App';
|
||||
|
||||
export const GroupForum = ({
|
||||
selectedGroup,
|
||||
@ -23,12 +10,13 @@ export const GroupForum = ({
|
||||
isAdmin,
|
||||
myAddress,
|
||||
hide,
|
||||
defaultThread,
|
||||
defaultThread,
|
||||
setDefaultThread,
|
||||
isPrivate
|
||||
isPrivate,
|
||||
}) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const [isMoved, setIsMoved] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hide) {
|
||||
setTimeout(() => setIsMoved(true), 300); // Wait for the fade-out to complete before moving
|
||||
@ -39,20 +27,27 @@ export const GroupForum = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
// reference to change height
|
||||
height: isMobile ? `calc(${rootHeight} - 127px` : "calc(100vh - 70px)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
opacity: hide ? 0 : 1,
|
||||
visibility: hide && 'hidden',
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
left: hide && '-1000px'
|
||||
}}
|
||||
>
|
||||
<GroupMail isPrivate={isPrivate} hide={hide} getSecretKey={getSecretKey} selectedGroup={selectedGroup} userInfo={userInfo} secretKey={secretKey} defaultThread={defaultThread} setDefaultThread={setDefaultThread} />
|
||||
|
||||
</div>
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 'calc(100vh - 70px)',
|
||||
left: hide && '-1000px',
|
||||
opacity: hide ? 0 : 1,
|
||||
position: hide ? 'fixed' : 'relative',
|
||||
visibility: hide && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<GroupMail
|
||||
isPrivate={isPrivate}
|
||||
hide={hide}
|
||||
getSecretKey={getSecretKey}
|
||||
selectedGroup={selectedGroup}
|
||||
userInfo={userInfo}
|
||||
secretKey={secretKey}
|
||||
defaultThread={defaultThread}
|
||||
setDefaultThread={setDefaultThread}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,69 +1,68 @@
|
||||
import React, {
|
||||
forwardRef, useEffect, useImperativeHandle,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
const selectItem = index => {
|
||||
const item = props.items[index]
|
||||
|
||||
if (item) {
|
||||
props.command(item)
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index) => {
|
||||
const item = props.items[index];
|
||||
|
||||
if (item) {
|
||||
props.command(item);
|
||||
}
|
||||
};
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex(
|
||||
(selectedIndex + props.items.length - 1) % props.items.length
|
||||
);
|
||||
};
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
||||
};
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex);
|
||||
};
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
|
||||
}
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length)
|
||||
}
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex)
|
||||
}
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="dropdown-menu">
|
||||
{props.items.length
|
||||
? props.items.map((item, index) => (
|
||||
<button
|
||||
className={index === selectedIndex ? 'is-selected' : ''}
|
||||
key={item.id || index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))
|
||||
: <div className="item">No result</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="dropdown-menu">
|
||||
{props.items.length ? (
|
||||
props.items.map((item, index) => (
|
||||
<button
|
||||
className={index === selectedIndex ? 'is-selected' : ''}
|
||||
key={item.id || index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="item">No result</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { NodeViewWrapper } from '@tiptap/react';
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
const ResizableImage = ({ node, updateAttributes, selected }) => {
|
||||
const imgRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const startResizing = (e) => {
|
||||
e.preventDefault();
|
||||
@ -40,18 +42,23 @@ const ResizableImage = ({ node, updateAttributes, selected }) => {
|
||||
src={node.attrs.src}
|
||||
alt={node.attrs.alt || ''}
|
||||
title={node.attrs.title || ''}
|
||||
style={{ width: node.attrs.width || 'auto', display: 'block', margin: '0 auto' }}
|
||||
style={{
|
||||
width: node.attrs.width || 'auto',
|
||||
display: 'block',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
draggable={false} // Prevent image dragging
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
bottom: 0,
|
||||
cursor: 'nwse-resize',
|
||||
height: '10px',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
backgroundColor: 'gray',
|
||||
cursor: 'nwse-resize',
|
||||
zIndex: 1, // Ensure the resize handle is above other content
|
||||
}}
|
||||
onMouseDown={startResizing}
|
||||
|
Loading…
x
Reference in New Issue
Block a user