fix: fix save as menu item + bump react-contexify to 6.0.0
This commit is contained in:
parent
eb4631ff99
commit
9199c7b529
|
@ -109,7 +109,7 @@
|
||||||
"protobufjs": "^7.2.4",
|
"protobufjs": "^7.2.4",
|
||||||
"rc-slider": "^10.2.1",
|
"rc-slider": "^10.2.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-contexify": "5.0.0",
|
"react-contexify": "^6.0.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-draggable": "^4.4.4",
|
"react-draggable": "^4.4.4",
|
||||||
"react-h5-audio-player": "^3.2.0",
|
"react-h5-audio-player": "^3.2.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Modules
|
// Modules
|
||||||
@import 'react-h5-audio-player/lib/styles.css';
|
@import 'react-h5-audio-player/lib/styles.css';
|
||||||
@import 'react-contexify/dist/ReactContexify.min.css';
|
@import 'react-contexify/dist/ReactContexify.css'; // the .css is actually referencing the .min.css in the 6.0.0
|
||||||
@import 'react-toastify/dist/ReactToastify.css';
|
@import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
@import 'sanitize.css/sanitize.css';
|
@import 'sanitize.css/sanitize.css';
|
||||||
|
|
|
@ -3,36 +3,35 @@ import styled from 'styled-components';
|
||||||
export const SessionContextMenuContainer = styled.div.attrs({
|
export const SessionContextMenuContainer = styled.div.attrs({
|
||||||
// custom props
|
// custom props
|
||||||
})`
|
})`
|
||||||
.react-contexify {
|
.contexify {
|
||||||
// be sure it is more than the one set for the More Information screen of messages
|
// be sure it is more than the one set for the More Information screen of messages
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
box-shadow: 0px 0px 10px var(--context-menu-shadow-color) !important;
|
box-shadow: 0px 0px 10px var(--context-menu-shadow-color) !important;
|
||||||
background-color: var(--context-menu-background-color);
|
background-color: var(--context-menu-background-color);
|
||||||
|
|
||||||
&.react-contexify__theme--dark {
|
&.contexify_theme-dark {
|
||||||
background-color: var(--context-menu-background-color);
|
background-color: var(--context-menu-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-contexify__item {
|
.contexify_item {
|
||||||
background: var(--context-menu-background-color);
|
background: var(--context-menu-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-contexify__item:not(.react-contexify__item--disabled):hover
|
.contexify_item:not(.react-contexify_item-disabled):hover > .contexify_itemContent {
|
||||||
> .react-contexify__item__content {
|
|
||||||
background: var(--context-menu-background-hover-color);
|
background: var(--context-menu-background-hover-color);
|
||||||
color: var(--context-menu-text-hover-color);
|
color: var(--context-menu-text-hover-color);
|
||||||
}
|
}
|
||||||
.react-contexify__item__content {
|
.contexify_itemContent {
|
||||||
transition: var(--default-duration);
|
transition: var(--default-duration);
|
||||||
color: var(--context-menu-text-color);
|
color: var(--context-menu-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.react-contexify__submenu {
|
&.contexify_submenu {
|
||||||
top: -28px !important; // height of an item element
|
top: -28px !important; // height of an item element
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-contexify__submenu-arrow {
|
.contexify_submenu-arrow {
|
||||||
line-height: 16px; // center the arrow for submenu
|
line-height: 16px; // center the arrow for submenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { animation, contextMenu, Item, Menu } from 'react-contexify';
|
import { contextMenu, Item, Menu } from 'react-contexify';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const VideoInputMenu = ({
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
{camerasList.map(m => {
|
{camerasList.map(m => {
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
|
@ -93,7 +93,7 @@ const AudioInputMenu = ({
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
{audioInputsList.map(m => {
|
{audioInputsList.map(m => {
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
|
@ -162,7 +162,7 @@ const AudioOutputMenu = ({
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
{audioOutputsList.map(m => {
|
{audioOutputsList.map(m => {
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { animation, Item, Menu, useContextMenu } from 'react-contexify';
|
import { Item, ItemParams, Menu, useContextMenu } from 'react-contexify';
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useClickAway, useMouse } from 'react-use';
|
import { useClickAway, useMouse } from 'react-use';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
import { Data } from '../../../../data/data';
|
import { Data } from '../../../../data/data';
|
||||||
|
|
||||||
import { MessageInteraction } from '../../../../interactions';
|
import { MessageInteraction } from '../../../../interactions';
|
||||||
|
@ -103,49 +103,6 @@ const DeleteForEveryone = ({ messageId }: { messageId: string }) => {
|
||||||
|
|
||||||
type MessageId = { messageId: string };
|
type MessageId = { messageId: string };
|
||||||
|
|
||||||
const SaveAttachment = ({ messageId }: MessageId) => {
|
|
||||||
const convoId = useSelectedConversationKey();
|
|
||||||
const attachments = useMessageAttachments(messageId);
|
|
||||||
const timestamp = useMessageTimestamp(messageId);
|
|
||||||
const serverTimestamp = useMessageServerTimestamp(messageId);
|
|
||||||
|
|
||||||
const sender = useMessageSender(messageId);
|
|
||||||
const saveAttachment = useCallback(
|
|
||||||
(e: any) => {
|
|
||||||
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
|
|
||||||
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
|
|
||||||
let targetAttachmentIndex = e.triggerEvent.path[1].getAttribute('data-attachmentindex');
|
|
||||||
e.event.stopPropagation();
|
|
||||||
if (!attachments?.length || !convoId || !sender) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetAttachmentIndex) {
|
|
||||||
targetAttachmentIndex = 0;
|
|
||||||
}
|
|
||||||
if (targetAttachmentIndex > attachments.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const messageTimestamp = timestamp || serverTimestamp || 0;
|
|
||||||
void saveAttachmentToDisk({
|
|
||||||
attachment: attachments[targetAttachmentIndex],
|
|
||||||
messageTimestamp,
|
|
||||||
messageSender: sender,
|
|
||||||
conversationId: convoId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[convoId, sender, attachments, serverTimestamp, timestamp]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!convoId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return attachments?.length ? (
|
|
||||||
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdminActionItems = ({ messageId }: MessageId) => {
|
const AdminActionItems = ({ messageId }: MessageId) => {
|
||||||
const convoId = useSelectedConversationKey();
|
const convoId = useSelectedConversationKey();
|
||||||
const isPublic = useSelectedIsPublic();
|
const isPublic = useSelectedIsPublic();
|
||||||
|
@ -218,6 +175,11 @@ export const MessageContextMenu = (props: Props) => {
|
||||||
const status = useMessageStatus(messageId);
|
const status = useMessageStatus(messageId);
|
||||||
const isDeletable = useMessageIsDeletable(messageId);
|
const isDeletable = useMessageIsDeletable(messageId);
|
||||||
const text = useMessageBody(messageId);
|
const text = useMessageBody(messageId);
|
||||||
|
const attachments = useMessageAttachments(messageId);
|
||||||
|
const timestamp = useMessageTimestamp(messageId);
|
||||||
|
const serverTimestamp = useMessageServerTimestamp(messageId);
|
||||||
|
|
||||||
|
const sender = useMessageSender(messageId);
|
||||||
|
|
||||||
const isOutgoing = direction === 'outgoing';
|
const isOutgoing = direction === 'outgoing';
|
||||||
const isSent = status === 'sent' || status === 'read'; // a read message should be replyable
|
const isSent = status === 'sent' || status === 'read'; // a read message should be replyable
|
||||||
|
@ -233,21 +195,24 @@ export const MessageContextMenu = (props: Props) => {
|
||||||
const [mouseX, setMouseX] = useState(0);
|
const [mouseX, setMouseX] = useState(0);
|
||||||
const [mouseY, setMouseY] = useState(0);
|
const [mouseY, setMouseY] = useState(0);
|
||||||
|
|
||||||
const onContextMenuShown = () => {
|
const onVisibilityChange = useCallback(
|
||||||
if (showEmojiPanel) {
|
(isVisible: boolean) => {
|
||||||
setShowEmojiPanel(false);
|
if (isVisible) {
|
||||||
}
|
if (showEmojiPanel) {
|
||||||
window.contextMenuShown = true;
|
setShowEmojiPanel(false);
|
||||||
};
|
}
|
||||||
|
window.contextMenuShown = true;
|
||||||
const onContextMenuHidden = useCallback(() => {
|
return;
|
||||||
// This function will called before the click event
|
}
|
||||||
// on the message would trigger (and I was unable to
|
// This function will called before the click event
|
||||||
// prevent propagation in this case), so use a short timeout
|
// on the message would trigger (and I was unable to
|
||||||
setTimeout(() => {
|
// prevent propagation in this case), so use a short timeout
|
||||||
window.contextMenuShown = false;
|
setTimeout(() => {
|
||||||
}, 100);
|
window.contextMenuShown = false;
|
||||||
}, []);
|
}, 100);
|
||||||
|
},
|
||||||
|
[showEmojiPanel]
|
||||||
|
);
|
||||||
|
|
||||||
const onShowDetail = async () => {
|
const onShowDetail = async () => {
|
||||||
const found = await Data.getMessageById(messageId);
|
const found = await Data.getMessageById(messageId);
|
||||||
|
@ -313,6 +278,29 @@ export const MessageContextMenu = (props: Props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveAttachment = (e: ItemParams) => {
|
||||||
|
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
|
||||||
|
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
|
||||||
|
const targetAttachmentIndex = isNumber(e?.props?.dataAttachmentIndex)
|
||||||
|
? e.props.dataAttachmentIndex
|
||||||
|
: 0;
|
||||||
|
e.event.stopPropagation();
|
||||||
|
if (!attachments?.length || !convoId || !sender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAttachmentIndex > attachments.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messageTimestamp = timestamp || serverTimestamp || 0;
|
||||||
|
void saveAttachmentToDisk({
|
||||||
|
attachment: attachments[targetAttachmentIndex],
|
||||||
|
messageTimestamp,
|
||||||
|
messageSender: sender,
|
||||||
|
conversationId: convoId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useClickAway(emojiPanelRef, () => {
|
useClickAway(emojiPanelRef, () => {
|
||||||
onEmojiLoseFocus();
|
onEmojiLoseFocus();
|
||||||
});
|
});
|
||||||
|
@ -360,18 +348,14 @@ export const MessageContextMenu = (props: Props) => {
|
||||||
</StyledEmojiPanelContainer>
|
</StyledEmojiPanelContainer>
|
||||||
)}
|
)}
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu
|
<Menu id={contextMenuId} onVisibilityChange={onVisibilityChange} animation="fade">
|
||||||
id={contextMenuId}
|
|
||||||
onShown={onContextMenuShown}
|
|
||||||
onHidden={onContextMenuHidden}
|
|
||||||
animation={animation.fade}
|
|
||||||
>
|
|
||||||
{enableReactions && (
|
{enableReactions && (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
<MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} />
|
<MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} />
|
||||||
)}
|
)}
|
||||||
<SaveAttachment messageId={messageId} />
|
{attachments?.length ? (
|
||||||
|
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
|
||||||
|
) : null}
|
||||||
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
|
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
|
||||||
{(isSent || !isOutgoing) && (
|
{(isSent || !isOutgoing) && (
|
||||||
<Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>
|
<Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import styled, { keyframes } from 'styled-components';
|
||||||
import useInterval from 'react-use/lib/useInterval';
|
import useInterval from 'react-use/lib/useInterval';
|
||||||
import useMount from 'react-use/lib/useMount';
|
import useMount from 'react-use/lib/useMount';
|
||||||
|
|
||||||
|
import { isNil, isString, toNumber } from 'lodash';
|
||||||
import { Data } from '../../../../data/data';
|
import { Data } from '../../../../data/data';
|
||||||
import { MessageRenderingProps } from '../../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import { getConversationController } from '../../../../session/conversations';
|
import { getConversationController } from '../../../../session/conversations';
|
||||||
|
@ -180,12 +181,22 @@ export const GenericReadableMessage = (props: Props) => {
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(e: React.MouseEvent<HTMLElement>) => {
|
(e: React.MouseEvent<HTMLElement>) => {
|
||||||
const enableContextMenu = !multiSelectMode && !msgProps?.isKickedFromGroup;
|
const enableContextMenu = !multiSelectMode && !msgProps?.isKickedFromGroup;
|
||||||
|
const attachmentIndexStr = (e?.target as any)?.parentElement?.getAttribute?.(
|
||||||
|
'data-attachmentindex'
|
||||||
|
);
|
||||||
|
const attachmentIndex =
|
||||||
|
isString(attachmentIndexStr) && !isNil(toNumber(attachmentIndexStr))
|
||||||
|
? toNumber(attachmentIndexStr)
|
||||||
|
: 0;
|
||||||
|
|
||||||
if (enableContextMenu) {
|
if (enableContextMenu) {
|
||||||
contextMenu.hideAll();
|
contextMenu.hideAll();
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
id: ctxMenuID,
|
id: ctxMenuID,
|
||||||
event: e,
|
event: e,
|
||||||
|
props: {
|
||||||
|
dataAttachmentIndex: attachmentIndex,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsRightClicked(enableContextMenu);
|
setIsRightClicked(enableContextMenu);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { animation, Item, Menu, Submenu } from 'react-contexify';
|
import { Item, Menu, Submenu } from 'react-contexify';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { setDisappearingMessagesByConvoId } from '../../interactions/conversationInteractions';
|
import { setDisappearingMessagesByConvoId } from '../../interactions/conversationInteractions';
|
||||||
import { isSearching } from '../../state/selectors/search';
|
import { isSearching } from '../../state/selectors/search';
|
||||||
|
@ -64,7 +64,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
||||||
return (
|
return (
|
||||||
<ContextConversationProvider value={convoId}>
|
<ContextConversationProvider value={convoId}>
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
<DisappearingMessageMenuItem />
|
<DisappearingMessageMenuItem />
|
||||||
<NotificationForConvoMenuItem />
|
<NotificationForConvoMenuItem />
|
||||||
<BlockMenuItem />
|
<BlockMenuItem />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { animation, Item, Menu } from 'react-contexify';
|
import { Item, Menu } from 'react-contexify';
|
||||||
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector';
|
import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector';
|
||||||
|
@ -44,7 +44,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
{/* Message request related actions */}
|
{/* Message request related actions */}
|
||||||
<AcceptMsgRequestMenuItem />
|
<AcceptMsgRequestMenuItem />
|
||||||
<DeclineMsgRequestMenuItem />
|
<DeclineMsgRequestMenuItem />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { animation, Item, Menu } from 'react-contexify';
|
import { Item, Menu } from 'react-contexify';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
|
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
|
||||||
|
@ -28,7 +28,7 @@ export const MessageRequestBannerContextMenu = (props: PropsContextConversationI
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionContextMenuContainer>
|
<SessionContextMenuContainer>
|
||||||
<Menu id={triggerId} animation={animation.fade}>
|
<Menu id={triggerId} animation="fade">
|
||||||
<HideBannerMenuItem />
|
<HideBannerMenuItem />
|
||||||
</Menu>
|
</Menu>
|
||||||
</SessionContextMenuContainer>
|
</SessionContextMenuContainer>
|
||||||
|
|
Loading…
Reference in New Issue