You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/components/leftpane/conversation-list-item/ConversationListItem.tsx

140 lines
4.3 KiB
TypeScript

import classNames from 'classnames';
import { isNil } from 'lodash';
import React, { useCallback } from 'react';
import { contextMenu } from 'react-contexify';
import { createPortal } from 'react-dom';
import { useDispatch, useSelector } from 'react-redux';
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,
useHasUnread,
useIsBlocked,
useIsPrivate,
useMentionedUs,
} from '../../../hooks/useParamSelector';
import { isSearching } from '../../../state/selectors/search';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu';
import { ConversationListItemHeaderItem } from './HeaderItem';
import { MessageItem } from './MessageItem';
type PropsHousekeeping = {
style?: object;
};
type Props = { conversationId: string } & PropsHousekeeping;
const Portal = ({ children }: { children: any }) => {
return createPortal(children, document.querySelector('.inbox.index') as Element);
};
const AvatarItem = () => {
const conversationId = useConvoIdFromContext();
const userName = useConversationUsername(conversationId);
const isPrivate = useIsPrivate(conversationId);
const avatarPath = useAvatarPath(conversationId);
const dispatch = useDispatch();
function onPrivateAvatarClick() {
dispatch(
updateUserDetailsModal({
conversationId,
userName: userName || '',
authorAvatarPath: avatarPath,
})
);
}
return (
<div>
<Avatar
size={AvatarSize.S}
pubkey={conversationId}
onAvatarClick={isPrivate ? onPrivateAvatarClick : undefined}
/>
</div>
);
};
const ConversationListItemInner = (props: Props) => {
const { conversationId, style } = props;
const key = `conversation-item-${conversationId}`;
const hasUnread = useHasUnread(conversationId);
let hasUnreadMentionedUs = useMentionedUs(conversationId);
let isBlocked = useIsBlocked(conversationId);
const isSearch = useSelector(isSearching);
const selectedConvo = useSelectedConversationKey();
const isSelectedConvo = conversationId === selectedConvo && !isNil(selectedConvo);
if (isSearch) {
// force isBlocked and hasUnreadMentionedUs to be false, we just want to display the row without any special style when showing search results
hasUnreadMentionedUs = false;
isBlocked = false;
}
const triggerId = `${key}-ctxmenu`;
const openConvo = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
// mousedown is invoked sooner than onClick, but for both right and left click
if (e.button === 0) {
void openConversationWithMessages({ conversationKey: conversationId, messageId: null });
}
},
[conversationId]
);
return (
<ContextConversationProvider value={conversationId}>
<div key={key}>
<div
role="button"
onMouseDown={openConvo}
onMouseUp={e => {
e.stopPropagation();
e.preventDefault();
}}
onContextMenu={e => {
contextMenu.show({
id: triggerId,
event: e,
});
}}
style={style}
className={classNames(
'module-conversation-list-item',
hasUnread ? 'module-conversation-list-item--has-unread' : null,
hasUnreadMentionedUs ? 'module-conversation-list-item--mentioned-us' : null,
isSelectedConvo ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<AvatarItem />
<div className="module-conversation-list-item__content">
<ConversationListItemHeaderItem />
<MessageItem />
</div>
</div>
<Portal>
<MemoConversationListItemContextMenu triggerId={triggerId} />
</Portal>
</div>
</ContextConversationProvider>
);
};
export const ConversationListItem = ConversationListItemInner;