Merge branch 'alex-chats' into chats

This commit is contained in:
Justin 2022-09-12 14:50:02 -04:00
commit 56c617bd32
13 changed files with 100 additions and 61 deletions

View File

@ -113,9 +113,9 @@ const SidebarNavigation = () => {
return (
<SidebarNavigationLink
to='/chats'
icon={require('@tabler/icons/messages.svg')}
icon={require('@tabler/icons/mail.svg')}
count={chatsCount}
text={<FormattedMessage id='tabs_bar.chats' defaultMessage='Chats' />}
text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />}
/>
);
}

View File

@ -17,8 +17,8 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
if (features.chats) {
return (
<ThumbNavigationLink
src={require('@tabler/icons/messages.svg')}
text={<FormattedMessage id='navigation.chats' defaultMessage='Chats' />}
src={require('@tabler/icons/mail.svg')}
text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />}
to='/chats'
exact
count={chatsCount}

View File

@ -1,7 +1,7 @@
import classNames from 'clsx';
import React from 'react';
type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 10
type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 10
const spaces = {
0: 'space-y-0',
@ -12,6 +12,7 @@ const spaces = {
3: 'space-y-3',
4: 'space-y-4',
5: 'space-y-5',
6: 'space-y-6',
10: 'space-y-10',
};

View File

@ -1,4 +1,5 @@
import { useMutation } from '@tanstack/react-query';
import classNames from 'clsx';
import React, { MutableRefObject, useState } from 'react';
import { useIntl, defineMessages } from 'react-intl';
@ -6,7 +7,7 @@ import { uploadMedia } from 'soapbox/actions/media';
import { HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui';
import UploadProgress from 'soapbox/components/upload-progress';
import UploadButton from 'soapbox/features/compose/components/upload_button';
import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import { IChat, useChat } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { truncateFilename } from 'soapbox/utils/media';
@ -23,14 +24,15 @@ const fileKeyGen = (): number => Math.floor((Math.random() * 0x10000));
interface IChatBox {
chat: IChat,
autosize?: boolean,
inputRef?: MutableRefObject<HTMLTextAreaElement>
inputRef?: MutableRefObject<HTMLTextAreaElement>,
className?: string,
}
/**
* Chat UI with just the messages and textarea.
* Reused between floating desktop chats and fullscreen/mobile chats.
*/
const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef, className }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const account = useOwnAccount();
@ -48,7 +50,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), {
retry: false,
onMutate: async(newMessage: any) => {
onMutate: async (newMessage: any) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(['chats', 'messages', chat.id]);
@ -206,7 +208,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
};
return (
<Stack className='overflow-hidden flex flex-grow' onMouseOver={handleMouseOver}>
<Stack className={classNames('overflow-hidden flex flex-grow', className)} onMouseOver={handleMouseOver}>
<div className='flex-grow h-full overflow-hidden flex justify-center'>
<ChatMessageList chat={chat} autosize />
</div>

View File

@ -15,10 +15,11 @@ import Blankslate from './chat-pane/blankslate';
interface IChatList {
onClickChat: (chat: any) => void,
useWindowScroll?: boolean,
fade?: boolean,
searchValue?: string
}
const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false, searchValue }) => {
const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false, searchValue, fade }) => {
const dispatch = useDispatch();
const chatListRef = useRef(null);
@ -75,18 +76,22 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false, s
)}
</PullToRefresh>
<div
className={classNames('inset-x-0 top-0 flex rounded-t-lg justify-center bg-gradient-to-b from-white pb-12 pt-8 pointer-events-none dark:from-gray-900 absolute transition-opacity duration-500', {
'opacity-0': isNearTop,
'opacity-100': !isNearTop,
})}
/>
<div
className={classNames('inset-x-0 bottom-0 flex rounded-b-lg justify-center bg-gradient-to-t from-white pt-12 pb-8 pointer-events-none dark:from-gray-900 absolute transition-opacity duration-500', {
'opacity-0': isNearBottom,
'opacity-100': !isNearBottom,
})}
/>
{fade && (
<>
<div
className={classNames('inset-x-0 top-0 flex rounded-t-lg justify-center bg-gradient-to-b from-white pb-12 pt-8 pointer-events-none dark:from-gray-900 absolute transition-opacity duration-500', {
'opacity-0': isNearTop,
'opacity-100': !isNearTop,
})}
/>
<div
className={classNames('inset-x-0 bottom-0 flex rounded-b-lg justify-center bg-gradient-to-t from-white pt-12 pb-8 pointer-events-none dark:from-gray-900 absolute transition-opacity duration-500', {
'opacity-0': isNearBottom,
'opacity-100': !isNearBottom,
})}
/>
</>
)}
</div>
);
};

View File

@ -283,7 +283,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
title={getFormattedTimestamp(chatMessage)}
className={
classNames({
'text-ellipsis break-words relative rounded-md p-2': true,
'text-ellipsis break-words relative rounded-md p-2 max-w-full': true,
'bg-primary-500 text-white mr-2': isMyMessage,
'bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100 order-2 ml-2': !isMyMessage,
})

View File

@ -84,7 +84,7 @@ const ChatPane = () => {
<ChatList
searchValue={debouncedValue}
onClickChat={handleClickChat}
useWindowScroll={false}
fade
/>
) : (
<Text>no results</Text>

View File

@ -21,12 +21,12 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
data-testid='chat'
>
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
<HStack alignItems='center' space={2}>
<Avatar src={chat.account?.avatar} size={40} />
<HStack alignItems='center' space={2} className='overflow-hidden'>
<Avatar src={chat.account?.avatar} size={40} className='flex-none' />
<Stack alignItems='start'>
<div className='flex items-center space-x-1 flex-grow'>
<Text weight='bold' size='sm' truncate>{chat.account?.display_name || `@${chat.account.username}`}</Text>
<Stack alignItems='start' className='overflow-hidden'>
<div className='flex items-center space-x-1 flex-grow w-full'>
<Text weight='bold' size='sm' align='left' truncate>{chat.account?.display_name || `@${chat.account.username}`}</Text>
{chat.account?.verified && <VerificationBadge />}
</div>
@ -37,7 +37,7 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
weight='medium'
theme='muted'
truncate
className='max-w-[200px]'
className='w-full'
data-testid='chat-last-message'
dangerouslySetInnerHTML={{ __html: chat.last_message?.content }}
/>
@ -54,7 +54,12 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
/>
)}
<RelativeTimestamp timestamp={chat.last_message.created_at} size='sm' />
<RelativeTimestamp
timestamp={chat.last_message.created_at}
align='right'
size='xs'
truncate
/>
</HStack>
)}
</HStack>

View File

@ -1,18 +1,19 @@
import React from 'react';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { launchChat } from 'soapbox/actions/chats';
import AccountSearch from 'soapbox/components/account_search';
import AudioToggle from 'soapbox/features/chats/components/audio-toggle';
import { Column } from '../../components/ui';
import { Card, CardTitle, Stack } from '../../components/ui';
import Chat from './components/chat';
import ChatBox from './components/chat-box';
import ChatList from './components/chat-list';
const messages = defineMessages({
title: { id: 'column.chats', defaultMessage: 'Chats' },
title: { id: 'column.chats', defaultMessage: 'Messages' },
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
});
@ -21,30 +22,44 @@ const ChatIndex: React.FC = () => {
const dispatch = useDispatch();
const history = useHistory();
const [chat, setChat] = useState<any>(null);
const handleSuggestion = (accountId: string) => {
dispatch(launchChat(accountId, history, true));
};
const handleClickChat = (chat: { id: string }) => {
history.push(`/chats/${chat.id}`);
const handleClickChat = (chat: any) => {
// history.push(`/chats/${chat.id}`);
setChat(chat);
};
return (
<Column label={intl.formatMessage(messages.title)}>
<div className='column__switch'>
<AudioToggle />
<Card className='p-0 h-[calc(100vh-176px)] overflow-hidden' variant='rounded'>
<div className='grid grid-cols-9 overflow-hidden h-full'>
<Stack className='col-span-3 p-6 bg-gradient-to-r from-white to-gray-100 overflow-hidden' space={6}>
<CardTitle title={intl.formatMessage(messages.title)} />
<AccountSearch
placeholder={intl.formatMessage(messages.searchPlaceholder)}
onSelected={handleSuggestion}
/>
<Stack className='-mx-3 flex-grow h-full'>
<ChatList onClickChat={handleClickChat} />
</Stack>
</Stack>
<Stack className='col-span-6 h-full overflow-hidden'>
{chat && (
<Stack className='h-full overflow-hidden'>
<Chat chat={chat} onClick={() => {}} />
<div className='h-full overflow-hidden'>
<ChatBox className='h-full overflow-hidden' chat={chat} onSetInputRef={() => {}} />
</div>
</Stack>
)}
</Stack>
</div>
<AccountSearch
placeholder={intl.formatMessage(messages.searchPlaceholder)}
onSelected={handleSuggestion}
/>
<ChatList
onClickChat={handleClickChat}
useWindowScroll
/>
</Column>
</Card>
);
};

View File

@ -29,6 +29,7 @@ import ThumbNavigation from 'soapbox/components/thumb_navigation';
import { Layout } from 'soapbox/components/ui';
import { useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
import AdminPage from 'soapbox/pages/admin_page';
import ChatsPage from 'soapbox/pages/chats-page';
import DefaultPage from 'soapbox/pages/default_page';
// import GroupsPage from 'soapbox/pages/groups_page';
// import GroupPage from 'soapbox/pages/group_page';
@ -265,8 +266,8 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {
{features.suggestions && <WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />}
{features.profileDirectory && <WrappedRoute path='/directory' publicRoute page={DefaultPage} component={Directory} content={children} />}
{features.chats && <WrappedRoute path='/chats' exact page={DefaultPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/:chatId' page={DefaultPage} component={ChatRoom} content={children} />}
{features.chats && <WrappedRoute path='/chats' exact page={ChatsPage} component={ChatIndex} content={children} />}
{features.chats && <WrappedRoute path='/chats/:chatId' page={ChatsPage} component={ChatRoom} content={children} />}
<WrappedRoute path='/follow_requests' page={DefaultPage} component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />

View File

@ -194,7 +194,7 @@
"column.birthdays": "Birthdays",
"column.blocks": "Blocked users",
"column.bookmarks": "Bookmarks",
"column.chats": "Chats",
"column.chats": "Messages",
"column.community": "Local timeline",
"column.crypto_donate": "Donate Cryptocurrency",
"column.developers": "Developers",

View File

@ -0,0 +1,12 @@
import React from 'react';
/** Custom layout for chats on desktop. */
const ChatsPage: React.FC = ({ children }) => {
return (
<div className='md:col-span-12 lg:col-span-9 pb-36'>
{children}
</div>
);
};
export default ChatsPage;

View File

@ -3,8 +3,10 @@ import { useEffect, useState } from 'react';
import { fetchRelationships } from 'soapbox/actions/accounts';
import snackbar from 'soapbox/actions/snackbar';
import compareId from 'soapbox/compare_id';
import { useChatContext } from 'soapbox/contexts/chat-context';
import { useApi, useAppDispatch } from 'soapbox/hooks';
import { normalizeChatMessage } from 'soapbox/normalizers';
import { queryClient } from './client';
@ -40,11 +42,7 @@ export interface IChatMessage {
pending?: boolean
}
const reverseOrder = (a: IChat, b: IChat): number => {
if (Number(a.id) < Number(b.id)) return -1;
if (Number(a.id) > Number(b.id)) return 1;
return 0;
};
const reverseOrder = (a: IChat, b: IChat): number => compareId(a.id, b.id);
const useChatMessages = (chatId: string) => {
const api = useApi();
@ -57,7 +55,7 @@ const useChatMessages = (chatId: string) => {
});
const hasMore = !!headers.link;
const result = data.sort(reverseOrder);
const result = data.sort(reverseOrder).map(normalizeChatMessage);
const nextMaxId = result[0]?.id;
return {