Merge pull request #3020 from yougotwill/fix/ses-1409/multiselect_border_shadow

SES 1409, 579, 714
pull/3023/head
Audric Ackermann 1 year ago committed by GitHub
commit 427b6ade3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -113,7 +113,7 @@
"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",
"react-intersection-observer": "^8.30.3", "react-intersection-observer": "^9.7.0",
"react-mentions": "^4.4.9", "react-mentions": "^4.4.9",
"react-qr-svg": "^2.2.1", "react-qr-svg": "^2.2.1",
"react-redux": "8.0.4", "react-redux": "8.0.4",

@ -134,7 +134,6 @@ textarea {
.module-message__container { .module-message__container {
position: relative; position: relative;
display: inline-block; display: inline-block;
overflow: hidden;
min-width: 30px; min-width: 30px;
// To limit messages with things forcing them wider, like long attachment names. // To limit messages with things forcing them wider, like long attachment names.
width: 100%; width: 100%;

@ -48,15 +48,14 @@ const CrownWrapper = styled.div`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: absolute; position: absolute;
bottom: 0%; bottom: 11%;
right: 12%; right: 11%;
height: 20px; height: 18px;
width: 20px; width: 18px;
transform: translate(20%, 20%); // getting over 23% creates a glitch transform: translate(20%, 20%); // getting over 23% creates a glitch
color: #f7c347; color: #f7c347;
background: var(--background-primary-color); background: var(--background-primary-color);
border-radius: 50%; border-radius: 50%;
box-shadow: var(--drop-shadow);
`; `;
export const CrownIcon = () => { export const CrownIcon = () => {

@ -5,6 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { setNextMessageToPlayId } from '../../state/ducks/conversations'; import { setNextMessageToPlayId } from '../../state/ducks/conversations';
import { useMessageSelected } from '../../state/selectors';
import { import {
getNextMessageToPlayId, getNextMessageToPlayId,
getSortedMessagesOfSelectedConversation, getSortedMessagesOfSelectedConversation,
@ -25,7 +26,7 @@ const StyledSpeedButton = styled.div`
} }
`; `;
export const StyledH5AudioPlayer = styled(H5AudioPlayer)` export const StyledH5AudioPlayer = styled(H5AudioPlayer)<{ dropShadow?: boolean }>`
&.rhap_container { &.rhap_container {
min-width: 220px; min-width: 220px;
padding: 0px; padding: 0px;
@ -145,6 +146,8 @@ export const StyledH5AudioPlayer = styled(H5AudioPlayer)`
height: 20px; height: 20px;
margin-right: 0px; margin-right: 0px;
} }
${props => props.dropShadow && 'box-shadow: var(--drop-shadow);'}
`; `;
export const AudioPlayerWithEncryptedFile = (props: { export const AudioPlayerWithEncryptedFile = (props: {
@ -162,6 +165,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
const messageProps = useSelector(getSortedMessagesOfSelectedConversation); const messageProps = useSelector(getSortedMessagesOfSelectedConversation);
const nextMessageToPlayId = useSelector(getNextMessageToPlayId); const nextMessageToPlayId = useSelector(getNextMessageToPlayId);
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
const selected = useMessageSelected(messageId);
const dataTestId = `audio-${messageId}`; const dataTestId = `audio-${messageId}`;
@ -263,6 +267,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
play: <SessionIcon iconType="play" iconSize="small" />, play: <SessionIcon iconType="play" iconSize="small" />,
pause: <SessionIcon iconType="pause" iconSize="small" />, pause: <SessionIcon iconType="pause" iconSize="small" />,
}} }}
dropShadow={selected}
/> />
); );
}; };

@ -24,6 +24,7 @@ type Props = {
playIconOverlay?: boolean; playIconOverlay?: boolean;
softCorners: boolean; softCorners: boolean;
forceSquare?: boolean; forceSquare?: boolean;
dropShadow?: boolean;
attachmentIndex?: number; attachmentIndex?: number;
onClick?: (attachment: AttachmentTypeWithPath | AttachmentType) => void; onClick?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
@ -55,6 +56,7 @@ export const Image = (props: Props) => {
playIconOverlay, playIconOverlay,
softCorners, softCorners,
forceSquare, forceSquare,
dropShadow,
attachmentIndex, attachmentIndex,
url, url,
width: _width, width: _width,
@ -101,6 +103,7 @@ export const Image = (props: Props) => {
maxWidth: width, maxWidth: width,
minHeight: height, minHeight: height,
minWidth: width, minWidth: width,
boxShadow: dropShadow ? 'var(--drop-shadow)' : undefined,
}} }}
data-attachmentindex={attachmentIndex} data-attachmentindex={attachmentIndex}
> >

@ -10,14 +10,16 @@ import {
isVideoAttachment, isVideoAttachment,
} from '../../types/Attachment'; } from '../../types/Attachment';
import { useMessageSelected } from '../../state/selectors';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
import { Image } from './Image'; import { Image } from './Image';
import { IsMessageVisibleContext } from './message/message-content/MessageContent'; import { IsMessageVisibleContext } from './message/message-content/MessageContent';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
type Props = { type Props = {
attachments: Array<AttachmentTypeWithPath>; attachments: Array<AttachmentTypeWithPath>;
onError: () => void; onError: () => void;
onClickAttachment?: (attachment: AttachmentTypeWithPath | AttachmentType) => void; onClickAttachment?: (attachment: AttachmentTypeWithPath | AttachmentType) => void;
messageId?: string;
}; };
const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>` const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>`
@ -28,7 +30,12 @@ const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>`
`; `;
const Row = ( const Row = (
props: Props & { renderedSize: number; startIndex: number; totalAttachmentsCount: number } props: Props & {
renderedSize: number;
startIndex: number;
totalAttachmentsCount: number;
selected: boolean;
}
) => { ) => {
const { const {
attachments, attachments,
@ -37,6 +44,7 @@ const Row = (
startIndex, startIndex,
onClickAttachment, onClickAttachment,
totalAttachmentsCount, totalAttachmentsCount,
selected,
} = props; } = props;
const isMessageVisible = useContext(IsMessageVisibleContext); const isMessageVisible = useContext(IsMessageVisibleContext);
const moreMessagesOverlay = totalAttachmentsCount > 3; const moreMessagesOverlay = totalAttachmentsCount > 3;
@ -61,6 +69,7 @@ const Row = (
softCorners={true} softCorners={true}
darkOverlay={showOverlay} darkOverlay={showOverlay}
overlayText={showOverlay ? moreMessagesOverlayText : undefined} overlayText={showOverlay ? moreMessagesOverlayText : undefined}
dropShadow={selected}
/> />
); );
})} })}
@ -69,7 +78,9 @@ const Row = (
}; };
export const ImageGrid = (props: Props) => { export const ImageGrid = (props: Props) => {
const { attachments, onError, onClickAttachment } = props; const { attachments, onError, onClickAttachment, messageId } = props;
const selected = useMessageSelected(messageId);
if (!attachments || !attachments.length) { if (!attachments || !attachments.length) {
return null; return null;
@ -85,6 +96,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE} renderedSize={THUMBNAIL_SIDE}
startIndex={0} startIndex={0}
totalAttachmentsCount={attachments.length} totalAttachmentsCount={attachments.length}
selected={selected}
/> />
</StyledImageGrid> </StyledImageGrid>
); );
@ -101,6 +113,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE} renderedSize={THUMBNAIL_SIDE}
startIndex={0} startIndex={0}
totalAttachmentsCount={attachments.length} totalAttachmentsCount={attachments.length}
selected={selected}
/> />
</StyledImageGrid> </StyledImageGrid>
); );
@ -118,6 +131,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={THUMBNAIL_SIDE} renderedSize={THUMBNAIL_SIDE}
startIndex={0} startIndex={0}
totalAttachmentsCount={attachments.length} totalAttachmentsCount={attachments.length}
selected={selected}
/> />
<StyledImageGrid flexDirection={'column'}> <StyledImageGrid flexDirection={'column'}>
@ -128,6 +142,7 @@ export const ImageGrid = (props: Props) => {
renderedSize={columnImageSide} renderedSize={columnImageSide}
startIndex={1} startIndex={1}
totalAttachmentsCount={attachments.length} totalAttachmentsCount={attachments.length}
selected={selected}
/> />
</StyledImageGrid> </StyledImageGrid>
</StyledImageGrid> </StyledImageGrid>

@ -51,8 +51,7 @@ type Props = SessionMessageListProps & {
scrollToNow: () => Promise<void>; scrollToNow: () => Promise<void>;
}; };
// isGroup is used to align the ExpireTimer with the member avatars const StyledMessagesContainer = styled.div`
const StyledMessagesContainer = styled.div<{ isGroup: boolean }>`
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
gap: var(--margins-sm); gap: var(--margins-sm);
@ -61,11 +60,8 @@ const StyledMessagesContainer = styled.div<{ isGroup: boolean }>`
overflow-x: hidden; overflow-x: hidden;
min-width: 370px; min-width: 370px;
scrollbar-width: 4px; scrollbar-width: 4px;
padding-top: var(--margins-sm); padding-top: var(--margins-sm);
padding-right: var(--margins-lg);
padding-bottom: var(--margins-xl); padding-bottom: var(--margins-xl);
padding-left: ${props => (props.isGroup ? 'var(--margins-xs)' : 'var(--margins-lg)')};
.session-icon-button { .session-icon-button {
display: flex; display: flex;
@ -126,7 +122,6 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
<StyledMessagesContainer <StyledMessagesContainer
className="messages-container" className="messages-container"
id={messageContainerDomID} id={messageContainerDomID}
isGroup={!conversation.isPrivate}
onScroll={this.handleScroll} onScroll={this.handleScroll}
ref={this.props.messageContainerRef} ref={this.props.messageContainerRef}
data-testid="messages-container" data-testid="messages-container"

@ -18,32 +18,26 @@ import {
} from '../../basic/SessionButton'; } from '../../basic/SessionButton';
import { SessionIconButton } from '../../icon'; import { SessionIconButton } from '../../icon';
function onDeleteSelectedMessagesForEveryone(
selectedConversationKey: string,
selectedMessageIds: Array<string>
) {
if (selectedConversationKey) {
void deleteMessagesByIdForEveryone(selectedMessageIds, selectedConversationKey);
}
}
export const SelectionOverlay = () => { export const SelectionOverlay = () => {
const selectedMessageIds = useSelector(getSelectedMessageIds); const selectedMessageIds = useSelector(getSelectedMessageIds);
const selectedConversationKey = useSelectedConversationKey(); const selectedConversationKey = useSelectedConversationKey();
const isPublic = useSelectedIsPublic(); const isPublic = useSelectedIsPublic();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { i18n } = window;
function onCloseOverlay() { function onCloseOverlay() {
dispatch(resetSelectedMessageIds()); dispatch(resetSelectedMessageIds());
} }
function onDeleteSelectedMessages() {
if (selectedConversationKey) {
void deleteMessagesById(selectedMessageIds, selectedConversationKey);
}
}
function onDeleteSelectedMessagesForEveryone() {
if (selectedConversationKey) {
void deleteMessagesByIdForEveryone(selectedMessageIds, selectedConversationKey);
}
}
const isOnlyServerDeletable = isPublic; const isOnlyServerDeletable = isPublic;
const deleteMessageButtonText = i18n('delete');
const deleteForEveryoneMessageButtonText = i18n('deleteForEveryone');
return ( return (
<div className="message-selection-overlay"> <div className="message-selection-overlay">
@ -52,21 +46,23 @@ export const SelectionOverlay = () => {
</div> </div>
<div className="button-group"> <div className="button-group">
{!isOnlyServerDeletable && (
<SessionButton
buttonColor={SessionButtonColor.Danger}
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
text={deleteMessageButtonText}
onClick={onDeleteSelectedMessages}
/>
)}
<SessionButton <SessionButton
buttonColor={SessionButtonColor.Danger} buttonColor={SessionButtonColor.Danger}
buttonShape={SessionButtonShape.Square} buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid} buttonType={SessionButtonType.Solid}
text={deleteForEveryoneMessageButtonText} text={window.i18n('delete')}
onClick={onDeleteSelectedMessagesForEveryone} onClick={() => {
if (selectedConversationKey) {
if (isOnlyServerDeletable) {
void onDeleteSelectedMessagesForEveryone(
selectedConversationKey,
selectedMessageIds
);
} else {
void deleteMessagesById(selectedMessageIds, selectedConversationKey);
}
}
}}
/> />
</div> </div>
</div> </div>

@ -1,9 +1,9 @@
import React, { useCallback, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { AttachmentSection } from './AttachmentSection'; import { AttachmentSection } from './AttachmentSection';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = { type Props = {
documents: Array<MediaItemType>; documents: Array<MediaItemType>;

@ -11,6 +11,7 @@ import {
toggleSelectedMessageId, toggleSelectedMessageId,
} from '../../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { StateType } from '../../../../state/reducer'; import { StateType } from '../../../../state/reducer';
import { useMessageSelected } from '../../../../state/selectors';
import { import {
getMessageAttachmentProps, getMessageAttachmentProps,
isMessageSelectionMode, isMessageSelectionMode,
@ -32,7 +33,7 @@ import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
import { ImageGrid } from '../../ImageGrid'; import { ImageGrid } from '../../ImageGrid';
import { LightBoxOptions } from '../../SessionConversation'; import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender'; import { ClickToTrustSender } from './ClickToTrustSender';
import { StyledMessageHighlighter } from './MessageContent'; import { MessageHighlighter } from './MessageHighlighter';
export type MessageAttachmentSelectorProps = Pick< export type MessageAttachmentSelectorProps = Pick<
MessageRenderingProps, MessageRenderingProps,
@ -53,7 +54,7 @@ type Props = {
highlight?: boolean; highlight?: boolean;
}; };
const StyledAttachmentContainer = styled.div<{ const StyledImageGridContainer = styled.div<{
messageDirection: MessageModelType; messageDirection: MessageModelType;
}>` }>`
text-align: center; text-align: center;
@ -63,6 +64,10 @@ const StyledAttachmentContainer = styled.div<{
justify-content: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')}; justify-content: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')};
`; `;
const StyledGenericAttachmentContainer = styled(MessageHighlighter)<{ selected: boolean }>`
${props => props.selected && 'box-shadow: var(--drop-shadow);'}
`;
export const MessageAttachment = (props: Props) => { export const MessageAttachment = (props: Props) => {
const { messageId, imageBroken, handleImageError, highlight = false } = props; const { messageId, imageBroken, handleImageError, highlight = false } = props;
@ -72,6 +77,7 @@ export const MessageAttachment = (props: Props) => {
); );
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
const selected = useMessageSelected(messageId);
const onClickOnImageGrid = useCallback( const onClickOnImageGrid = useCallback(
(attachment: AttachmentTypeWithPath | AttachmentType) => { (attachment: AttachmentTypeWithPath | AttachmentType) => {
if (multiSelectMode) { if (multiSelectMode) {
@ -138,21 +144,22 @@ export const MessageAttachment = (props: Props) => {
(isVideo(attachments) && hasVideoScreenshot(attachments))) (isVideo(attachments) && hasVideoScreenshot(attachments)))
) { ) {
return ( return (
<StyledMessageHighlighter highlight={highlight}> <MessageHighlighter highlight={highlight}>
<StyledAttachmentContainer messageDirection={direction}> <StyledImageGridContainer messageDirection={direction}>
<ImageGrid <ImageGrid
messageId={messageId}
attachments={attachments} attachments={attachments}
onError={handleImageError} onError={handleImageError}
onClickAttachment={onClickOnImageGrid} onClickAttachment={onClickOnImageGrid}
/> />
</StyledAttachmentContainer> </StyledImageGridContainer>
</StyledMessageHighlighter> </MessageHighlighter>
); );
} }
if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) { if (!firstAttachment.pending && !firstAttachment.error && isAudio(attachments)) {
return ( return (
<StyledMessageHighlighter <MessageHighlighter
highlight={highlight} highlight={highlight}
role="main" role="main"
onClick={(e: any) => { onClick={(e: any) => {
@ -168,14 +175,18 @@ export const MessageAttachment = (props: Props) => {
contentType={firstAttachment.contentType} contentType={firstAttachment.contentType}
messageId={messageId} messageId={messageId}
/> />
</StyledMessageHighlighter> </MessageHighlighter>
); );
} }
const { pending, fileName, fileSize, contentType } = firstAttachment; const { pending, fileName, fileSize, contentType } = firstAttachment;
const extension = getExtensionForDisplay({ contentType, fileName }); const extension = getExtensionForDisplay({ contentType, fileName });
return ( return (
<StyledMessageHighlighter highlight={highlight} className="module-message__generic-attachment"> <StyledGenericAttachmentContainer
highlight={highlight}
selected={selected}
className={'module-message__generic-attachment'}
>
{pending ? ( {pending ? (
<div className="module-message__generic-attachment__spinner-container"> <div className="module-message__generic-attachment__spinner-container">
<Spinner size="small" /> <Spinner size="small" />
@ -211,7 +222,7 @@ export const MessageAttachment = (props: Props) => {
{fileSize} {fileSize}
</div> </div>
</div> </div>
</StyledMessageHighlighter> </StyledGenericAttachmentContainer>
); );
}; };

@ -4,23 +4,25 @@ import moment from 'moment';
import React, { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react'; import React, { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react';
import { InView } from 'react-intersection-observer'; import { InView } from 'react-intersection-observer';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import styled, { css, keyframes } from 'styled-components'; import styled from 'styled-components';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType'; import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
import { StateType } from '../../../../state/reducer'; import { StateType } from '../../../../state/reducer';
import { useHideAvatarInMsgList, useMessageIsDeleted } from '../../../../state/selectors'; import {
useHideAvatarInMsgList,
useMessageIsDeleted,
useMessageSelected,
} from '../../../../state/selectors';
import { import {
getMessageContentSelectorProps, getMessageContentSelectorProps,
getQuotedMessageToAnimate, getQuotedMessageToAnimate,
getShouldHighlightMessage, getShouldHighlightMessage,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { import { useSelectedIsPrivate } from '../../../../state/selectors/selectedConversation';
useSelectedIsGroup,
useSelectedIsPrivate,
} from '../../../../state/selectors/selectedConversation';
import { canDisplayImage } from '../../../../types/Attachment'; import { canDisplayImage } from '../../../../types/Attachment';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment'; import { MessageAttachment } from './MessageAttachment';
import { MessageAvatar } from './MessageAvatar'; import { MessageAvatar } from './MessageAvatar';
import { MessageHighlighter } from './MessageHighlighter';
import { MessageLinkPreview } from './MessageLinkPreview'; import { MessageLinkPreview } from './MessageLinkPreview';
import { MessageQuote } from './MessageQuote'; import { MessageQuote } from './MessageQuote';
import { MessageText } from './MessageText'; import { MessageText } from './MessageText';
@ -54,41 +56,13 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>)
const StyledMessageContent = styled.div<{ msgDirection: MessageModelType }>` const StyledMessageContent = styled.div<{ msgDirection: MessageModelType }>`
display: flex; display: flex;
align-self: ${props => (props.msgDirection === 'incoming' ? 'flex-start' : 'flex-end')}; align-self: ${props => (props.msgDirection === 'incoming' ? 'flex-start' : 'flex-end')};
`; `;
const opacityAnimation = keyframes` const StyledMessageOpaqueContent = styled(MessageHighlighter)<{
0% {
opacity: 1;
}
25% {
opacity: 0.2;
}
50% {
opacity: 1;
}
75% {
opacity: 0.2;
}
100% {
opacity: 1;
}
`;
export const StyledMessageHighlighter = styled.div<{
highlight: boolean;
}>`
${props =>
props.highlight &&
css`
animation: ${opacityAnimation} 1s linear;
`}
`;
const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
isIncoming: boolean; isIncoming: boolean;
highlight: boolean; highlight: boolean;
selected: boolean;
}>` }>`
background: ${props => background: ${props =>
props.isIncoming props.isIncoming
@ -98,13 +72,13 @@ const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
padding: var(--padding-message-content); padding: var(--padding-message-content);
border-radius: var(--border-radius-message-box); border-radius: var(--border-radius-message-box);
width: 100%; width: 100%;
${props => props.selected && `box-shadow: var(--drop-shadow);`}
`; `;
export const IsMessageVisibleContext = createContext(false); export const IsMessageVisibleContext = createContext(false);
// NOTE aligns group member avatars with the ExpireTimer const StyledAvatarContainer = styled.div`
const StyledAvatarContainer = styled.div<{ hideAvatar: boolean; isGroup: boolean }>`
/* margin-inline-start: ${props => (!props.hideAvatar && props.isGroup ? '-11px' : '')}; */
align-self: flex-end; align-self: flex-end;
`; `;
@ -119,16 +93,12 @@ export const MessageContent = (props: Props) => {
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext); const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
const selectedIsPrivate = useSelectedIsPrivate(); const selectedIsPrivate = useSelectedIsPrivate();
const isGroup = useSelectedIsGroup();
const hideAvatar = useHideAvatarInMsgList(props.messageId); const hideAvatar = useHideAvatarInMsgList(props.messageId);
const [imageBroken, setImageBroken] = useState(false); const [imageBroken, setImageBroken] = useState(false);
const onVisible = (inView: boolean | object) => { const onVisible = (inView: boolean, _: IntersectionObserverEntry) => {
if ( if (inView) {
inView === true ||
((inView as any).type === 'focus' && (inView as any).returnValue === true)
) {
if (isMessageVisible !== true) { if (isMessageVisible !== true) {
setMessageIsVisible(true); setMessageIsVisible(true);
} }
@ -142,6 +112,7 @@ export const MessageContent = (props: Props) => {
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate); const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const shouldHighlightMessage = useSelector(getShouldHighlightMessage); const shouldHighlightMessage = useSelector(getShouldHighlightMessage);
const isQuotedMessageToAnimate = quotedMessageToAnimate === props.messageId; const isQuotedMessageToAnimate = quotedMessageToAnimate === props.messageId;
const selected = useMessageSelected(props.messageId);
useLayoutEffect(() => { useLayoutEffect(() => {
if (isQuotedMessageToAnimate) { if (isQuotedMessageToAnimate) {
@ -200,7 +171,7 @@ export const MessageContent = (props: Props) => {
title={toolTipTitle} title={toolTipTitle}
msgDirection={direction} msgDirection={direction}
> >
<StyledAvatarContainer hideAvatar={hideAvatar} isGroup={isGroup}> <StyledAvatarContainer>
<MessageAvatar <MessageAvatar
messageId={props.messageId} messageId={props.messageId}
hideAvatar={hideAvatar} hideAvatar={hideAvatar}
@ -210,6 +181,7 @@ export const MessageContent = (props: Props) => {
<InView <InView
id={`inview-content-${props.messageId}`} id={`inview-content-${props.messageId}`}
as={'div'}
onChange={onVisible} onChange={onVisible}
threshold={0} threshold={0}
rootMargin="500px 0px 500px 0px" rootMargin="500px 0px 500px 0px"
@ -222,7 +194,11 @@ export const MessageContent = (props: Props) => {
> >
<IsMessageVisibleContext.Provider value={isMessageVisible}> <IsMessageVisibleContext.Provider value={isMessageVisible}>
{hasContentBeforeAttachment && ( {hasContentBeforeAttachment && (
<StyledMessageOpaqueContent isIncoming={direction === 'incoming'} highlight={highlight}> <StyledMessageOpaqueContent
isIncoming={direction === 'incoming'}
highlight={highlight}
selected={selected}
>
{!isDeleted && ( {!isDeleted && (
<> <>
<MessageQuote messageId={props.messageId} /> <MessageQuote messageId={props.messageId} />

@ -88,19 +88,29 @@ const StyledEmojiPanelContainer = styled.div<{ x: number; y: number }>`
} }
`; `;
const DeleteForEveryone = ({ messageId }: { messageId: string }) => { const DeleteItem = ({ messageId }: { messageId: string }) => {
const convoId = useSelectedConversationKey(); const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const isDeletable = useMessageIsDeletable(messageId);
const isDeletableForEveryone = useMessageIsDeletableForEveryone(messageId); const isDeletableForEveryone = useMessageIsDeletableForEveryone(messageId);
if (!convoId || !isDeletableForEveryone) {
return null; const onDelete = useCallback(() => {
if (convoId) {
if (!isPublic && isDeletable) {
void deleteMessagesById([messageId], convoId);
} }
const onDeleteForEveryone = () => { if (isPublic && isDeletableForEveryone) {
void deleteMessagesByIdForEveryone([messageId], convoId); void deleteMessagesByIdForEveryone([messageId], convoId);
}; }
}
}, [convoId, isDeletable, isDeletableForEveryone, isPublic, messageId]);
const unsendMessageText = window.i18n('deleteForEveryone'); if (!convoId || (isPublic && !isDeletableForEveryone) || (!isPublic && !isDeletable)) {
return null;
}
return <Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item>; return <Item onClick={onDelete}>{window.i18n('delete')}</Item>;
}; };
type MessageId = { messageId: string }; type MessageId = { messageId: string };
@ -193,7 +203,6 @@ export const MessageContextMenu = (props: Props) => {
const isSelectedBlocked = useSelectedIsBlocked(); const isSelectedBlocked = useSelectedIsBlocked();
const convoId = useSelectedConversationKey(); const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const direction = useMessageDirection(messageId); const direction = useMessageDirection(messageId);
const status = useMessageStatus(messageId); const status = useMessageStatus(messageId);
@ -238,7 +247,6 @@ export const MessageContextMenu = (props: Props) => {
); );
const selectMessageText = window.i18n('selectMessage'); const selectMessageText = window.i18n('selectMessage');
const deleteMessageJustForMeText = window.i18n('deleteJustForMe');
const onReply = useCallback(() => { const onReply = useCallback(() => {
if (isSelectedBlocked) { if (isSelectedBlocked) {
@ -256,12 +264,6 @@ export const MessageContextMenu = (props: Props) => {
dispatch(toggleSelectedMessageId(messageId)); dispatch(toggleSelectedMessageId(messageId));
}, [dispatch, messageId]); }, [dispatch, messageId]);
const onDelete = useCallback(() => {
if (convoId) {
void deleteMessagesById([messageId], convoId);
}
}, [convoId, messageId]);
const onShowEmoji = () => { const onShowEmoji = () => {
hideAll(); hideAll();
setMouseX(docX); setMouseX(docX);
@ -388,10 +390,7 @@ export const MessageContextMenu = (props: Props) => {
</Item> </Item>
<RetryItem messageId={messageId} /> <RetryItem messageId={messageId} />
{isDeletable ? <Item onClick={onSelect}>{selectMessageText}</Item> : null} {isDeletable ? <Item onClick={onSelect}>{selectMessageText}</Item> : null}
{isDeletable && !isPublic ? ( <DeleteItem messageId={messageId} />
<Item onClick={onDelete}>{deleteMessageJustForMeText}</Item>
) : null}
<DeleteForEveryone messageId={messageId} />
<AdminActionItems messageId={messageId} /> <AdminActionItems messageId={messageId} />
</Menu> </Menu>
</SessionContextMenuContainer> </SessionContextMenuContainer>

@ -0,0 +1,29 @@
import styled, { css, keyframes } from 'styled-components';
const opacityAnimation = keyframes`
0% {
opacity: 1;
}
25% {
opacity: 0.2;
}
50% {
opacity: 1;
}
75% {
opacity: 0.2;
}
100% {
opacity: 1;
}
`;
export const MessageHighlighter = styled.div<{
highlight: boolean;
}>`
${props =>
props.highlight &&
css`
animation: ${opacityAnimation} 1s linear;
`}
`;

@ -7,9 +7,9 @@ import styled, { keyframes } from 'styled-components';
import { MessageRenderingProps } from '../../../../models/messageType'; import { MessageRenderingProps } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations'; import { getConversationController } from '../../../../session/conversations';
import { StateType } from '../../../../state/reducer'; import { StateType } from '../../../../state/reducer';
import { useMessageSelected } from '../../../../state/selectors';
import { import {
getGenericReadableMessageSelectorProps, getGenericReadableMessageSelectorProps,
getIsMessageSelected,
isMessageSelectionMode, isMessageSelectionMode,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus'; import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
@ -62,18 +62,6 @@ const StyledReadableMessage = styled.div<{
` `
background-color: var(--conversation-tab-background-selected-color); background-color: var(--conversation-tab-background-selected-color);
`} `}
${props =>
props.selected &&
`
&.message-selected {
.module-message {
&__container {
box-shadow: var(--drop-shadow);
}
}
}
`}
`; `;
export const GenericReadableMessage = (props: Props) => { export const GenericReadableMessage = (props: Props) => {
@ -85,9 +73,8 @@ export const GenericReadableMessage = (props: Props) => {
getGenericReadableMessageSelectorProps(state, props.messageId) getGenericReadableMessageSelectorProps(state, props.messageId)
); );
const isMessageSelected = useSelector((state: StateType) => const isMessageSelected = useMessageSelected(props.messageId);
getIsMessageSelected(state, props.messageId)
);
const multiSelectMode = useSelector(isMessageSelectionMode); const multiSelectMode = useSelector(isMessageSelectionMode);
const [isRightClicked, setIsRightClicked] = useState(false); const [isRightClicked, setIsRightClicked] = useState(false);
@ -155,7 +142,7 @@ export const GenericReadableMessage = (props: Props) => {
selected={selected} selected={selected}
isDetailView={isDetailView} isDetailView={isDetailView}
isRightClicked={isRightClicked} isRightClicked={isRightClicked}
className={classNames(selected && 'message-selected')} className={classNames(selected ? 'message-selected' : undefined)}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
key={`readable-message-${messageId}`} key={`readable-message-${messageId}`}
> >

@ -26,6 +26,7 @@ const StyledGroupInvitation = styled.div`
display: inline-block; display: inline-block;
padding: 4px; padding: 4px;
margin: var(--margins-xs) calc(var(--margins-lg) + var(--margins-md)) 0 var(--margins-lg);
border-radius: var(--border-radius-message-box); border-radius: var(--border-radius-message-box);

@ -116,7 +116,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
}); });
const onVisible = useCallback( const onVisible = useCallback(
async (inView: boolean | object) => { async (inView: boolean, _: IntersectionObserverEntry) => {
if (!selectedConversationKey) { if (!selectedConversationKey) {
return; return;
} }
@ -135,30 +135,16 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
} }
} }
if ( if (inView && isAppFocused && oldestMessageId === messageId && !fetchingMoreInProgress) {
inView === true &&
isAppFocused &&
oldestMessageId === messageId &&
!fetchingMoreInProgress
) {
debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId); debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId);
} }
if ( if (inView && isAppFocused && youngestMessageId === messageId && !fetchingMoreInProgress) {
inView === true &&
isAppFocused &&
youngestMessageId === messageId &&
!fetchingMoreInProgress
) {
debouncedTriggerLoadMoreBottom(selectedConversationKey, youngestMessageId); debouncedTriggerLoadMoreBottom(selectedConversationKey, youngestMessageId);
} }
// this part is just handling the marking of the message as read if needed // this part is just handling the marking of the message as read if needed
if ( if (inView) {
(inView === true ||
((inView as any).type === 'focus' && (inView as any).returnValue === true)) &&
isAppFocused
) {
if (isUnread) { if (isUnread) {
// TODOLATER this is pretty expensive and should instead use values from the redux store // TODOLATER this is pretty expensive and should instead use values from the redux store
const found = await Data.getMessageById(messageId); const found = await Data.getMessageById(messageId);

@ -42,11 +42,11 @@ import { AttachmentTypeWithPath } from '../../../../types/Attachment';
import { getAbsoluteAttachmentPath } from '../../../../types/MessageAttachment'; import { getAbsoluteAttachmentPath } from '../../../../types/MessageAttachment';
import { Avatar, AvatarSize } from '../../../avatar/Avatar'; import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { Flex } from '../../../basic/Flex'; import { Flex } from '../../../basic/Flex';
import { SpacerMD } from '../../../basic/Text'; import { SpacerLG, SpacerMD, SpacerXL } from '../../../basic/Text';
import { PanelButtonGroup, PanelIconButton } from '../../../buttons'; import { PanelButtonGroup, PanelIconButton } from '../../../buttons';
import { MediaItemType } from '../../../lightbox/LightboxGallery'; import { MediaItemType } from '../../../lightbox/LightboxGallery';
import { MediaGallery } from '../../media-gallery/MediaGallery'; import { MediaGallery } from '../../media-gallery/MediaGallery';
import { Header } from './components'; import { Header, StyledScrollContainer } from './components';
async function getMediaGalleryProps( async function getMediaGalleryProps(
conversationId: string conversationId: string
@ -278,7 +278,8 @@ export const OverlayRightPanelSettings = () => {
}; };
return ( return (
<> <StyledScrollContainer>
<Flex container={true} flexDirection={'column'} alignItems={'center'}>
<HeaderItem /> <HeaderItem />
<PanelButtonGroup style={{ margin: '0 var(--margins-lg)' }}> <PanelButtonGroup style={{ margin: '0 var(--margins-lg)' }}>
{showUpdateGroupNameButton && ( {showUpdateGroupNameButton && (
@ -349,6 +350,9 @@ export const OverlayRightPanelSettings = () => {
/> />
)} )}
</PanelButtonGroup> </PanelButtonGroup>
</> <SpacerLG />
<SpacerXL />
</Flex>
</StyledScrollContainer>
); );
}; };

@ -77,15 +77,13 @@ const MessageBody = ({
); );
}; };
const StyledMessageDetailContainer = styled.div` const StyledMessageInfoContainer = styled.div`
height: calc(100% - 48px); height: calc(100% - 48px);
width: 100%; width: 100%;
overflow-y: auto; max-width: 650px;
overflow: hidden auto;
z-index: 2; z-index: 2;
`;
const StyledMessageDetail = styled.div`
max-width: 650px;
margin-inline-start: auto; margin-inline-start: auto;
margin-inline-end: auto; margin-inline-end: auto;
padding: var(--margins-sm) var(--margins-2xl) var(--margins-lg); padding: var(--margins-sm) var(--margins-2xl) var(--margins-lg);
@ -254,8 +252,7 @@ export const OverlayMessageInfo = () => {
<Header hideBackButton={true} closeButtonOnClick={closePanel}> <Header hideBackButton={true} closeButtonOnClick={closePanel}>
<HeaderTitle>{window.i18n('messageInfo')}</HeaderTitle> <HeaderTitle>{window.i18n('messageInfo')}</HeaderTitle>
</Header> </Header>
<StyledMessageDetailContainer> <StyledMessageInfoContainer>
<StyledMessageDetail>
<MessageBody <MessageBody
messageId={messageId} messageId={messageId}
supportsAttachmentCarousel={supportsAttachmentCarousel} supportsAttachmentCarousel={supportsAttachmentCarousel}
@ -342,8 +339,7 @@ export const OverlayMessageInfo = () => {
)} )}
</PanelButtonGroup> </PanelButtonGroup>
<SpacerXL /> <SpacerXL />
</StyledMessageDetail> </StyledMessageInfoContainer>
</StyledMessageDetailContainer>
</Flex> </Flex>
</StyledScrollContainer> </StyledScrollContainer>
); );

@ -8,7 +8,7 @@ import {
ReduxConversationType, ReduxConversationType,
} from '../ducks/conversations'; } from '../ducks/conversations';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getMessagePropsByMessageId } from './conversations'; import { getIsMessageSelected, getMessagePropsByMessageId } from './conversations';
import { useSelectedIsPrivate } from './selectedConversation'; import { useSelectedIsPrivate } from './selectedConversation';
function useMessagePropsByMessageId(messageId: string | undefined) { function useMessagePropsByMessageId(messageId: string | undefined) {
@ -145,3 +145,7 @@ export function useHideAvatarInMsgList(messageId?: string) {
const selectedIsPrivate = useSelectedIsPrivate(); const selectedIsPrivate = useSelectedIsPrivate();
return msgProps?.propsForMessage.direction === 'outgoing' || selectedIsPrivate; return msgProps?.propsForMessage.direction === 'outgoing' || selectedIsPrivate;
} }
export function useMessageSelected(messageId?: string) {
return useSelector((state: StateType) => getIsMessageSelected(state, messageId));
}

@ -10,7 +10,11 @@ import { UserUtils } from '../../session/utils';
import { ReleasedFeatures } from '../../util/releaseFeature'; import { ReleasedFeatures } from '../../util/releaseFeature';
import { ReduxConversationType } from '../ducks/conversations'; import { ReduxConversationType } from '../ducks/conversations';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getIsMessageSelectionMode, getSelectedConversation } from './conversations'; import {
getIsMessageSelectionMode,
getSelectedConversation,
getSelectedMessageIds,
} from './conversations';
import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo'; import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
/** /**
@ -387,3 +391,7 @@ export function useIsMessageSelectionMode() {
export function useSelectedLastMessage() { export function useSelectedLastMessage() {
return useSelector((state: StateType) => getSelectedConversation(state)?.lastMessage); return useSelector((state: StateType) => getSelectedConversation(state)?.lastMessage);
} }
export function useSelectedMessageIds() {
return useSelector(getSelectedMessageIds);
}

@ -6155,10 +6155,10 @@ react-h5-audio-player@^3.2.0:
"@iconify/icons-mdi" "~1.1.0" "@iconify/icons-mdi" "~1.1.0"
"@iconify/react" "^3.1.3" "@iconify/react" "^3.1.3"
react-intersection-observer@^8.30.3: react-intersection-observer@^9.7.0:
version "8.34.0" version "9.7.0"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz#6f6e67831c52e6233f6b6cc7eb55814820137c42" resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.7.0.tgz#da65834ace0852e04b73cb97f0c48bdaa5b13589"
integrity sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw== integrity sha512-euleEjBVaMRwSOMNVcMX5WGn74GfZ9I78nx9SUb5a0eXd0IhegjJcUliSO9Jd+xiaZ5rgFvbGoVln66lpMyUUg==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"

Loading…
Cancel
Save