Merge branch 'alex-chats' into chats
This commit is contained in:
commit
56c617bd32
|
@ -113,9 +113,9 @@ const SidebarNavigation = () => {
|
||||||
return (
|
return (
|
||||||
<SidebarNavigationLink
|
<SidebarNavigationLink
|
||||||
to='/chats'
|
to='/chats'
|
||||||
icon={require('@tabler/icons/messages.svg')}
|
icon={require('@tabler/icons/mail.svg')}
|
||||||
count={chatsCount}
|
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) {
|
if (features.chats) {
|
||||||
return (
|
return (
|
||||||
<ThumbNavigationLink
|
<ThumbNavigationLink
|
||||||
src={require('@tabler/icons/messages.svg')}
|
src={require('@tabler/icons/mail.svg')}
|
||||||
text={<FormattedMessage id='navigation.chats' defaultMessage='Chats' />}
|
text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />}
|
||||||
to='/chats'
|
to='/chats'
|
||||||
exact
|
exact
|
||||||
count={chatsCount}
|
count={chatsCount}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from 'clsx';
|
import classNames from 'clsx';
|
||||||
import React from 'react';
|
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 = {
|
const spaces = {
|
||||||
0: 'space-y-0',
|
0: 'space-y-0',
|
||||||
|
@ -12,6 +12,7 @@ const spaces = {
|
||||||
3: 'space-y-3',
|
3: 'space-y-3',
|
||||||
4: 'space-y-4',
|
4: 'space-y-4',
|
||||||
5: 'space-y-5',
|
5: 'space-y-5',
|
||||||
|
6: 'space-y-6',
|
||||||
10: 'space-y-10',
|
10: 'space-y-10',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import classNames from 'clsx';
|
||||||
import React, { MutableRefObject, useState } from 'react';
|
import React, { MutableRefObject, useState } from 'react';
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
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 { HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||||
import UploadProgress from 'soapbox/components/upload-progress';
|
import UploadProgress from 'soapbox/components/upload-progress';
|
||||||
import UploadButton from 'soapbox/features/compose/components/upload_button';
|
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 { IChat, useChat } from 'soapbox/queries/chats';
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
import { truncateFilename } from 'soapbox/utils/media';
|
import { truncateFilename } from 'soapbox/utils/media';
|
||||||
|
@ -23,14 +24,15 @@ const fileKeyGen = (): number => Math.floor((Math.random() * 0x10000));
|
||||||
interface IChatBox {
|
interface IChatBox {
|
||||||
chat: IChat,
|
chat: IChat,
|
||||||
autosize?: boolean,
|
autosize?: boolean,
|
||||||
inputRef?: MutableRefObject<HTMLTextAreaElement>
|
inputRef?: MutableRefObject<HTMLTextAreaElement>,
|
||||||
|
className?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat UI with just the messages and textarea.
|
* Chat UI with just the messages and textarea.
|
||||||
* Reused between floating desktop chats and fullscreen/mobile chats.
|
* 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 intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -48,7 +50,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
|
||||||
|
|
||||||
const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), {
|
const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), {
|
||||||
retry: false,
|
retry: false,
|
||||||
onMutate: async(newMessage: any) => {
|
onMutate: async (newMessage: any) => {
|
||||||
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
||||||
await queryClient.cancelQueries(['chats', 'messages', chat.id]);
|
await queryClient.cancelQueries(['chats', 'messages', chat.id]);
|
||||||
|
|
||||||
|
@ -206,7 +208,7 @@ const ChatBox: React.FC<IChatBox> = ({ chat, autosize, inputRef }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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'>
|
<div className='flex-grow h-full overflow-hidden flex justify-center'>
|
||||||
<ChatMessageList chat={chat} autosize />
|
<ChatMessageList chat={chat} autosize />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,10 +15,11 @@ import Blankslate from './chat-pane/blankslate';
|
||||||
interface IChatList {
|
interface IChatList {
|
||||||
onClickChat: (chat: any) => void,
|
onClickChat: (chat: any) => void,
|
||||||
useWindowScroll?: boolean,
|
useWindowScroll?: boolean,
|
||||||
|
fade?: boolean,
|
||||||
searchValue?: string
|
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 dispatch = useDispatch();
|
||||||
|
|
||||||
const chatListRef = useRef(null);
|
const chatListRef = useRef(null);
|
||||||
|
@ -75,18 +76,22 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false, s
|
||||||
)}
|
)}
|
||||||
</PullToRefresh>
|
</PullToRefresh>
|
||||||
|
|
||||||
<div
|
{fade && (
|
||||||
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,
|
<div
|
||||||
'opacity-100': !isNearTop,
|
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,
|
<div
|
||||||
'opacity-100': !isNearBottom,
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -283,7 +283,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat, autosize }) => {
|
||||||
title={getFormattedTimestamp(chatMessage)}
|
title={getFormattedTimestamp(chatMessage)}
|
||||||
className={
|
className={
|
||||||
classNames({
|
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-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,
|
'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
|
<ChatList
|
||||||
searchValue={debouncedValue}
|
searchValue={debouncedValue}
|
||||||
onClickChat={handleClickChat}
|
onClickChat={handleClickChat}
|
||||||
useWindowScroll={false}
|
fade
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text>no results</Text>
|
<Text>no results</Text>
|
||||||
|
|
|
@ -21,12 +21,12 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
|
||||||
data-testid='chat'
|
data-testid='chat'
|
||||||
>
|
>
|
||||||
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
|
<HStack alignItems='center' justifyContent='between' space={2} className='w-full'>
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2} className='overflow-hidden'>
|
||||||
<Avatar src={chat.account?.avatar} size={40} />
|
<Avatar src={chat.account?.avatar} size={40} className='flex-none' />
|
||||||
|
|
||||||
<Stack alignItems='start'>
|
<Stack alignItems='start' className='overflow-hidden'>
|
||||||
<div className='flex items-center space-x-1 flex-grow'>
|
<div className='flex items-center space-x-1 flex-grow w-full'>
|
||||||
<Text weight='bold' size='sm' truncate>{chat.account?.display_name || `@${chat.account.username}`}</Text>
|
<Text weight='bold' size='sm' align='left' truncate>{chat.account?.display_name || `@${chat.account.username}`}</Text>
|
||||||
{chat.account?.verified && <VerificationBadge />}
|
{chat.account?.verified && <VerificationBadge />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ const Chat: React.FC<IChatInterface> = ({ chat, onClick }) => {
|
||||||
weight='medium'
|
weight='medium'
|
||||||
theme='muted'
|
theme='muted'
|
||||||
truncate
|
truncate
|
||||||
className='max-w-[200px]'
|
className='w-full'
|
||||||
data-testid='chat-last-message'
|
data-testid='chat-last-message'
|
||||||
dangerouslySetInnerHTML={{ __html: chat.last_message?.content }}
|
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>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { launchChat } from 'soapbox/actions/chats';
|
import { launchChat } from 'soapbox/actions/chats';
|
||||||
import AccountSearch from 'soapbox/components/account_search';
|
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';
|
import ChatList from './components/chat-list';
|
||||||
|
|
||||||
const messages = defineMessages({
|
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…' },
|
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,30 +22,44 @@ const ChatIndex: React.FC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
const [chat, setChat] = useState<any>(null);
|
||||||
|
|
||||||
const handleSuggestion = (accountId: string) => {
|
const handleSuggestion = (accountId: string) => {
|
||||||
dispatch(launchChat(accountId, history, true));
|
dispatch(launchChat(accountId, history, true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickChat = (chat: { id: string }) => {
|
const handleClickChat = (chat: any) => {
|
||||||
history.push(`/chats/${chat.id}`);
|
// history.push(`/chats/${chat.id}`);
|
||||||
|
setChat(chat);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.title)}>
|
<Card className='p-0 h-[calc(100vh-176px)] overflow-hidden' variant='rounded'>
|
||||||
<div className='column__switch'>
|
<div className='grid grid-cols-9 overflow-hidden h-full'>
|
||||||
<AudioToggle />
|
<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>
|
</div>
|
||||||
|
</Card>
|
||||||
<AccountSearch
|
|
||||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
|
||||||
onSelected={handleSuggestion}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ChatList
|
|
||||||
onClickChat={handleClickChat}
|
|
||||||
useWindowScroll
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ThumbNavigation from 'soapbox/components/thumb_navigation';
|
||||||
import { Layout } from 'soapbox/components/ui';
|
import { Layout } from 'soapbox/components/ui';
|
||||||
import { useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
|
import { useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
|
||||||
import AdminPage from 'soapbox/pages/admin_page';
|
import AdminPage from 'soapbox/pages/admin_page';
|
||||||
|
import ChatsPage from 'soapbox/pages/chats-page';
|
||||||
import DefaultPage from 'soapbox/pages/default_page';
|
import DefaultPage from 'soapbox/pages/default_page';
|
||||||
// import GroupsPage from 'soapbox/pages/groups_page';
|
// import GroupsPage from 'soapbox/pages/groups_page';
|
||||||
// import GroupPage from 'soapbox/pages/group_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.suggestions && <WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />}
|
||||||
{features.profileDirectory && <WrappedRoute path='/directory' publicRoute page={DefaultPage} component={Directory} 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' exact page={ChatsPage} component={ChatIndex} content={children} />}
|
||||||
{features.chats && <WrappedRoute path='/chats/:chatId' page={DefaultPage} component={ChatRoom} 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='/follow_requests' page={DefaultPage} component={FollowRequests} content={children} />
|
||||||
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />
|
<WrappedRoute path='/blocks' page={DefaultPage} component={Blocks} content={children} />
|
||||||
|
|
|
@ -194,7 +194,7 @@
|
||||||
"column.birthdays": "Birthdays",
|
"column.birthdays": "Birthdays",
|
||||||
"column.blocks": "Blocked users",
|
"column.blocks": "Blocked users",
|
||||||
"column.bookmarks": "Bookmarks",
|
"column.bookmarks": "Bookmarks",
|
||||||
"column.chats": "Chats",
|
"column.chats": "Messages",
|
||||||
"column.community": "Local timeline",
|
"column.community": "Local timeline",
|
||||||
"column.crypto_donate": "Donate Cryptocurrency",
|
"column.crypto_donate": "Donate Cryptocurrency",
|
||||||
"column.developers": "Developers",
|
"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 { fetchRelationships } from 'soapbox/actions/accounts';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import compareId from 'soapbox/compare_id';
|
||||||
import { useChatContext } from 'soapbox/contexts/chat-context';
|
import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||||
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
||||||
|
import { normalizeChatMessage } from 'soapbox/normalizers';
|
||||||
|
|
||||||
import { queryClient } from './client';
|
import { queryClient } from './client';
|
||||||
|
|
||||||
|
@ -40,11 +42,7 @@ export interface IChatMessage {
|
||||||
pending?: boolean
|
pending?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const reverseOrder = (a: IChat, b: IChat): number => {
|
const reverseOrder = (a: IChat, b: IChat): number => compareId(a.id, b.id);
|
||||||
if (Number(a.id) < Number(b.id)) return -1;
|
|
||||||
if (Number(a.id) > Number(b.id)) return 1;
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useChatMessages = (chatId: string) => {
|
const useChatMessages = (chatId: string) => {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
@ -57,7 +55,7 @@ const useChatMessages = (chatId: string) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasMore = !!headers.link;
|
const hasMore = !!headers.link;
|
||||||
const result = data.sort(reverseOrder);
|
const result = data.sort(reverseOrder).map(normalizeChatMessage);
|
||||||
const nextMaxId = result[0]?.id;
|
const nextMaxId = result[0]?.id;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue