From cef59be00536e1095ef4ba5f17b04dbe216afe51 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 19 Mar 2024 15:49:13 +1100 Subject: [PATCH] fix: ellipsis on name rather than pubkey also cleaned up our contexts a little bit --- stylesheets/_modules.scss | 3 ++ ts/components/conversation/ContactName.tsx | 31 +++++++++++++------ ts/components/conversation/ImageGrid.tsx | 6 ++-- .../conversation/SessionLastSeenIndicator.tsx | 6 ++-- .../conversation/SessionMessagesList.tsx | 7 +++-- .../SessionMessagesListContainer.tsx | 15 +++------ .../message-content/MessageContent.tsx | 23 +++++++------- .../MessageContentWithStatus.tsx | 31 ++++++++++--------- .../message/message-content/MessageQuote.tsx | 10 ++++-- .../message-content/MessageReactions.tsx | 7 +++-- .../message/message-content/MessageStatus.tsx | 8 +++-- .../message/message-content/quote/Quote.tsx | 7 ++--- .../message-content/quote/QuoteAuthor.tsx | 12 +++---- .../message-item/ExpirableReadableMessage.tsx | 8 ++--- .../message-item/GenericReadableMessage.tsx | 20 ++++++------ .../message/message-item/Message.tsx | 13 ++------ .../message/message-item/ReadableMessage.tsx | 13 ++------ .../message-info/OverlayMessageInfo.tsx | 9 ++++-- .../ConversationListItem.tsx | 5 ++- .../conversation-list-item/HeaderItem.tsx | 2 +- .../conversation-list-item/MessageItem.tsx | 2 +- .../conversation-list-item/UserItem.tsx | 20 ++++++------ .../menu/ConversationListItemContextMenu.tsx | 8 ++--- ts/components/menu/Menu.tsx | 2 +- ts/components/search/MessageSearchResults.tsx | 11 ++++--- .../ConvoIdContext.tsx | 0 ts/contexts/ScrollToLoadedMessage.tsx | 16 ++++++++++ ts/contexts/isDetailViewContext.tsx | 10 ++++++ ts/contexts/isMessageVisibleContext.tsx | 7 +++++ 29 files changed, 176 insertions(+), 136 deletions(-) rename ts/{components/leftpane/conversation-list-item => contexts}/ConvoIdContext.tsx (100%) create mode 100644 ts/contexts/ScrollToLoadedMessage.tsx create mode 100644 ts/contexts/isDetailViewContext.tsx create mode 100644 ts/contexts/isMessageVisibleContext.tsx diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 31b7840df..491c846d4 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -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); diff --git a/ts/components/conversation/ContactName.tsx b/ts/components/conversation/ContactName.tsx index 718467b78..5216371f3 100644 --- a/ts/components/conversation/ContactName.tsx +++ b/ts/components/conversation/ContactName.tsx @@ -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 ? ( - +
- +
) : null} - {shouldShowProfile ? ' ' : null} - {shouldShowPubkey ? {pubkey} : null} + {shouldShowPubkey ?
{pubkey}
: null}
); }; diff --git a/ts/components/conversation/ImageGrid.tsx b/ts/components/conversation/ImageGrid.tsx index 6d47e9b44..d4f9c3ce7 100644 --- a/ts/components/conversation/ImageGrid.tsx +++ b/ts/components/conversation/ImageGrid.tsx @@ -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; @@ -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; diff --git a/ts/components/conversation/SessionLastSeenIndicator.tsx b/ts/components/conversation/SessionLastSeenIndicator.tsx index 9b65f07cd..31ff6458a 100644 --- a/ts/components/conversation/SessionLastSeenIndicator.tsx +++ b/ts/components/conversation/SessionLastSeenIndicator.tsx @@ -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; diff --git a/ts/components/conversation/SessionMessagesList.tsx b/ts/components/conversation/SessionMessagesList.tsx index 30a0a04bf..3f7f4d42a 100644 --- a/ts/components/conversation/SessionMessagesList.tsx +++ b/ts/components/conversation/SessionMessagesList.tsx @@ -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 ( - <> + {messagesProps.map(messageProps => { const messageId = messageProps.message.props.messageId; const unreadIndicator = messageProps.showUnreadIndicator ? ( @@ -170,6 +171,6 @@ export const SessionMessagesList = (props: { return [, ...componentToMerge]; })} - + ); }; diff --git a/ts/components/conversation/SessionMessagesListContainer.tsx b/ts/components/conversation/SessionMessagesListContainer.tsx index 042b84a41..5da6eccfc 100644 --- a/ts/components/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/conversation/SessionMessagesListContainer.tsx @@ -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; diff --git a/ts/components/conversation/message/message-content/MessageContent.tsx b/ts/components/conversation/message/message-content/MessageContent.tsx index 04f8b9e1f..143185577 100644 --- a/ts/components/conversation/message/message-content/MessageContent.tsx +++ b/ts/components/conversation/message/message-content/MessageContent.tsx @@ -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,8 +22,6 @@ import { } from '../../../../state/selectors/conversations'; import { useSelectedIsPrivate } from '../../../../state/selectors/selectedConversation'; import { canDisplayImage } from '../../../../types/Attachment'; -import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; -import { hasDetailView } from '../message-item/Message'; import { MessageAttachment } from './MessageAttachment'; import { MessageAvatar } from './MessageAvatar'; import { MessageHighlighter } from './MessageHighlighter'; @@ -33,7 +34,7 @@ export type MessageContentSelectorProps = Pick< 'text' | 'direction' | 'timestamp' | 'serverTimestamp' | 'previews' | 'quote' | 'attachments' >; -type Props = hasDetailView & { +type Props = { messageId: string; }; @@ -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, props.isDetailView); + 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 ( { display: 'flex', flexDirection: 'column', gap: 'var(--margins-xs)', + maxWidth: '100%', }} > @@ -192,7 +193,7 @@ export const MessageContent = (props: Props) => { > {!isDeleted && ( <> - + ; -type Props = hasDetailView & { +type Props = { messageId: string; ctxMenuID: string; dataTestId: string; enableReactions: boolean; }; -const StyledMessageContentContainer = styled.div` +const StyledMessageContentContainer = styled.div<{ isIncoming: boolean; isDetailView: boolean }>` display: flex; flex-direction: column; justify-content: flex-start; @@ -42,17 +42,20 @@ const StyledMessageContentContainer = styled.div (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; `; export const MessageContentWithStatuses = (props: Props) => { + const isDetailView = useIsDetailMessageView(); + const contentProps = useSelector((state: StateType) => getMessageContentWithStatusesSelectorProps(state, props.messageId) ); @@ -91,7 +94,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) { @@ -128,21 +131,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} > - + {!isDetailView && } - + - + {!isDeleted && ( { setPopupReaction={setPopupReaction} onPopupClick={handlePopupClick} noAvatar={hideAvatar} - isDetailView={isDetailView} /> ) : null} diff --git a/ts/components/conversation/message/message-content/MessageQuote.tsx b/ts/components/conversation/message/message-content/MessageQuote.tsx index e241d662e..12e20a289 100644 --- a/ts/components/conversation/message/message-content/MessageQuote.tsx +++ b/ts/components/conversation/message/message-content/MessageQuote.tsx @@ -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'; @@ -8,10 +9,9 @@ import { openConversationToSpecificMessage } from '../../../../state/ducks/conve import { StateType } from '../../../../state/reducer'; import { useMessageDirection } from '../../../../state/selectors'; import { getMessageQuoteProps } from '../../../../state/selectors/conversations'; -import { hasDetailView } from '../message-item/Message'; import { Quote } from './quote/Quote'; -type Props = hasDetailView & { +type Props = { messageId: string; }; @@ -20,6 +20,7 @@ export type MessageQuoteSelectorProps = Pick { const selected = useSelector((state: StateType) => getMessageQuoteProps(state, props.messageId)); const direction = useMessageDirection(props.messageId); + const isMessageDetailView = useIsDetailMessageView(); if (!selected || isEmpty(selected)) { return null; @@ -39,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'); @@ -98,7 +103,6 @@ export const MessageQuote = (props: Props) => { author={quote.author} referencedMessageNotFound={quoteNotFound} isFromMe={Boolean(quote.isFromMe)} - isDetailView={props.isDetailView} /> ); }; diff --git a/ts/components/conversation/message/message-content/MessageReactions.tsx b/ts/components/conversation/message/message-content/MessageReactions.tsx index 0dbb27353..80e571ab7 100644 --- a/ts/components/conversation/message/message-content/MessageReactions.tsx +++ b/ts/components/conversation/message/message-content/MessageReactions.tsx @@ -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'; @@ -9,7 +10,6 @@ import { SortedReactionList } from '../../../../types/Reaction'; import { nativeEmojiData } from '../../../../util/emoji'; import { Flex } from '../../../basic/Flex'; import { SessionIcon } from '../../../icon'; -import { hasDetailView } from '../message-item/Message'; import { Reaction, ReactionProps } from '../reactions/Reaction'; import { StyledPopupContainer } from '../reactions/ReactionPopup'; @@ -138,7 +138,7 @@ export type MessageReactsSelectorProps = Pick< 'convoId' | 'serverId' | 'reacts' | 'sortedReacts' >; -type Props = hasDetailView & { +type Props = { messageId: string; hasReactLimit?: boolean; onClick: (emoji: string) => void; @@ -151,6 +151,8 @@ type Props = hasDetailView & { }; 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([]); diff --git a/ts/components/conversation/message/message-content/MessageStatus.tsx b/ts/components/conversation/message/message-content/MessageStatus.tsx index eeaa3da59..41bcdb42c 100644 --- a/ts/components/conversation/message/message-content/MessageStatus.tsx +++ b/ts/components/conversation/message/message-content/MessageStatus.tsx @@ -5,14 +5,14 @@ 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'; import { SessionIcon, SessionIconType } from '../../../icon'; import { ExpireTimer } from '../../ExpireTimer'; -import { hasDetailView } from '../message-item/Message'; -type Props = hasDetailView & { +type Props = { messageId: string; dataTestId?: string | undefined; }; @@ -30,7 +30,9 @@ type Props = hasDetailView & { * - 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); diff --git a/ts/components/conversation/message/message-content/quote/Quote.tsx b/ts/components/conversation/message/message-content/quote/Quote.tsx index b5e86717f..3c42b14ca 100644 --- a/ts/components/conversation/message/message-content/quote/Quote.tsx +++ b/ts/components/conversation/message/message-content/quote/Quote.tsx @@ -4,7 +4,6 @@ import { isEmpty } from 'lodash'; import styled from 'styled-components'; import { useIsMessageSelectionMode } from '../../../../../state/selectors/selectedConversation'; import * as MIME from '../../../../../types/MIME'; -import { hasDetailView } from '../../message-item/Message'; import { QuoteAuthor } from './QuoteAuthor'; import { QuoteIconContainer } from './QuoteIconContainer'; import { QuoteText } from './QuoteText'; @@ -44,7 +43,7 @@ const StyledQuoteTextContent = styled.div` justify-content: center; `; -export type QuoteProps = hasDetailView & { +export type QuoteProps = { author: string; isFromMe: boolean; isIncoming: boolean; @@ -71,7 +70,7 @@ export interface QuotedAttachmentType { export const Quote = (props: QuoteProps) => { const isSelectionMode = useIsMessageSelectionMode(); - const { isIncoming, attachment, text, isDetailView, referencedMessageNotFound, onClick } = props; + const { isIncoming, attachment, text, referencedMessageNotFound, onClick } = props; const [imageBroken, setImageBroken] = useState(false); const handleImageErrorBound = () => { @@ -96,7 +95,7 @@ export const Quote = (props: QuoteProps) => { referencedMessageNotFound={referencedMessageNotFound} /> - + ` +const StyledQuoteAuthor = styled.div<{ isIncoming: boolean }>` color: ${props => props.isIncoming ? 'var(--message-bubbles-received-text-color)' @@ -17,17 +16,18 @@ const StyledQuoteAuthor = styled.div` line-height: 18px; margin-bottom: 2px; overflow-x: hidden; - white-space: ${props => (props.isDetailView ? undefined : 'nowrap')}; + white-space: nowrap; text-overflow: ellipsis; + .module-contact-name { font-weight: bold; } `; -type QuoteAuthorProps = Pick & hasDetailView; +type QuoteAuthorProps = Pick; export const QuoteAuthor = (props: QuoteAuthorProps) => { - const { author, isIncoming, isDetailView } = props; + const { author, isIncoming } = props; const isPublic = useSelectedIsPublic(); const { authorName, isMe } = useQuoteAuthorName(author); @@ -37,7 +37,7 @@ export const QuoteAuthor = (props: QuoteAuthorProps) => { } return ( - + { + extends Omit { messageId: string; isControlMessage?: boolean; } @@ -110,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; @@ -136,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 ( diff --git a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx index 2a2d28a5c..ab1254b97 100644 --- a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx @@ -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'; @@ -14,7 +15,6 @@ import { } from '../../../../state/selectors/conversations'; import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus'; import { StyledMessageReactionsContainer } from '../message-content/MessageReactions'; -import { hasDetailView } from './Message'; export type GenericReadableMessageSelectorProps = Pick< MessageRenderingProps, @@ -27,7 +27,7 @@ export type GenericReadableMessageSelectorProps = Pick< | 'isDeleted' >; -type Props = hasDetailView & { +type Props = { messageId: string; ctxMenuID: string; }; @@ -38,12 +38,11 @@ const highlightedMessageAnimation = keyframes` } `; -const StyledReadableMessage = styled.div< - hasDetailView & { - selected: boolean; - isRightClicked: boolean; - } ->` +const StyledReadableMessage = styled.div<{ + selected: boolean; + isDetailView: boolean; + isRightClicked: boolean; +}>` display: flex; align-items: center; width: 100%; @@ -65,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); @@ -149,7 +150,6 @@ export const GenericReadableMessage = (props: Props) => { diff --git a/ts/components/conversation/message/message-item/Message.tsx b/ts/components/conversation/message/message-item/Message.tsx index b18400ed2..b038a3de8 100644 --- a/ts/components/conversation/message/message-item/Message.tsx +++ b/ts/components/conversation/message/message-item/Message.tsx @@ -10,10 +10,7 @@ import { GenericReadableMessage } from './GenericReadableMessage'; // Same as MIN_WIDTH in ImageGrid.tsx export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = THUMBNAIL_SIDE; -/** when the message info panel is displayed for a message, we disable onClick and make some other minor UI changes */ -export type hasDetailView = { isDetailView?: boolean }; - -type Props = hasDetailView & { +type Props = { messageId: string; }; @@ -28,11 +25,5 @@ export const Message = (props: Props) => { return null; } - return ( - - ); + return ; }; diff --git a/ts/components/conversation/message/message-item/ReadableMessage.tsx b/ts/components/conversation/message/message-item/ReadableMessage.tsx index 619563732..0d436d9d8 100644 --- a/ts/components/conversation/message/message-item/ReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ReadableMessage.tsx @@ -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 diff --git a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx index 496babbeb..5e271d796 100644 --- a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx +++ b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx @@ -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 ( - - - + + + + + ); }; diff --git a/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx index f032d36ca..d1ce3e5f7 100644 --- a/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx +++ b/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx @@ -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'; diff --git a/ts/components/leftpane/conversation-list-item/HeaderItem.tsx b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx index 1f435bfb1..81d37e82f 100644 --- a/ts/components/leftpane/conversation-list-item/HeaderItem.tsx +++ b/ts/components/leftpane/conversation-list-item/HeaderItem.tsx @@ -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 = () => { diff --git a/ts/components/leftpane/conversation-list-item/MessageItem.tsx b/ts/components/leftpane/conversation-list-item/MessageItem.tsx index e4fc216fb..5b0d8bf1a 100644 --- a/ts/components/leftpane/conversation-list-item/MessageItem.tsx +++ b/ts/components/leftpane/conversation-list-item/MessageItem.tsx @@ -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 = () => { diff --git a/ts/components/leftpane/conversation-list-item/UserItem.tsx b/ts/components/leftpane/conversation-list-item/UserItem.tsx index ec293ae3c..fc6ad9028 100644 --- a/ts/components/leftpane/conversation-list-item/UserItem.tsx +++ b/ts/components/leftpane/conversation-list-item/UserItem.tsx @@ -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 ( -
- -
+ ); }; diff --git a/ts/components/menu/ConversationListItemContextMenu.tsx b/ts/components/menu/ConversationListItemContextMenu.tsx index 9e3e0680b..d0e2808e9 100644 --- a/ts/components/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/menu/ConversationListItemContextMenu.tsx @@ -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; diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx index 69cb07118..407dfe289 100644 --- a/ts/components/menu/Menu.tsx +++ b/ts/components/menu/Menu.tsx @@ -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 */ diff --git a/ts/components/search/MessageSearchResults.tsx b/ts/components/search/MessageSearchResults.tsx index 13ac83098..e452d194c 100644 --- a/ts/components/search/MessageSearchResults.tsx +++ b/ts/components/search/MessageSearchResults.tsx @@ -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` diff --git a/ts/components/leftpane/conversation-list-item/ConvoIdContext.tsx b/ts/contexts/ConvoIdContext.tsx similarity index 100% rename from ts/components/leftpane/conversation-list-item/ConvoIdContext.tsx rename to ts/contexts/ConvoIdContext.tsx diff --git a/ts/contexts/ScrollToLoadedMessage.tsx b/ts/contexts/ScrollToLoadedMessage.tsx new file mode 100644 index 000000000..3f1e9b80d --- /dev/null +++ b/ts/contexts/ScrollToLoadedMessage.tsx @@ -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); +} diff --git a/ts/contexts/isDetailViewContext.tsx b/ts/contexts/isDetailViewContext.tsx new file mode 100644 index 000000000..e2f31cf84 --- /dev/null +++ b/ts/contexts/isDetailViewContext.tsx @@ -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(false); + +export function useIsDetailMessageView() { + return useContext(IsDetailMessageViewContext); +} diff --git a/ts/contexts/isMessageVisibleContext.tsx b/ts/contexts/isMessageVisibleContext.tsx new file mode 100644 index 000000000..814f10167 --- /dev/null +++ b/ts/contexts/isMessageVisibleContext.tsx @@ -0,0 +1,7 @@ +import { createContext, useContext } from 'react'; + +export const IsMessageVisibleContext = createContext(false); + +export function useIsMessageVisible() { + return useContext(IsMessageVisibleContext); +}