Merge branch 'alex-chats' into chats
This commit is contained in:
commit
56c617bd32
|
@ -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' />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -84,7 +84,7 @@ const ChatPane = () => {
|
|||
<ChatList
|
||||
searchValue={debouncedValue}
|
||||
onClickChat={handleClickChat}
|
||||
useWindowScroll={false}
|
||||
fade
|
||||
/>
|
||||
) : (
|
||||
<Text>no results</Text>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue