@ -13,7 +13,7 @@ import { useChatContext } from '@/contexts/chat-context';
|
||||
import Emojify from '@/features/emoji/emojify';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useRelationshipQuery } from '@/queries/accounts/use-relationship';
|
||||
import { useChatActions } from '@/queries/chats';
|
||||
import { useDeleteChat } from '@/queries/chats';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
|
||||
import type { Menu } from '@/components/dropdown-menu';
|
||||
@ -32,14 +32,14 @@ interface IChatListItemInterface {
|
||||
onClick: (chat: Chat) => void;
|
||||
}
|
||||
|
||||
const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
|
||||
const ChatListItem: React.FC<IChatListItemInterface> = React.memo(({ chat, onClick }) => {
|
||||
const { openModal } = useModalsActions();
|
||||
const intl = useIntl();
|
||||
const features = useFeatures();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { isUsingMainChatPage } = useChatContext();
|
||||
const { deleteChat } = useChatActions(chat?.id as string);
|
||||
const deleteChat = useDeleteChat(chat?.id as string);
|
||||
const { data: relationship } = useRelationshipQuery(chat?.account.id);
|
||||
|
||||
const isBlocked = relationship?.blocked_by && false;
|
||||
@ -159,6 +159,6 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { ChatListItem as default };
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
import PullToRefresh from '@/components/pull-to-refresh';
|
||||
@ -37,6 +37,14 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false })
|
||||
|
||||
const handleRefresh = () => refetch();
|
||||
|
||||
const renderChatListItem = useCallback((_index: number, chat: Chat | 'shoutbox') => {
|
||||
if (chat === 'shoutbox') {
|
||||
return <div key='shoutbox' className='px-2'><ChatListShoutbox onClick={onClickChat} /></div>;
|
||||
}
|
||||
|
||||
return <div key={chat.id} className='px-2'><ChatListItem chat={chat} onClick={onClickChat} /></div>;
|
||||
}, [onClickChat]);
|
||||
|
||||
const renderEmpty = () => {
|
||||
if (isFetching) {
|
||||
return (
|
||||
@ -65,11 +73,7 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false })
|
||||
useWindowScroll={useWindowScroll}
|
||||
data={allChats}
|
||||
endReached={handleLoadMore}
|
||||
itemContent={(_index, chat) => (
|
||||
<div className='px-2'>
|
||||
{chat === 'shoutbox' ? <ChatListShoutbox onClick={onClickChat} /> : <ChatListItem chat={chat} onClick={onClickChat} />}
|
||||
</div>
|
||||
)}
|
||||
itemContent={renderChatListItem}
|
||||
components={{
|
||||
ScrollSeekPlaceholder: PlaceholderChat,
|
||||
Footer: () => hasNextPage ? <Spinner withText={false} /> : null,
|
||||
|
||||
@ -10,7 +10,7 @@ import Stack from '@/components/ui/stack';
|
||||
import Text from '@/components/ui/text';
|
||||
import PlaceholderChatMessage from '@/features/placeholder/components/placeholder-chat-message';
|
||||
import { useRelationshipQuery } from '@/queries/accounts/use-relationship';
|
||||
import { useChatActions, useChatMessages } from '@/queries/chats';
|
||||
import { useChatMessages, useMarkChatAsRead } from '@/queries/chats';
|
||||
|
||||
import ChatMessage from './chat-message';
|
||||
|
||||
@ -67,13 +67,13 @@ interface IChatMessageList {
|
||||
}
|
||||
|
||||
/** Scrollable list of chat messages. */
|
||||
const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
const ChatMessageList: React.FC<IChatMessageList> = React.memo(({ chat }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const node = useRef<VirtuosoHandle>(null);
|
||||
const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20);
|
||||
|
||||
const { markChatAsRead } = useChatActions(chat.id);
|
||||
const markChatAsRead = useMarkChatAsRead(chat.id);
|
||||
const {
|
||||
data: chatMessages,
|
||||
fetchNextPage,
|
||||
@ -140,7 +140,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
const cachedChatMessages = buildCachedMessages();
|
||||
const cachedChatMessages = useMemo(() => buildCachedMessages(), [chatMessages]);
|
||||
|
||||
const initialScrollPositionProps = useMemo(() => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
@ -160,7 +160,13 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
return false;
|
||||
}, [firstItemIndex, hasNextPage, isFetching]);
|
||||
|
||||
const renderDivider = (key: React.Key, text: string) => <Divider key={key} text={text} textSize='xs' />;
|
||||
const renderChatMessage = useCallback((index: number, chatMessage: ChatMessageEntity | { type: 'divider'; text: string }) => {
|
||||
if ('type' in chatMessage && chatMessage.type === 'divider') {
|
||||
return <Divider key={index} text={chatMessage.text} textSize='xs' />;
|
||||
}
|
||||
|
||||
return <ChatMessage key={chatMessage.id} chat={chat} chatMessage={chatMessage} />;
|
||||
}, [chat]);
|
||||
|
||||
useEffect(() => {
|
||||
const lastMessage = formattedChatMessages[formattedChatMessages.length - 1];
|
||||
@ -177,7 +183,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
* 2) it has not already been read
|
||||
*/
|
||||
if (!isMessagePending) {
|
||||
markChatAsRead(lastMessageId);
|
||||
markChatAsRead.mutate(lastMessageId);
|
||||
}
|
||||
}, [formattedChatMessages.length]);
|
||||
|
||||
@ -245,13 +251,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
data={cachedChatMessages}
|
||||
startReached={handleStartReached}
|
||||
followOutput='auto'
|
||||
itemContent={(index, chatMessage) => {
|
||||
if (chatMessage.type === 'divider') {
|
||||
return renderDivider(index, (chatMessage as any).text);
|
||||
} else {
|
||||
return <ChatMessage chat={chat} chatMessage={chatMessage} />;
|
||||
}
|
||||
}}
|
||||
itemContent={(index, chatMessage) => renderChatMessage(index, chatMessage)}
|
||||
components={{
|
||||
List,
|
||||
Scroller,
|
||||
@ -267,6 +267,6 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { ChatMessageList as default, List as ChatMessageListList, Scroller as ChatMessageListScroller };
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import clsx from 'clsx';
|
||||
import escape from 'lodash/escape';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
@ -12,8 +11,7 @@ import Stack from '@/components/ui/stack';
|
||||
import Text from '@/components/ui/text';
|
||||
import { MediaGallery } from '@/features/ui/util/async-components';
|
||||
import { useAppSelector } from '@/hooks/use-app-selector';
|
||||
import { ChatKeys, useChatActions } from '@/queries/chats';
|
||||
import { queryClient } from '@/queries/client';
|
||||
import { useDeleteChatMessage } from '@/queries/chats';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
import { stripHTML } from '@/utils/html';
|
||||
import { onlyEmoji } from '@/utils/rich-content';
|
||||
@ -46,26 +44,17 @@ interface IChatMessage {
|
||||
chatMessage: ChatMessageEntity;
|
||||
}
|
||||
|
||||
const ChatMessage = (props: IChatMessage) => {
|
||||
const ChatMessage: React.FC<IChatMessage> = React.memo((props) => {
|
||||
const { chat, chatMessage } = props;
|
||||
|
||||
const { openModal } = useModalsActions();
|
||||
const intl = useIntl();
|
||||
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const { deleteChatMessage } = useChatActions(chat.id);
|
||||
const deleteChatMessage = useDeleteChatMessage(chat.id);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||
|
||||
const handleDeleteMessage = useMutation({
|
||||
mutationFn: (chatMessageId: string) => deleteChatMessage(chatMessageId),
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ChatKeys.chatMessages(chat.id),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const content = parseContent(chatMessage);
|
||||
const isMyMessage = chatMessage.account_id === me;
|
||||
|
||||
@ -137,14 +126,14 @@ const ChatMessage = (props: IChatMessage) => {
|
||||
if (isMyMessage) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.delete),
|
||||
action: () => handleDeleteMessage.mutate(chatMessage.id),
|
||||
action: () => deleteChatMessage.mutate(chatMessage.id),
|
||||
icon: require('@phosphor-icons/core/regular/trash.svg'),
|
||||
destructive: true,
|
||||
});
|
||||
} else {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.deleteForMe),
|
||||
action: () => handleDeleteMessage.mutate(chatMessage.id),
|
||||
action: () => deleteChatMessage.mutate(chatMessage.id),
|
||||
icon: require('@phosphor-icons/core/regular/trash.svg'),
|
||||
destructive: true,
|
||||
});
|
||||
@ -273,6 +262,6 @@ const ChatMessage = (props: IChatMessage) => {
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { ChatMessage as default };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context';
|
||||
@ -25,13 +25,13 @@ const ChatPane = () => {
|
||||
const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext();
|
||||
const { chatsQuery: { data: chats, isLoading } } = useChats();
|
||||
|
||||
const handleClickChat = (nextChat: Chat | 'shoutbox') => {
|
||||
const handleClickChat = useCallback((nextChat: Chat | 'shoutbox') => {
|
||||
if (nextChat === 'shoutbox') {
|
||||
changeScreen(ChatWidgetScreens.SHOUTBOX);
|
||||
} else {
|
||||
changeScreen(ChatWidgetScreens.CHAT, nextChat.id);
|
||||
}
|
||||
};
|
||||
}, [changeScreen]);
|
||||
|
||||
const renderBody = () => {
|
||||
if (Number(chats?.length) > 0 || showShoutbox || isLoading) {
|
||||
|
||||
@ -10,7 +10,7 @@ import Text from '@/components/ui/text';
|
||||
import { ChatWidgetScreens, useChatContext } from '@/contexts/chat-context';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useUnblockAccountMutation, useRelationshipQuery } from '@/queries/accounts/use-relationship';
|
||||
import { useChatActions } from '@/queries/chats';
|
||||
import { useDeleteChat } from '@/queries/chats';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
|
||||
import ChatPaneHeader from './chat-pane-header';
|
||||
@ -30,7 +30,7 @@ const ChatSettings = () => {
|
||||
|
||||
const { openModal } = useModalsActions();
|
||||
const { chat, changeScreen, toggleChatPane } = useChatContext();
|
||||
const { deleteChat } = useChatActions(chat?.id as string);
|
||||
const deleteChat = useDeleteChat(chat?.id as string);
|
||||
|
||||
const { mutate: unblockAccount } = useUnblockAccountMutation(chat?.account.id!);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
import { uploadMedia } from '@/actions/media';
|
||||
import Stack from '@/components/ui/stack';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useChatActions } from '@/queries/chats';
|
||||
import { useCreateChatMessage } from '@/queries/chats';
|
||||
import toast from '@/toast';
|
||||
|
||||
import ChatComposer from './chat-composer';
|
||||
@ -51,7 +51,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { createChatMessage } = useChatActions(chat.id);
|
||||
const createChatMessage = useCreateChatMessage(chat.id);
|
||||
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [attachment, setAttachment] = useState<MediaAttachment | null>(null);
|
||||
@ -99,8 +99,6 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
const insertLine = () => setContent(content + '\n');
|
||||
|
||||
const handleKeyDown: React.KeyboardEventHandler = (event) => {
|
||||
markRead();
|
||||
|
||||
if (event.key === 'Enter' && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
insertLine();
|
||||
@ -120,13 +118,6 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const markRead = () => {
|
||||
// markAsRead.mutate();
|
||||
// dispatch(markChatRead(chatId));
|
||||
};
|
||||
|
||||
const handleMouseOver = () => markRead();
|
||||
|
||||
const handleRemoveFile = () => {
|
||||
setAttachment(null);
|
||||
setResetFileKey(fileKeyGen());
|
||||
@ -159,7 +150,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
}, [chat.id, inputRef?.current]);
|
||||
|
||||
return (
|
||||
<Stack className={clsx('flex grow overflow-hidden', className)} onMouseOver={handleMouseOver}>
|
||||
<Stack className={clsx('flex grow overflow-hidden', className)}>
|
||||
<div className='flex h-full grow justify-center overflow-hidden'>
|
||||
<ChatMessageList key={chat.id} chat={chat} />
|
||||
</div>
|
||||
|
||||
@ -13,7 +13,7 @@ import VerificationBadge from '@/components/verification-badge';
|
||||
import { chatRoute } from '@/features/ui/router';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useUnblockAccountMutation, useRelationshipQuery } from '@/queries/accounts/use-relationship';
|
||||
import { useChat, useChatActions } from '@/queries/chats';
|
||||
import { useChat, useDeleteChat } from '@/queries/chats';
|
||||
import { useModalsActions } from '@/stores/modals';
|
||||
|
||||
import Chat from '../../chat';
|
||||
@ -47,7 +47,7 @@ const ChatsPageChat = () => {
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const { deleteChat } = useChatActions(chat?.id as string);
|
||||
const deleteChat = useDeleteChat(chat?.id as string);
|
||||
|
||||
const isBlocked = !!useRelationshipQuery(chat?.account.id).data?.blocked_by;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { CardTitle } from '@/components/ui/card';
|
||||
@ -19,13 +19,13 @@ const ChatsPageSidebar = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleClickChat = (chat: Chat | 'shoutbox') => {
|
||||
const handleClickChat = useCallback((chat: Chat | 'shoutbox') => {
|
||||
if (chat === 'shoutbox') {
|
||||
navigate({ to: '/chats/shoutbox' });
|
||||
} else {
|
||||
navigate({ to: '/chats/$chatId', params: { chatId: chat.id } });
|
||||
}
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
const handleChatCreate = () => {
|
||||
navigate({ to: '/chats/new' });
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import sumBy from 'lodash/sumBy';
|
||||
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema } from 'pl-api';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { importEntities } from '@/actions/importer';
|
||||
@ -121,17 +120,14 @@ const useChat = (chatId?: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const useChatActions = (chatId: string) => {
|
||||
const { account } = useOwnAccount();
|
||||
const useMarkChatAsRead = (chatId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
const { setUnreadChatsCount } = useStatContext();
|
||||
|
||||
const { chat, changeScreen } = useChatContext();
|
||||
|
||||
const markChatAsRead = async (lastReadId: string) =>
|
||||
client.chats.markChatAsRead(chatId, lastReadId)
|
||||
.then((data) => {
|
||||
return useMutation({
|
||||
mutationFn: (lastReadId: string) => client.chats.markChatAsRead(chatId, lastReadId),
|
||||
onSuccess: (data) => {
|
||||
updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id);
|
||||
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResponse<unknown>>>(['chats', 'search']);
|
||||
|
||||
@ -145,12 +141,17 @@ const useChatActions = (chatId: string) => {
|
||||
});
|
||||
setUnreadChatsCount(sumBy(flattenedQueryData, (chat: Chat) => chat.unread));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(() => null);
|
||||
const useCreateChatMessage = (chatId: string) => {
|
||||
const { account } = useOwnAccount();
|
||||
const client = useClient();
|
||||
|
||||
const createChatMessage = useMutation({
|
||||
const { chat } = useChatContext();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ chatId, content, mediaId }: { chatId: string; content: string; mediaId?: string }) =>
|
||||
client.chats.createChatMessage(chatId, { content, media_id: mediaId }),
|
||||
retry: false,
|
||||
@ -212,9 +213,14 @@ const useChatActions = (chatId: string) => {
|
||||
reOrderChatListItems();
|
||||
},
|
||||
});
|
||||
const deleteChatMessage = useCallback((chatMessageId: string) => client.chats.deleteChatMessage(chatId, chatMessageId), [chatId]);
|
||||
};
|
||||
|
||||
const deleteChat = useMutation({
|
||||
const useDeleteChat = (chatId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
const { changeScreen } = useChatContext();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => client.chats.deleteChat(chatId),
|
||||
onSuccess() {
|
||||
changeScreen(ChatWidgetScreens.INBOX);
|
||||
@ -222,13 +228,19 @@ const useChatActions = (chatId: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['chats', 'search'] });
|
||||
},
|
||||
});
|
||||
|
||||
return useMemo(() => ({
|
||||
createChatMessage,
|
||||
deleteChat,
|
||||
deleteChatMessage,
|
||||
markChatAsRead,
|
||||
}), [createChatMessage, deleteChat, deleteChatMessage, markChatAsRead]);
|
||||
};
|
||||
|
||||
export { ChatKeys, useChat, useChatActions, useChats, useChatMessages };
|
||||
const useDeleteChatMessage = (chatId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (chatMessageId: string) => client.chats.deleteChatMessage(chatId, chatMessageId),
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ChatKeys.chatMessages(chatId),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { ChatKeys, useChat, useMarkChatAsRead, useCreateChatMessage, useDeleteChat, useDeleteChatMessage, useChats, useChatMessages };
|
||||
|
||||
@ -208,6 +208,7 @@
|
||||
@apply py-1.5 pr-4 rtl:pl-4 rtl:pr-0;
|
||||
|
||||
> :first-child {
|
||||
word-break: break-word;
|
||||
@apply text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user