use React Provider for convoListItem (#2088)

this is to avoid passing down the prop to all the components
This commit is contained in:
Audric Ackermann 2021-12-16 15:13:04 +11:00 committed by GitHub
parent 38325215e6
commit abd146c4ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 468 additions and 473 deletions

View File

@ -8,7 +8,7 @@ import _ from 'underscore';
import { NotificationBubble } from './notification-bubble/NotificationBubble';
import { ReadableMessage } from './ReadableMessage';
import { arrayContainsUsOnly } from '../../../../models/message';
import { useConversationsUsernameOrFull } from '../../../../hooks/useParamSelector';
import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector';
// This component is used to display group updates in the conversation view.
@ -16,7 +16,7 @@ const ChangeItemJoined = (added: Array<string>): string => {
if (!added.length) {
throw new Error('Group update add is missing contacts');
}
const names = useConversationsUsernameOrFull(added);
const names = useConversationsUsernameWithQuoteOrFullPubkey(added);
const joinKey = added.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup';
return window.i18n(joinKey, [names.join(', ')]);
};
@ -25,7 +25,7 @@ const ChangeItemKicked = (kicked: Array<string>): string => {
if (!kicked.length) {
throw new Error('Group update kicked is missing contacts');
}
const names = useConversationsUsernameOrFull(kicked);
const names = useConversationsUsernameWithQuoteOrFullPubkey(kicked);
if (arrayContainsUsOnly(kicked)) {
return window.i18n('youGotKickedFromGroup');
@ -40,7 +40,7 @@ const ChangeItemLeft = (left: Array<string>): string => {
throw new Error('Group update remove is missing contacts');
}
const names = useConversationsUsernameOrFull(left);
const names = useConversationsUsernameWithQuoteOrFullPubkey(left);
if (arrayContainsUsOnly(left)) {
return window.i18n('youLeftTheGroup');

View File

@ -1,400 +0,0 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import { contextMenu } from 'react-contexify';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { Timestamp } from '../conversation/Timestamp';
import { ContactName } from '../conversation/ContactName';
import { TypingAnimation } from '../conversation/TypingAnimation';
import { createPortal } from 'react-dom';
import styled from 'styled-components';
import { PubKey } from '../../session/types';
import {
LastMessageType,
openConversationWithMessages,
ReduxConversationType,
} from '../../state/ducks/conversations';
import _ from 'underscore';
import { useDispatch, useSelector } from 'react-redux';
import { SectionType } from '../../state/ducks/section';
import { getFocusedSection } from '../../state/selectors/section';
import { ConversationNotificationSettingType } from '../../models/conversation';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
import { updateUserDetailsModal } from '../../state/ducks/modalDialog';
import { approveConversation, blockConvoById } from '../../interactions/conversationInteractions';
import { useAvatarPath, useConversationUsername, useIsMe } from '../../hooks/useParamSelector';
import { MessageBody } from '../conversation/message/message-content/MessageBody';
import { OutgoingMessageStatus } from '../conversation/message/message-content/OutgoingMessageStatus';
import { SessionIcon, SessionIconButton } from '../icon';
import { MemoConversationListItemContextMenu } from '../menu/ConversationListItemContextMenu';
// tslint:disable-next-line: no-empty-interface
export interface ConversationListItemProps extends ReduxConversationType {}
export const StyledConversationListItemIconWrapper = styled.div`
svg {
margin: 0px 2px;
}
display: flex;
flex-direction: row;
`;
type PropsHousekeeping = {
style?: Object;
isMessageRequest?: boolean;
};
// tslint:disable: use-simple-attributes
type Props = ConversationListItemProps & PropsHousekeeping;
const Portal = ({ children }: { children: any }) => {
return createPortal(children, document.querySelector('.inbox.index') as Element);
};
const HeaderItem = (props: {
unreadCount: number;
mentionedUs: boolean;
activeAt?: number;
conversationId: string;
isPinned: boolean;
currentNotificationSetting: ConversationNotificationSettingType;
}) => {
const {
unreadCount,
mentionedUs,
activeAt,
isPinned,
conversationId,
currentNotificationSetting,
} = props;
let atSymbol = null;
let unreadCountDiv = null;
if (unreadCount > 0) {
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
}
const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message;
const pinIcon =
isMessagesSection && isPinned ? (
<SessionIcon iconType="pin" iconColor={'var(--color-text-subtle)'} iconSize="small" />
) : null;
const NotificationSettingIcon = () => {
if (!isMessagesSection) {
return null;
}
switch (currentNotificationSetting) {
case 'all':
return null;
case 'disabled':
return (
<SessionIcon iconType="mute" iconColor={'var(--color-text-subtle)'} iconSize="small" />
);
case 'mentions_only':
return (
<SessionIcon iconType="bell" iconColor={'var(--color-text-subtle)'} iconSize="small" />
);
default:
return null;
}
};
return (
<div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
)}
>
<UserItem conversationId={conversationId} />
</div>
<StyledConversationListItemIconWrapper>
{pinIcon}
<NotificationSettingIcon />
</StyledConversationListItemIconWrapper>
{unreadCountDiv}
{atSymbol}
<div
className={classNames(
'module-conversation-list-item__header__date',
unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
)}
>
<Timestamp timestamp={activeAt} extended={false} isConversationListItem={true} />
</div>
</div>
);
};
const UserItem = (props: { conversationId: string }) => {
const { conversationId } = props;
const shortenedPubkey = PubKey.shorten(conversationId);
const isMe = useIsMe(conversationId);
const username = useConversationUsername(conversationId);
const displayedPubkey = username ? shortenedPubkey : conversationId;
const displayName = isMe ? window.i18n('noteToSelf') : username;
let shouldShowPubkey = false;
if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) {
shouldShowPubkey = true;
}
return (
<div className="module-conversation__user">
<ContactName
pubkey={displayedPubkey}
name={username}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
);
};
const MessageItem = (props: {
lastMessage?: LastMessageType;
isTyping: boolean;
unreadCount: number;
isMessageRequest: boolean;
conversationId: string;
}) => {
const { lastMessage, isTyping, unreadCount } = props;
if (!lastMessage && !isTyping) {
return null;
}
const text = lastMessage?.text || '';
if (isEmpty(text)) {
return null;
}
return (
<div className="module-conversation-list-item__message">
<div
className={classNames(
'module-conversation-list-item__message__text',
unreadCount > 0 ? 'module-conversation-list-item__message__text--has-unread' : null
)}
>
{isTyping ? (
<TypingAnimation />
) : (
<MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
)}
</div>
<MessageRequestButtons
conversationId={props.conversationId}
isMessageRequest={props.isMessageRequest}
/>
{lastMessage && lastMessage.status && !props.isMessageRequest ? (
<OutgoingMessageStatus status={lastMessage.status} />
) : null}
</div>
);
};
const AvatarItem = (props: { conversationId: string; isPrivate: boolean }) => {
const { isPrivate, conversationId } = props;
const userName = useConversationUsername(conversationId);
const avatarPath = useAvatarPath(conversationId);
const dispatch = useDispatch();
function onPrivateAvatarClick() {
dispatch(
updateUserDetailsModal({
conversationId: conversationId,
userName: userName || '',
authorAvatarPath: avatarPath,
})
);
}
return (
<div className="module-conversation-list-item__avatar-container">
<Avatar
size={AvatarSize.S}
pubkey={conversationId}
onAvatarClick={isPrivate ? onPrivateAvatarClick : undefined}
/>
</div>
);
};
const RejectMessageRequestButton = ({ conversationId }: { conversationId: string }) => {
/**
* Removes conversation from requests list,
* adds ID to block list, syncs the block with linked devices.
*/
const handleConversationBlock = async () => {
await blockConvoById(conversationId);
await forceSyncConfigurationNowIfNeeded();
};
return (
<SessionIconButton
iconType="exit"
iconSize="large"
onClick={handleConversationBlock}
backgroundColor="var(--color-destructive)"
iconColor="var(--color-foreground-primary)"
iconPadding="var(--margins-xs)"
borderRadius="2px"
margin="0 5px 0 0"
/>
);
};
const ApproveMessageRequestButton = ({ conversationId }: { conversationId: string }) => {
return (
<SessionIconButton
iconType="check"
iconSize="large"
onClick={async () => {
await approveConversation(conversationId);
}}
backgroundColor="var(--color-accent)"
iconColor="var(--color-foreground-primary)"
iconPadding="var(--margins-xs)"
borderRadius="2px"
/>
);
};
const MessageRequestButtons = ({
conversationId,
isMessageRequest,
}: {
conversationId: string;
isMessageRequest: boolean;
}) => {
if (!isMessageRequest) {
return null;
}
return (
<>
<RejectMessageRequestButton conversationId={conversationId} />
<ApproveMessageRequestButton conversationId={conversationId} />
</>
);
};
// tslint:disable: max-func-body-length
const ConversationListItem = (props: Props) => {
const {
activeAt,
unreadCount,
id: conversationId,
isSelected,
isBlocked,
style,
mentionedUs,
isMe,
isPinned,
isTyping,
lastMessage,
hasNickname,
isKickedFromGroup,
left,
type,
isPublic,
avatarPath,
isPrivate,
currentNotificationSetting,
weAreAdmin,
isMessageRequest,
} = props;
const triggerId = `conversation-item-${conversationId}-ctxmenu`;
const key = `conversation-item-${conversationId}`;
const openConvo = useCallback(
async (e: React.MouseEvent<HTMLDivElement>) => {
// mousedown is invoked sooner than onClick, but for both right and left click
if (e.button === 0) {
await openConversationWithMessages({ conversationKey: conversationId });
}
},
[conversationId]
);
return (
<div key={key}>
<div
role="button"
onMouseDown={openConvo}
onMouseUp={e => {
e.stopPropagation();
e.preventDefault();
}}
onContextMenu={(e: any) => {
contextMenu.show({
id: triggerId,
event: e,
});
}}
style={style}
className={classNames(
'module-conversation-list-item',
unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
unreadCount && unreadCount > 0 && mentionedUs
? 'module-conversation-list-item--mentioned-us'
: null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<AvatarItem conversationId={conversationId} isPrivate={isPrivate || false} />
<div className="module-conversation-list-item__content">
<HeaderItem
mentionedUs={!!mentionedUs}
unreadCount={unreadCount || 0}
activeAt={activeAt}
isPinned={!!isPinned}
conversationId={conversationId}
currentNotificationSetting={currentNotificationSetting || 'all'}
/>
<MessageItem
isTyping={!!isTyping}
unreadCount={unreadCount || 0}
lastMessage={lastMessage}
isMessageRequest={Boolean(isMessageRequest)}
conversationId={conversationId}
/>
</div>
</div>
<Portal>
<MemoConversationListItemContextMenu
triggerId={triggerId}
conversationId={conversationId}
hasNickname={!!hasNickname}
isBlocked={!!isBlocked}
isPrivate={!!isPrivate}
isKickedFromGroup={!!isKickedFromGroup}
isMe={!!isMe}
isPublic={!!isPublic}
left={!!left}
type={type}
currentNotificationSetting={currentNotificationSetting || 'all'}
avatarPath={avatarPath || null}
weAreAdmin={weAreAdmin}
/>
</Portal>
</div>
);
};
export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual);

View File

@ -1,6 +1,6 @@
import React from 'react';
import { MemoConversationListItemWithDetails } from './ConversationListItem';
import { MemoConversationListItemWithDetails } from './conversation-list-item/ConversationListItem';
import { AutoSizer, List } from 'react-virtualized';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { useSelector } from 'react-redux';

View File

@ -3,7 +3,7 @@ import { AutoSizer, List } from 'react-virtualized';
import {
ConversationListItemProps,
MemoConversationListItemWithDetails,
} from './ConversationListItem';
} from './conversation-list-item/ConversationListItem';
import { ReduxConversationType } from '../../state/ducks/conversations';
import { SearchResults, SearchResultsProps } from '../search/SearchResults';
import { UserUtils } from '../../session/utils';

View File

@ -0,0 +1,142 @@
import React, { useCallback, useContext } from 'react';
import classNames from 'classnames';
import { contextMenu } from 'react-contexify';
import { Avatar, AvatarSize } from '../../avatar/Avatar';
import { createPortal } from 'react-dom';
import {
openConversationWithMessages,
ReduxConversationType,
} from '../../../state/ducks/conversations';
import _ from 'underscore';
import { useDispatch } from 'react-redux';
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog';
import {
useAvatarPath,
useConversationUsername,
useIsPrivate,
} from '../../../hooks/useParamSelector';
import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu';
import { HeaderItem } from './HeaderItem';
import { MessageItem } from './MessageItem';
// tslint:disable-next-line: no-empty-interface
export type ConversationListItemProps = Pick<
ReduxConversationType,
'unreadCount' | 'id' | 'isSelected' | 'isBlocked' | 'mentionedUs' | 'unreadCount' | 'profileName'
>;
/**
* This React context is used to share deeply in the tree of the ConversationListItem what is the ID we are currently rendering.
* This is to avoid passing the prop to all the subtree component
*/
export const ContextConversationId = React.createContext('');
type PropsHousekeeping = {
style?: Object;
isMessageRequest?: boolean;
};
// tslint:disable: use-simple-attributes
type Props = ConversationListItemProps & PropsHousekeeping;
const Portal = ({ children }: { children: any }) => {
return createPortal(children, document.querySelector('.inbox.index') as Element);
};
const AvatarItem = () => {
const conversationId = useContext(ContextConversationId);
const userName = useConversationUsername(conversationId);
const isPrivate = useIsPrivate(conversationId);
const avatarPath = useAvatarPath(conversationId);
const dispatch = useDispatch();
function onPrivateAvatarClick() {
dispatch(
updateUserDetailsModal({
conversationId: conversationId,
userName: userName || '',
authorAvatarPath: avatarPath,
})
);
}
return (
<div className="module-conversation-list-item__avatar-container">
<Avatar
size={AvatarSize.S}
pubkey={conversationId}
onAvatarClick={isPrivate ? onPrivateAvatarClick : undefined}
/>
</div>
);
};
// tslint:disable: max-func-body-length
const ConversationListItem = (props: Props) => {
const {
unreadCount,
id: conversationId,
isSelected,
isBlocked,
style,
mentionedUs,
isMessageRequest,
} = props;
const triggerId = `conversation-item-${conversationId}-ctxmenu`;
const key = `conversation-item-${conversationId}`;
const openConvo = useCallback(
async (e: React.MouseEvent<HTMLDivElement>) => {
// mousedown is invoked sooner than onClick, but for both right and left click
if (e.button === 0) {
await openConversationWithMessages({ conversationKey: conversationId });
}
},
[conversationId]
);
return (
<ContextConversationId.Provider value={conversationId}>
<div key={key}>
<div
role="button"
onMouseDown={openConvo}
onMouseUp={e => {
e.stopPropagation();
e.preventDefault();
}}
onContextMenu={(e: any) => {
contextMenu.show({
id: triggerId,
event: e,
});
}}
style={style}
className={classNames(
'module-conversation-list-item',
unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
unreadCount && unreadCount > 0 && mentionedUs
? 'module-conversation-list-item--mentioned-us'
: null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<AvatarItem />
<div className="module-conversation-list-item__content">
<HeaderItem />
<MessageItem isMessageRequest={Boolean(isMessageRequest)} />
</div>
</div>
<Portal>
<MemoConversationListItemContextMenu triggerId={triggerId} />
</Portal>
</div>
</ContextConversationId.Provider>
);
};
export const MemoConversationListItemWithDetails = React.memo(ConversationListItem, _.isEqual);

View File

@ -0,0 +1,116 @@
import classNames from 'classnames';
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useConversationPropsById, useIsPinned } from '../../../hooks/useParamSelector';
import { SectionType } from '../../../state/ducks/section';
import { getFocusedSection } from '../../../state/selectors/section';
import { Timestamp } from '../../conversation/Timestamp';
import { SessionIcon } from '../../icon';
import { ContextConversationId } from './ConversationListItem';
import { UserItem } from './UserItem';
const NotificationSettingIcon = (props: { isMessagesSection: boolean }) => {
const convoSetting = useSelector(useConversationPropsById)?.currentNotificationSetting;
if (!props.isMessagesSection) {
return null;
}
switch (convoSetting) {
case 'all':
return null;
case 'disabled':
return (
<SessionIcon iconType="mute" iconColor={'var(--color-text-subtle)'} iconSize="small" />
);
case 'mentions_only':
return (
<SessionIcon iconType="bell" iconColor={'var(--color-text-subtle)'} iconSize="small" />
);
default:
return null;
}
};
const StyledConversationListItemIconWrapper = styled.div`
svg {
margin: 0px 2px;
}
display: flex;
flex-direction: row;
`;
function useHeaderItemProps(conversationId: string) {
const convoProps = useConversationPropsById(conversationId);
if (!convoProps) {
return null;
}
return {
isPinned: !!convoProps.isPinned,
mentionedUs: convoProps.mentionedUs || false,
unreadCount: convoProps.unreadCount || 0,
activeAt: convoProps.activeAt,
};
}
const ListItemIcons = () => {
const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message;
const conversationId = useContext(ContextConversationId);
const isPinned = useIsPinned(conversationId);
const pinIcon =
isMessagesSection && isPinned ? (
<SessionIcon iconType="pin" iconColor={'var(--color-text-subtle)'} iconSize="small" />
) : null;
return (
<StyledConversationListItemIconWrapper>
{pinIcon}
<NotificationSettingIcon isMessagesSection={isMessagesSection} />
</StyledConversationListItemIconWrapper>
);
};
export const HeaderItem = () => {
const conversationId = useContext(ContextConversationId);
const convoProps = useHeaderItemProps(conversationId);
if (!convoProps) {
return null;
}
const { unreadCount, mentionedUs, activeAt } = convoProps;
let atSymbol = null;
let unreadCountDiv = null;
if (unreadCount > 0) {
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
}
return (
<div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
)}
>
<UserItem />
</div>
<ListItemIcons />
{unreadCountDiv}
{atSymbol}
<div
className={classNames(
'module-conversation-list-item__header__date',
unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
)}
>
<Timestamp timestamp={activeAt} extended={false} isConversationListItem={true} />
</div>
</div>
);
};

View File

@ -0,0 +1,61 @@
import classNames from 'classnames';
import React, { useContext } from 'react';
import { isEmpty } from 'underscore';
import { useConversationPropsById } from '../../../hooks/useParamSelector';
import { MessageBody } from '../../conversation/message/message-content/MessageBody';
import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus';
import { TypingAnimation } from '../../conversation/TypingAnimation';
import { ContextConversationId } from './ConversationListItem';
import { MessageRequestButtons } from './MessageRequest';
function useMessageItemProps(convoId: string) {
const convoProps = useConversationPropsById(convoId);
if (!convoProps) {
return null;
}
return {
isTyping: !!convoProps.isTyping,
lastMessage: convoProps.lastMessage,
unreadCount: convoProps.unreadCount || 0,
};
}
export const MessageItem = (props: { isMessageRequest: boolean }) => {
const conversationId = useContext(ContextConversationId);
const convoProps = useMessageItemProps(conversationId);
if (!convoProps) {
return null;
}
const { lastMessage, isTyping, unreadCount } = convoProps;
if (!lastMessage && !isTyping) {
return null;
}
const text = lastMessage?.text || '';
if (isEmpty(text)) {
return null;
}
return (
<div className="module-conversation-list-item__message">
<div
className={classNames(
'module-conversation-list-item__message__text',
unreadCount > 0 ? 'module-conversation-list-item__message__text--has-unread' : null
)}
>
{isTyping ? (
<TypingAnimation />
) : (
<MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
)}
</div>
<MessageRequestButtons isMessageRequest={props.isMessageRequest} />
{lastMessage && lastMessage.status && !props.isMessageRequest ? (
<OutgoingMessageStatus status={lastMessage.status} />
) : null}
</div>
);
};

View File

@ -0,0 +1,64 @@
import React, { useContext } from 'react';
import {
approveConversation,
blockConvoById,
} from '../../../interactions/conversationInteractions';
import { forceSyncConfigurationNowIfNeeded } from '../../../session/utils/syncUtils';
import { SessionIconButton } from '../../icon';
import { ContextConversationId } from './ConversationListItem';
const RejectMessageRequestButton = () => {
const conversationId = useContext(ContextConversationId);
/**
* Removes conversation from requests list,
* adds ID to block list, syncs the block with linked devices.
*/
const handleConversationBlock = async () => {
await blockConvoById(conversationId);
await forceSyncConfigurationNowIfNeeded();
};
return (
<SessionIconButton
iconType="exit"
iconSize="large"
onClick={handleConversationBlock}
backgroundColor="var(--color-destructive)"
iconColor="var(--color-foreground-primary)"
iconPadding="var(--margins-xs)"
borderRadius="2px"
margin="0 5px 0 0"
/>
);
};
const ApproveMessageRequestButton = () => {
const conversationId = useContext(ContextConversationId);
return (
<SessionIconButton
iconType="check"
iconSize="large"
onClick={async () => {
await approveConversation(conversationId);
}}
backgroundColor="var(--color-accent)"
iconColor="var(--color-foreground-primary)"
iconPadding="var(--margins-xs)"
borderRadius="2px"
/>
);
};
export const MessageRequestButtons = ({ isMessageRequest }: { isMessageRequest: boolean }) => {
if (!isMessageRequest) {
return null;
}
return (
<>
<RejectMessageRequestButton />
<ApproveMessageRequestButton />
</>
);
};

View File

@ -0,0 +1,34 @@
import React, { useContext } from 'react';
import { useConversationUsername, useIsMe } from '../../../hooks/useParamSelector';
import { PubKey } from '../../../session/types';
import { ContactName } from '../../conversation/ContactName';
import { ContextConversationId } from './ConversationListItem';
export const UserItem = () => {
const conversationId = useContext(ContextConversationId);
const shortenedPubkey = PubKey.shorten(conversationId);
const isMe = useIsMe(conversationId);
const username = useConversationUsername(conversationId);
const displayedPubkey = username ? shortenedPubkey : conversationId;
const displayName = isMe ? window.i18n('noteToSelf') : username;
let shouldShowPubkey = false;
if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) {
shouldShowPubkey = true;
}
return (
<div className="module-conversation__user">
<ContactName
pubkey={displayedPubkey}
name={username}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
);
};

View File

@ -4,7 +4,7 @@ import React from 'react';
import { SpacerLG } from '../../basic/Text';
import { useDispatch, useSelector } from 'react-redux';
import { getConversationRequests } from '../../../state/selectors/conversations';
import { MemoConversationListItemWithDetails } from '../ConversationListItem';
import { MemoConversationListItemWithDetails } from '../conversation-list-item/ConversationListItem';
import styled from 'styled-components';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../basic/SessionButton';
import { OverlayHeader } from './OverlayHeader';

View File

@ -1,11 +1,13 @@
import React from 'react';
import React, { useContext } from 'react';
import { animation, Menu } from 'react-contexify';
import _ from 'underscore';
import { useAvatarPath, useConversationUsername } from '../../hooks/useParamSelector';
import {
ConversationNotificationSettingType,
ConversationTypeEnum,
} from '../../models/conversation';
useAvatarPath,
useConversationPropsById,
useConversationUsername,
} from '../../hooks/useParamSelector';
import { ConversationTypeEnum } from '../../models/conversation';
import { ContextConversationId } from '../leftpane/conversation-list-item/ConversationListItem';
import {
getBanMenuItem,
@ -25,26 +27,18 @@ import {
} from './Menu';
export type PropsContextConversationItem = {
conversationId: string;
triggerId: string;
type: ConversationTypeEnum;
isMe: boolean;
isPublic: boolean;
isPrivate: boolean;
isBlocked: boolean;
hasNickname: boolean;
isKickedFromGroup: boolean;
left: boolean;
theme?: any;
currentNotificationSetting: ConversationNotificationSettingType;
avatarPath: string | null;
weAreAdmin?: boolean;
};
const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {
const conversationId = useContext(ContextConversationId);
const itemMenuProps = useConversationPropsById(conversationId);
const { triggerId } = props;
if (!itemMenuProps) {
return null;
}
const {
conversationId,
triggerId,
isBlocked,
isMe,
isPublic,
@ -55,7 +49,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
currentNotificationSetting,
isPrivate,
weAreAdmin,
} = props;
} = itemMenuProps;
const isGroup = type === 'group';

View File

@ -449,7 +449,7 @@ export function getNotificationForConvoMenuItem({
left: boolean | undefined;
isBlocked: boolean | undefined;
isPrivate: boolean | undefined;
currentNotificationSetting: ConversationNotificationSettingType;
currentNotificationSetting: ConversationNotificationSettingType | undefined;
conversationId: string;
}): JSX.Element | null {
if (showNotificationConvo(Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked))) {
@ -461,7 +461,7 @@ export function getNotificationForConvoMenuItem({
).map((n: ConversationNotificationSettingType) => {
// do this separately so typescript's compiler likes it
const keyToUse: LocalizerKeys =
n === 'all'
n === 'all' || !n
? 'notificationForConvo_all'
: n === 'disabled'
? 'notificationForConvo_disabled'

View File

@ -2,7 +2,7 @@ import React from 'react';
import {
ConversationListItemProps,
MemoConversationListItemWithDetails,
} from '../leftpane/ConversationListItem';
} from '../leftpane/conversation-list-item/ConversationListItem';
export type SearchResultsProps = {
contacts: Array<ConversationListItemProps>;

View File

@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import { PubKey } from '../../session/types';
import { ConversationListItemProps } from '../leftpane/ConversationListItem';
import { ConversationListItemProps } from '../leftpane/conversation-list-item/ConversationListItem';
export type Props = {
contacts: Array<ConversationListItemProps>;

View File

@ -3,13 +3,9 @@ import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
import { StateType } from '../state/reducer';
export function useAvatarPath(pubkey: string | undefined) {
return useSelector((state: StateType) => {
if (!pubkey) {
return null;
}
return state.conversations.conversationLookup[pubkey]?.avatarPath || null;
});
export function useAvatarPath(convoId: string | undefined) {
const convoProps = useConversationPropsById(convoId);
return convoProps?.avatarPath || null;
}
export function useOurAvatarPath() {
@ -20,39 +16,25 @@ export function useOurAvatarPath() {
*
* @returns convo.profileName || convo.name || convo.id or undefined if the convo is not found
*/
export function useConversationUsername(pubkey?: string) {
return useSelector((state: StateType) => {
if (!pubkey) {
return undefined;
}
const convo = state.conversations.conversationLookup[pubkey];
if (!convo) {
return pubkey;
}
return convo?.profileName || convo?.name || convo.id;
});
export function useConversationUsername(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return convoProps?.profileName || convoProps?.name || convoId;
}
/**
* Returns either the nickname, profileName, or the shorten pubkey
*/
export function useConversationUsernameOrShorten(pubkey?: string) {
return useSelector((state: StateType) => {
if (!pubkey) {
return undefined;
}
const convo = state.conversations.conversationLookup[pubkey];
if (!convo) {
return PubKey.shorten(pubkey);
}
return convo?.profileName || convo?.name || PubKey.shorten(convo.id);
});
export function useConversationUsernameOrShorten(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return convoProps?.profileName || convoProps?.name || (convoId && PubKey.shorten(convoId));
}
/**
* Returns either the nickname, profileName, or the shorten of the pubkeys given
* Returns either the nickname, the profileName, in '"' or the full pubkeys given
*/
export function useConversationsUsernameOrFull(pubkeys: Array<string>) {
export function useConversationsUsernameWithQuoteOrFullPubkey(pubkeys: Array<string>) {
return useSelector((state: StateType) => {
return pubkeys.map(pubkey => {
if (pubkey === UserUtils.getOurPubKeyStrFromCache() || pubkey.toLowerCase() === 'you') {
@ -73,16 +55,18 @@ export function useIsMe(pubkey?: string) {
}
export function useIsClosedGroup(convoId?: string) {
return useSelector((state: StateType) => {
if (!convoId) {
return false;
}
const convo = state.conversations.conversationLookup[convoId];
if (!convo) {
return false;
}
return (convo.isGroup && !convo.isPublic) || false;
});
const convoProps = useConversationPropsById(convoId);
return (convoProps && convoProps.isGroup && !convoProps.isPublic) || false;
}
export function useIsPrivate(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return Boolean(convoProps && convoProps.isPrivate);
}
export function useIsPinned(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return Boolean(convoProps && convoProps.isPinned);
}
export function useConversationPropsById(convoId?: string) {