Merge pull request #3055 from Bilb/fix/ses-1514/message-info-avatar-overflow

Fix/ses 1514/message info avatar overflow
pull/3060/head
Audric Ackermann 2 months ago committed by GitHub
commit 1c71c8fb67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -210,7 +210,6 @@
display: flex;
align-items: center;
justify-content: center;
margin-right: -20px; // offsets the edit icon button so it's centered
p {
font-size: $session-font-md;

@ -609,6 +609,9 @@
flex-direction: column;
align-items: stretch;
overflow: hidden;
max-height: 100%;
display: flex;
gap: 5px;
.session-icon-button:first-child {
margin-right: var(--margins-sm);

@ -1,12 +1,11 @@
import React from 'react';
import classNames from 'classnames';
import { CSSProperties } from 'styled-components';
import React from 'react';
import { Emojify } from './Emojify';
import {
useNicknameOrProfileNameOrShortenedPubkey,
useIsPrivate,
useNicknameOrProfileNameOrShortenedPubkey,
} from '../../hooks/useParamSelector';
import { Emojify } from './Emojify';
type Props = {
pubkey: string;
@ -25,12 +24,20 @@ export const ContactName = (props: Props) => {
const convoName = useNicknameOrProfileNameOrShortenedPubkey(pubkey);
const isPrivate = useIsPrivate(pubkey);
const shouldShowProfile = Boolean(convoName || profileName || name);
const commonStyles = {
'min-width': 0,
'text-overflow': 'ellipsis',
overflow: 'hidden',
} as React.CSSProperties;
const styles = (
boldProfileName
? {
fontWeight: 'bold',
...commonStyles,
}
: {}
: commonStyles
) as React.CSSProperties;
const textProfile = profileName || name || convoName || window.i18n('anonymous');
@ -39,15 +46,19 @@ export const ContactName = (props: Props) => {
className={classNames(prefix, compact && 'compact')}
dir="auto"
data-testid={`${prefix}__profile-name`}
style={{ textOverflow: 'inherit' }}
style={{
textOverflow: 'inherit',
display: 'flex',
flexDirection: 'row',
gap: 'var(--margins-xs)',
}}
>
{shouldShowProfile ? (
<span style={styles as CSSProperties} className={`${prefix}__profile-name`}>
<div style={styles} className={`${prefix}__profile-name`}>
<Emojify text={textProfile} sizeClass="small" isGroup={!isPrivate} />
</span>
</div>
) : null}
{shouldShowProfile ? ' ' : null}
{shouldShowPubkey ? <span className={`${prefix}__profile-number`}>{pubkey}</span> : null}
{shouldShowPubkey ? <div className={`${prefix}__profile-number`}>{pubkey}</div> : null}
</span>
);
};

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React from 'react';
import styled from 'styled-components';
import {
@ -10,10 +10,10 @@ import {
isVideoAttachment,
} from '../../types/Attachment';
import { useIsMessageVisible } from '../../contexts/isMessageVisibleContext';
import { useMessageSelected } from '../../state/selectors';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
import { Image } from './Image';
import { IsMessageVisibleContext } from './message/message-content/MessageContent';
type Props = {
attachments: Array<AttachmentTypeWithPath>;
@ -46,7 +46,7 @@ const Row = (
totalAttachmentsCount,
selected,
} = props;
const isMessageVisible = useContext(IsMessageVisibleContext);
const isMessageVisible = useIsMessageVisible();
const moreMessagesOverlay = totalAttachmentsCount > 3;
const moreMessagesOverlayText = moreMessagesOverlay ? `+${totalAttachmentsCount - 3}` : undefined;

@ -1,9 +1,9 @@
import React, { useContext, useLayoutEffect } from 'react';
import React, { useLayoutEffect } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useScrollToLoadedMessage } from '../../contexts/ScrollToLoadedMessage';
import { getQuotedMessageToAnimate } from '../../state/selectors/conversations';
import { isDarkTheme } from '../../state/selectors/theme';
import { ScrollToLoadedMessageContext } from './SessionMessagesListContainer';
const LastSeenBar = styled.div`
height: 2px;
@ -52,7 +52,7 @@ export const SessionLastSeenIndicator = (props: {
const darkMode = useSelector(isDarkTheme);
// if this unread-indicator is not unique it's going to cause issues
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
const scrollToLoadedMessage = useScrollToLoadedMessage();
const { messageId, didScroll, setDidScroll } = props;

@ -26,9 +26,10 @@ import { Message } from './message/message-item/Message';
import { MessageRequestResponse } from './message/message-item/MessageRequestResponse';
import { CallNotification } from './message/message-item/notification-bubble/CallNotification';
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { IsDetailMessageViewContext } from '../../contexts/isDetailViewContext';
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
import { TimerNotification } from './TimerNotification';
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { InteractionNotification } from './message/message-item/InteractionNotification';
function isNotTextboxEvent(e: KeyboardEvent) {
@ -98,7 +99,7 @@ export const SessionMessagesList = (props: {
}
return (
<>
<IsDetailMessageViewContext.Provider value={false}>
{messagesProps.map(messageProps => {
const messageId = messageProps.message.props.messageId;
const unreadIndicator = messageProps.showUnreadIndicator ? (
@ -170,6 +171,6 @@ export const SessionMessagesList = (props: {
return [<Message messageId={messageId} key={messageId} />, ...componentToMerge];
})}
</>
</IsDetailMessageViewContext.Provider>
);
};

@ -15,6 +15,10 @@ import {
} from '../../state/ducks/conversations';
import { SessionScrollButton } from '../SessionScrollButton';
import {
ScrollToLoadedMessageContext,
ScrollToLoadedReasons,
} from '../../contexts/ScrollToLoadedMessage';
import { StateType } from '../../state/reducer';
import {
getQuotedMessageToAnimate,
@ -31,17 +35,6 @@ export type SessionMessageListProps = {
};
export const messageContainerDomID = 'messages-container';
export type ScrollToLoadedReasons =
| 'quote-or-search-result'
| 'go-to-bottom'
| 'unread-indicator'
| 'load-more-top'
| 'load-more-bottom';
export const ScrollToLoadedMessageContext = React.createContext(
(_loadedMessageIdToScrollTo: string, _reason: ScrollToLoadedReasons) => {}
);
type Props = SessionMessageListProps & {
conversationKey?: string;
messagesProps: Array<SortedMessageModelProps>;

@ -38,10 +38,10 @@ export type MessageAvatarSelectorProps = Pick<
'sender' | 'isSenderAdmin' | 'lastMessageOfSeries'
>;
type Props = { messageId: string; hideAvatar: boolean; isPrivate: boolean; isDetailView?: boolean };
type Props = { messageId: string; isPrivate: boolean };
export const MessageAvatar = (props: Props) => {
const { messageId, hideAvatar, isPrivate, isDetailView } = props;
const { messageId, isPrivate } = props;
const dispatch = useDispatch();
const selectedConvoKey = useSelectedConversationKey();
@ -137,13 +137,9 @@ export const MessageAvatar = (props: Props) => {
// The styledAvatar, when rendered needs to have a width with margins included of var(--width-avatar-group-msg-list).
// This is so that the other message is still aligned when the avatar is not rendered (we need to make up for the space used by the avatar, and we use a margin of width-avatar-group-msg-list)
return (
<StyledAvatar
style={{
visibility: hideAvatar ? 'hidden' : undefined,
}}
>
<StyledAvatar>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={sender} />
{!isDetailView && isSenderAdmin ? <CrownIcon /> : null}
{isSenderAdmin ? <CrownIcon /> : null}
</StyledAvatar>
);
};

@ -1,10 +1,13 @@
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import moment from 'moment';
import React, { createContext, useCallback, useContext, useLayoutEffect, useState } from 'react';
import React, { useCallback, useLayoutEffect, useState } from 'react';
import { InView } from 'react-intersection-observer';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useScrollToLoadedMessage } from '../../../../contexts/ScrollToLoadedMessage';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { IsMessageVisibleContext } from '../../../../contexts/isMessageVisibleContext';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
import { StateType } from '../../../../state/reducer';
import {
@ -19,7 +22,6 @@ import {
} from '../../../../state/selectors/conversations';
import { useSelectedIsPrivate } from '../../../../state/selectors/selectedConversation';
import { canDisplayImage } from '../../../../types/Attachment';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
import { MessageAttachment } from './MessageAttachment';
import { MessageAvatar } from './MessageAvatar';
import { MessageHighlighter } from './MessageHighlighter';
@ -34,7 +36,6 @@ export type MessageContentSelectorProps = Pick<
type Props = {
messageId: string;
isDetailView?: boolean;
};
// TODO not too sure what is this doing? It is not preventDefault()
@ -76,13 +77,13 @@ const StyledMessageOpaqueContent = styled(MessageHighlighter)<{
${props => props.selected && `box-shadow: var(--drop-shadow);`}
`;
export const IsMessageVisibleContext = createContext(false);
const StyledAvatarContainer = styled.div`
align-self: flex-end;
`;
export const MessageContent = (props: Props) => {
const isDetailView = useIsDetailMessageView();
const [highlight, setHighlight] = useState(false);
const [didScroll, setDidScroll] = useState(false);
const contentProps = useSelector((state: StateType) =>
@ -91,9 +92,9 @@ export const MessageContent = (props: Props) => {
const isDeleted = useMessageIsDeleted(props.messageId);
const [isMessageVisible, setMessageIsVisible] = useState(false);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
const scrollToLoadedMessage = useScrollToLoadedMessage();
const selectedIsPrivate = useSelectedIsPrivate();
const hideAvatar = useHideAvatarInMsgList(props.messageId);
const hideAvatar = useHideAvatarInMsgList(props.messageId, isDetailView);
const [imageBroken, setImageBroken] = useState(false);
@ -153,8 +154,7 @@ export const MessageContent = (props: Props) => {
const toolTipTitle = moment(serverTimestamp || timestamp).format('llll');
const isDetailViewAndSupportsAttachmentCarousel =
props.isDetailView && canDisplayImage(attachments);
const isDetailViewAndSupportsAttachmentCarousel = isDetailView && canDisplayImage(attachments);
return (
<StyledMessageContent
@ -164,14 +164,11 @@ export const MessageContent = (props: Props) => {
title={toolTipTitle}
msgDirection={direction}
>
<StyledAvatarContainer>
<MessageAvatar
messageId={props.messageId}
hideAvatar={hideAvatar}
isPrivate={selectedIsPrivate}
isDetailView={props.isDetailView}
/>
</StyledAvatarContainer>
{hideAvatar ? null : (
<StyledAvatarContainer>
<MessageAvatar messageId={props.messageId} isPrivate={selectedIsPrivate} />
</StyledAvatarContainer>
)}
<InView
id={`inview-content-${props.messageId}`}
@ -184,6 +181,7 @@ export const MessageContent = (props: Props) => {
display: 'flex',
flexDirection: 'column',
gap: 'var(--margins-xs)',
maxWidth: '100%',
}}
>
<IsMessageVisibleContext.Provider value={isMessageVisible}>

@ -2,6 +2,7 @@ import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
import { MessageRenderingProps } from '../../../../models/messageType';
import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
@ -29,30 +30,33 @@ export type MessageContentWithStatusSelectorProps = { isGroup: boolean } & Pick<
type Props = {
messageId: string;
ctxMenuID: string;
isDetailView?: boolean;
dataTestId: string;
enableReactions: boolean;
};
const StyledMessageContentContainer = styled.div<{ isIncoming: boolean }>`
const StyledMessageContentContainer = styled.div<{ isIncoming: boolean; isDetailView: boolean }>`
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: ${props => (props.isIncoming ? 'flex-start' : 'flex-end')};
padding-left: ${props => (props.isIncoming ? 0 : '25%')};
padding-right: ${props => (props.isIncoming ? '25%' : 0)};
padding-left: ${props => (props.isDetailView || props.isIncoming ? 0 : '25%')};
padding-right: ${props => (props.isDetailView || !props.isIncoming ? 0 : '25%')};
width: 100%;
max-width: '100%';
margin-right: var(--margins-md);
`;
const StyledMessageWithAuthor = styled.div`
max-width: '100%';
max-width: 100%;
display: flex;
flex-direction: column;
min-width: 0;
gap: var(--margins-xs);
`;
export const MessageContentWithStatuses = (props: Props) => {
const isDetailView = useIsDetailMessageView();
const contentProps = useSelector((state: StateType) =>
getMessageContentWithStatusesSelectorProps(state, props.messageId)
);
@ -91,7 +95,7 @@ export const MessageContentWithStatuses = (props: Props) => {
}
};
const { messageId, ctxMenuID, isDetailView = false, dataTestId, enableReactions } = props;
const { messageId, ctxMenuID, dataTestId, enableReactions } = props;
const [popupReaction, setPopupReaction] = useState('');
if (!contentProps) {
@ -119,6 +123,7 @@ export const MessageContentWithStatuses = (props: Props) => {
return (
<StyledMessageContentContainer
isIncoming={isIncoming}
isDetailView={isDetailView}
onMouseLeave={() => {
setPopupReaction('');
}}
@ -127,21 +132,22 @@ export const MessageContentWithStatuses = (props: Props) => {
messageId={messageId}
className={classNames('module-message', `module-message--${direction}`)}
role={'button'}
isDetailView={isDetailView}
onClick={onClickOnMessageOuterContainer}
onDoubleClickCapture={onDoubleClickReplyToMessage}
dataTestId={dataTestId}
>
<Flex container={true} flexDirection="column" flexShrink={0} alignItems="flex-end">
<Flex
container={true}
flexDirection="column"
flexShrink={0}
alignItems="flex-end"
maxWidth="100%"
>
<StyledMessageWithAuthor>
{!isDetailView && <MessageAuthorText messageId={messageId} />}
<MessageContent messageId={messageId} isDetailView={isDetailView} />
<MessageContent messageId={messageId} />
</StyledMessageWithAuthor>
<MessageStatus
dataTestId="msg-status"
messageId={messageId}
isDetailView={isDetailView}
/>
<MessageStatus dataTestId="msg-status" messageId={messageId} />
</Flex>
{!isDeleted && (
<MessageContextMenu
@ -160,7 +166,6 @@ export const MessageContentWithStatuses = (props: Props) => {
setPopupReaction={setPopupReaction}
onPopupClick={handlePopupClick}
noAvatar={hideAvatar}
isDetailView={isDetailView}
/>
) : null}
</StyledMessageContentContainer>

@ -1,6 +1,7 @@
import { isEmpty, toNumber } from 'lodash';
import React from 'react';
import { useSelector } from 'react-redux';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { Data } from '../../../../data/data';
import { MessageRenderingProps } from '../../../../models/messageType';
import { ToastUtils } from '../../../../session/utils';
@ -19,6 +20,7 @@ export type MessageQuoteSelectorProps = Pick<MessageRenderingProps, 'quote' | 'd
export const MessageQuote = (props: Props) => {
const selected = useSelector((state: StateType) => getMessageQuoteProps(state, props.messageId));
const direction = useMessageDirection(props.messageId);
const isMessageDetailView = useIsDetailMessageView();
if (!selected || isEmpty(selected)) {
return null;
@ -38,6 +40,10 @@ export const MessageQuote = (props: Props) => {
event.preventDefault();
event.stopPropagation();
if (isMessageDetailView) {
return;
}
if (!quote) {
ToastUtils.pushOriginalNotFound();
window.log.warn('onQuoteClick: quote not valid');

@ -1,6 +1,7 @@
import { isEmpty, isEqual } from 'lodash';
import React, { ReactElement, useEffect, useState } from 'react';
import styled from 'styled-components';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { useMessageReactsPropsById } from '../../../../hooks/useParamSelector';
import { MessageRenderingProps } from '../../../../models/messageType';
import { REACT_LIMIT } from '../../../../session/constants';
@ -147,10 +148,11 @@ type Props = {
inModal?: boolean;
onSelected?: (emoji: string) => boolean;
noAvatar: boolean;
isDetailView?: boolean;
};
export const MessageReactions = (props: Props) => {
const isDetailView = useIsDetailMessageView();
const {
messageId,
hasReactLimit = true,
@ -161,7 +163,6 @@ export const MessageReactions = (props: Props) => {
inModal = false,
onSelected,
noAvatar,
isDetailView,
} = props;
const [reactions, setReactions] = useState<SortedReactionList>([]);

@ -5,6 +5,7 @@ import styled from 'styled-components';
import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector';
import { useMessageStatus } from '../../../../state/selectors';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { getMostRecentMessageId } from '../../../../state/selectors/conversations';
import { useSelectedIsGroupOrCommunity } from '../../../../state/selectors/selectedConversation';
import { SpacerXS } from '../../../basic/Text';
@ -12,7 +13,6 @@ import { SessionIcon, SessionIconType } from '../../../icon';
import { ExpireTimer } from '../../ExpireTimer';
type Props = {
isDetailView: boolean;
messageId: string;
dataTestId?: string | undefined;
};
@ -30,7 +30,9 @@ type Props = {
* - if the message is incoming: do not show anything (3)
* - if the message is outgoing: show the text for the last message, or a message sending, or in the error state. (4)
*/
export const MessageStatus = ({ isDetailView, messageId, dataTestId }: Props) => {
export const MessageStatus = ({ messageId, dataTestId }: Props) => {
const isDetailView = useIsDetailMessageView();
const status = useMessageStatus(messageId);
const selected = useMessageExpirationPropsById(messageId);

@ -3,10 +3,10 @@ import React, { MouseEvent, useState } from 'react';
import { isEmpty } from 'lodash';
import styled from 'styled-components';
import { useIsMessageSelectionMode } from '../../../../../state/selectors/selectedConversation';
import * as MIME from '../../../../../types/MIME';
import { QuoteAuthor } from './QuoteAuthor';
import { QuoteIconContainer } from './QuoteIconContainer';
import { QuoteText } from './QuoteText';
import * as MIME from '../../../../../types/MIME';
const StyledQuoteContainer = styled.div`
min-width: 300px; // if the quoted content is small it doesn't look very good so we set a minimum

@ -2,9 +2,9 @@ import React from 'react';
import styled from 'styled-components';
import { useQuoteAuthorName } from '../../../../../hooks/useParamSelector';
import { PubKey } from '../../../../../session/types';
import { useSelectedIsPublic } from '../../../../../state/selectors/selectedConversation';
import { ContactName } from '../../../ContactName';
import { QuoteProps } from './Quote';
import { useSelectedIsPublic } from '../../../../../state/selectors/selectedConversation';
const StyledQuoteAuthor = styled.div<{ isIncoming: boolean }>`
color: ${props =>
@ -18,6 +18,7 @@ const StyledQuoteAuthor = styled.div<{ isIncoming: boolean }>`
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.module-contact-name {
font-weight: bold;
}

@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useInterval, useMount } from 'react-use';
import styled from 'styled-components';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { Data } from '../../../../data/data';
import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelector';
import { MessageModelType } from '../../../../models/messageType';
@ -84,7 +85,6 @@ export interface ExpirableReadableMessageProps
extends Omit<ReadableMessageProps, 'receivedAt' | 'isUnread'> {
messageId: string;
isControlMessage?: boolean;
isDetailView?: boolean;
}
function ExpireTimerControlMessage({
@ -109,6 +109,7 @@ function ExpireTimerControlMessage({
export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) => {
const selected = useMessageExpirationPropsById(props.messageId);
const isDetailView = useIsDetailMessageView();
const { isControlMessage, onClick, onDoubleClickCapture, role, dataTestId } = props;
@ -135,7 +136,7 @@ export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) =
} = selected;
// NOTE we want messages on the left in the message detail view regardless of direction
const direction = props.isDetailView ? 'incoming' : _direction;
const direction = isDetailView ? 'incoming' : _direction;
const isIncoming = direction === 'incoming';
return (

@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { contextMenu } from 'react-contexify';
import { useSelector } from 'react-redux';
import styled, { keyframes } from 'styled-components';
import { useIsDetailMessageView } from '../../../../contexts/isDetailViewContext';
import { MessageRenderingProps } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations';
import { StateType } from '../../../../state/reducer';
@ -29,7 +30,6 @@ export type GenericReadableMessageSelectorProps = Pick<
type Props = {
messageId: string;
ctxMenuID: string;
isDetailView?: boolean;
};
const highlightedMessageAnimation = keyframes`
@ -40,8 +40,8 @@ const highlightedMessageAnimation = keyframes`
const StyledReadableMessage = styled.div<{
selected: boolean;
isDetailView: boolean;
isRightClicked: boolean;
isDetailView?: boolean;
}>`
display: flex;
align-items: center;
@ -64,7 +64,9 @@ const StyledReadableMessage = styled.div<{
`;
export const GenericReadableMessage = (props: Props) => {
const { ctxMenuID, messageId, isDetailView } = props;
const isDetailView = useIsDetailMessageView();
const { ctxMenuID, messageId } = props;
const [enableReactions, setEnableReactions] = useState(true);
@ -148,7 +150,6 @@ export const GenericReadableMessage = (props: Props) => {
<MessageContentWithStatuses
ctxMenuID={ctxMenuID}
messageId={messageId}
isDetailView={isDetailView}
dataTestId={'message-content'}
enableReactions={enableReactions}
/>

@ -12,7 +12,6 @@ export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = THUMBNAIL_SIDE;
type Props = {
messageId: string;
isDetailView?: boolean; // when the detail is shown for a message, we disable click and some other stuff
};
export const Message = (props: Props) => {
@ -26,11 +25,5 @@ export const Message = (props: Props) => {
return null;
}
return (
<GenericReadableMessage
ctxMenuID={ctxMenuID}
messageId={props.messageId}
isDetailView={props.isDetailView}
/>
);
return <GenericReadableMessage ctxMenuID={ctxMenuID} messageId={props.messageId} />;
};

@ -39,7 +39,7 @@ export const MessageRequestResponse = (props: PropsForMessageRequestResponse) =>
id={`msg-${messageId}`}
>
<SpacerSM />
<Text text={msgText} subtle={true} ellipsisOverflow={true} />
<Text text={msgText} subtle={true} ellipsisOverflow={false} textAlign="center" />
</Flex>
</ReadableMessage>
);

@ -1,14 +1,8 @@
import { debounce, noop } from 'lodash';
import React, {
AriaRole,
MouseEventHandler,
useCallback,
useContext,
useLayoutEffect,
useState,
} from 'react';
import React, { AriaRole, MouseEventHandler, useCallback, useLayoutEffect, useState } from 'react';
import { InView } from 'react-intersection-observer';
import { useDispatch, useSelector } from 'react-redux';
import { useScrollToLoadedMessage } from '../../../../contexts/ScrollToLoadedMessage';
import { Data } from '../../../../data/data';
import { useHasUnread } from '../../../../hooks/useParamSelector';
import { getConversationController } from '../../../../session/conversations';
@ -28,7 +22,6 @@ import {
} from '../../../../state/selectors/conversations';
import { getIsAppFocused } from '../../../../state/selectors/section';
import { useSelectedConversationKey } from '../../../../state/selectors/selectedConversation';
import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer';
export type ReadableMessageProps = {
children: React.ReactNode;
@ -95,7 +88,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
const [didScroll, setDidScroll] = useState(false);
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
const scrollToLoadedMessage = useScrollToLoadedMessage();
// if this unread-indicator is rendered,
// we want to scroll here only if the conversation was not opened to a specific message

@ -10,6 +10,7 @@ import { getMessageInfoId } from '../../../../../state/selectors/conversations';
import { Flex } from '../../../../basic/Flex';
import { Header, HeaderTitle, StyledScrollContainer } from '../components';
import { IsDetailMessageViewContext } from '../../../../../contexts/isDetailViewContext';
import { Data } from '../../../../../data/data';
import { useRightOverlayMode } from '../../../../../hooks/useUI';
import {
@ -71,9 +72,11 @@ const MessageBody = ({
}
return (
<StyledMessageBody>
<Message messageId={messageId} isDetailView={true} />
</StyledMessageBody>
<IsDetailMessageViewContext.Provider value={true}>
<StyledMessageBody>
<Message messageId={messageId} />
</StyledMessageBody>
</IsDetailMessageViewContext.Provider>
);
};

@ -2,7 +2,7 @@ import React from 'react';
import styled from 'styled-components';
import { MessageInfoLabel } from '.';
import { useConversationUsername } from '../../../../../../hooks/useParamSelector';
import { Avatar, AvatarSize } from '../../../../../avatar/Avatar';
import { Avatar, AvatarSize, CrownIcon } from '../../../../../avatar/Avatar';
const StyledFromContainer = styled.div`
display: flex;
@ -30,8 +30,12 @@ const StyledMessageInfoAuthor = styled.div`
font-size: var(--font-size-lg);
`;
export const MessageFrom = (props: { sender: string }) => {
const { sender } = props;
const StyledAvatar = styled.div`
position: relative;
`;
export const MessageFrom = (props: { sender: string; isSenderAdmin: boolean }) => {
const { sender, isSenderAdmin } = props;
const profileName = useConversationUsername(sender);
const from = window.i18n('from');
@ -39,7 +43,10 @@ export const MessageFrom = (props: { sender: string }) => {
<StyledMessageInfoAuthor>
<MessageInfoLabel>{from}</MessageInfoLabel>
<StyledFromContainer>
<Avatar size={AvatarSize.M} pubkey={sender} onAvatarClick={undefined} />
<StyledAvatar>
<Avatar size={AvatarSize.M} pubkey={sender} onAvatarClick={undefined} />
{isSenderAdmin ? <CrownIcon /> : null}
</StyledAvatar>
<StyledAuthorNamesContainer>
{!!profileName && <Name>{profileName}</Name>}
<Pubkey>{sender}</Pubkey>

@ -14,6 +14,7 @@ import {
useMessageHash,
useMessageReceivedAt,
useMessageSender,
useMessageSenderIsAdmin,
useMessageServerId,
useMessageServerTimestamp,
useMessageTimestamp,
@ -111,6 +112,7 @@ export const MessageInfo = ({ messageId, errors }: { messageId: string; errors:
const sentAt = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const receivedAt = useMessageReceivedAt(messageId);
const isSenderAdmin = useMessageSenderIsAdmin(messageId);
if (!messageId || !sender) {
return null;
@ -137,7 +139,7 @@ export const MessageInfo = ({ messageId, errors }: { messageId: string; errors:
<LabelWithInfo label={`${window.i18n('received')}:`} info={receivedAtStr} />
) : null}
<SpacerSM />
<MessageFrom sender={sender} />
<MessageFrom sender={sender} isSenderAdmin={isSenderAdmin} />
{hasError && (
<>
<SpacerSM />

@ -10,6 +10,10 @@ import { Avatar, AvatarSize } from '../../avatar/Avatar';
import { openConversationWithMessages } from '../../../state/ducks/conversations';
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog';
import {
ContextConversationProvider,
useConvoIdFromContext,
} from '../../../contexts/ConvoIdContext';
import {
useAvatarPath,
useConversationUsername,
@ -21,7 +25,6 @@ import {
import { isSearching } from '../../../state/selectors/search';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu';
import { ContextConversationProvider, useConvoIdFromContext } from './ConvoIdContext';
import { ConversationListItemHeaderItem } from './HeaderItem';
import { MessageItem } from './MessageItem';

@ -2,6 +2,7 @@ import classNames from 'classnames';
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useConvoIdFromContext } from '../../../contexts/ConvoIdContext';
import { Data } from '../../../data/data';
import {
useActiveAt,
@ -20,7 +21,6 @@ import { isSearching } from '../../../state/selectors/search';
import { getIsMessageSection } from '../../../state/selectors/section';
import { Timestamp } from '../../conversation/Timestamp';
import { SessionIcon } from '../../icon';
import { useConvoIdFromContext } from './ConvoIdContext';
import { UserItem } from './UserItem';
const NotificationSettingIcon = () => {

@ -2,6 +2,7 @@ import classNames from 'classnames';
import { isEmpty } from 'lodash';
import React from 'react';
import { useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../../contexts/ConvoIdContext';
import {
useHasUnread,
useIsPrivate,
@ -15,7 +16,6 @@ import { assertUnreachable } from '../../../types/sqlSharedTypes';
import { TypingAnimation } from '../../conversation/TypingAnimation';
import { MessageBody } from '../../conversation/message/message-content/MessageBody';
import { SessionIcon } from '../../icon';
import { useConvoIdFromContext } from './ConvoIdContext';
import { InteractionItem } from './InteractionItem';
export const MessageItem = () => {

@ -1,5 +1,6 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../../contexts/ConvoIdContext';
import {
useConversationRealName,
useConversationUsername,
@ -9,7 +10,6 @@ import {
import { PubKey } from '../../../session/types';
import { isSearching } from '../../../state/selectors/search';
import { ContactName } from '../../conversation/ContactName';
import { useConvoIdFromContext } from './ConvoIdContext';
export const UserItem = () => {
const conversationId = useConvoIdFromContext();
@ -36,15 +36,13 @@ export const UserItem = () => {
}
return (
<div className="module-conversation__user">
<ContactName
pubkey={displayedPubkey}
name={username}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
<ContactName
pubkey={displayedPubkey}
name={username}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
);
};

@ -2,10 +2,11 @@ import React from 'react';
import { Item, Menu } from 'react-contexify';
import { useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../contexts/ConvoIdContext';
import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector';
import { getConversationController } from '../../session/conversations';
import { isSearching } from '../../state/selectors/search';
import { getIsMessageSection } from '../../state/selectors/section';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
import {
AcceptMsgRequestMenuItem,
@ -17,16 +18,15 @@ import {
DeclineAndBlockMsgRequestMenuItem,
DeclineMsgRequestMenuItem,
DeleteMessagesMenuItem,
DeletePrivateConversationMenuItem,
InviteContactMenuItem,
LeaveGroupOrCommunityMenuItem,
MarkAllReadMenuItem,
MarkConversationUnreadMenuItem,
NotificationForConvoMenuItem,
ShowUserDetailsMenuItem,
UnbanMenuItem,
DeletePrivateConversationMenuItem,
NotificationForConvoMenuItem,
} from './Menu';
import { isSearching } from '../../state/selectors/search';
export type PropsContextConversationItem = {
triggerId: string;

@ -2,6 +2,7 @@ import React from 'react';
import { Item, Submenu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../contexts/ConvoIdContext';
import {
useAvatarPath,
useConversationUsername,
@ -56,7 +57,6 @@ import { getIsMessageSection } from '../../state/selectors/section';
import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { SessionButtonColor } from '../basic/SessionButton';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
/** Menu items standardized */

@ -1,15 +1,15 @@
import React from 'react';
import styled, { CSSProperties } from 'styled-components';
import { useConversationUsername, useIsPrivate } from '../../hooks/useParamSelector';
import { MessageAttributes } from '../../models/messageType';
import { UserUtils } from '../../session/utils';
import { getOurPubKeyStrFromCache } from '../../session/utils/User';
import { openConversationToSpecificMessage } from '../../state/ducks/conversations';
import { ContactName } from '../conversation/ContactName';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { Timestamp } from '../conversation/Timestamp';
import { MessageBodyHighlight } from '../basic/MessageBodyHighlight';
import { MessageAttributes } from '../../models/messageType';
import { useConversationUsername, useIsPrivate } from '../../hooks/useParamSelector';
import { UserUtils } from '../../session/utils';
import { ContactName } from '../conversation/ContactName';
import { Timestamp } from '../conversation/Timestamp';
export type MessageResultProps = MessageAttributes & { snippet: string };
@ -58,6 +58,7 @@ const StyledResultText = styled.div`
display: inline-flex;
flex-direction: column;
align-items: stretch;
min-width: 0;
`;
const ResultsHeader = styled.div`

@ -0,0 +1,16 @@
import { createContext, useContext } from 'react';
export type ScrollToLoadedReasons =
| 'quote-or-search-result'
| 'go-to-bottom'
| 'unread-indicator'
| 'load-more-top'
| 'load-more-bottom';
export const ScrollToLoadedMessageContext = createContext(
(_loadedMessageIdToScrollTo: string, _reason: ScrollToLoadedReasons) => {}
);
export function useScrollToLoadedMessage() {
return useContext(ScrollToLoadedMessageContext);
}

@ -0,0 +1,10 @@
import { createContext, useContext } from 'react';
/**
* When the message is rendered as part of the detailView (right panel) we disable onClick and make some other minor UI changes
*/
export const IsDetailMessageViewContext = createContext<boolean>(false);
export function useIsDetailMessageView() {
return useContext(IsDetailMessageViewContext);
}

@ -0,0 +1,7 @@
import { createContext, useContext } from 'react';
export const IsMessageVisibleContext = createContext(false);
export function useIsMessageVisible() {
return useContext(IsMessageVisibleContext);
}

@ -160,10 +160,10 @@ export const useMessageText = (messageId: string | undefined): string | undefine
return useMessagePropsByMessageId(messageId)?.propsForMessage.text;
};
export function useHideAvatarInMsgList(messageId?: string) {
export function useHideAvatarInMsgList(messageId?: string, isDetailView?: boolean) {
const msgProps = useMessagePropsByMessageId(messageId);
const selectedIsPrivate = useSelectedIsPrivate();
return msgProps?.propsForMessage.direction === 'outgoing' || selectedIsPrivate;
return isDetailView || msgProps?.propsForMessage.direction === 'outgoing' || selectedIsPrivate;
}
export function useMessageSelected(messageId?: string) {

Loading…
Cancel
Save