diff --git a/app/soapbox/features/chats/components/chat-message-list.tsx b/app/soapbox/features/chats/components/chat-message-list.tsx index c3bd7e69c..50026c4d1 100644 --- a/app/soapbox/features/chats/components/chat-message-list.tsx +++ b/app/soapbox/features/chats/components/chat-message-list.tsx @@ -1,3 +1,4 @@ +import { useMutation } from '@tanstack/react-query'; import classNames from 'clsx'; import { Map as ImmutableMap, @@ -16,10 +17,12 @@ import { initReportById } from 'soapbox/actions/reports'; import { Avatar, HStack, IconButton, Spinner, Stack, Text } from 'soapbox/components/ui'; import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container'; import emojify from 'soapbox/features/emoji/emoji'; +import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery } from 'soapbox/features/ui/util/async-components'; import { useAppSelector, useAppDispatch, useRefEventHandler, useOwnAccount } from 'soapbox/hooks'; -import { IChat, IChatMessage, useChatMessages } from 'soapbox/queries/chats'; +import { IChat, IChatMessage, useChat, useChatMessages } from 'soapbox/queries/chats'; +import { queryClient } from 'soapbox/queries/client'; import { onlyEmoji } from 'soapbox/utils/rich_content'; import type { Menu } from 'soapbox/components/dropdown_menu'; @@ -80,10 +83,10 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut const [initialLoad, setInitialLoad] = useState(true); const [scrollPosition, setScrollPosition] = useState(0); - const { data: chatMessages, isFetching, isFetched, fetchNextPage, isFetchingNextPage, isPlaceholderData } = useChatMessages(chat.id); + const { deleteChatMessage } = useChat(chat.id); + const { data: chatMessages, isLoading, isFetching, isFetched, fetchNextPage, isFetchingNextPage, isPlaceholderData } = useChatMessages(chat.id); const formattedChatMessages = chatMessages || []; - const me = useAppSelector(state => state.me); @@ -94,6 +97,12 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut const initialCount = useMemo(() => formattedChatMessages.length, []); + const handleDeleteMessage = useMutation((chatMessageId: string) => deleteChatMessage(chatMessageId), { + onSettled: () => { + queryClient.invalidateQueries(['chats', 'messages', chat.id]); + }, + }); + const scrollToBottom = () => { messagesEnd.current?.scrollIntoView(false); }; @@ -224,12 +233,6 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut ); - const handleDeleteMessage = (chatId: string, messageId: string) => { - return () => { - dispatch(deleteChatMessage(chatId, messageId)); - }; - }; - const handleReportUser = (userId: string) => { return () => { dispatch(initReportById(userId)); @@ -242,7 +245,7 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut const menu: Menu = [ { text: intl.formatMessage(messages.delete), - action: handleDeleteMessage(chatMessage.chat_id, chatMessage.id), + action: () => handleDeleteMessage.mutate(chatMessage.id), icon: require('@tabler/icons/trash.svg'), destructive: true, }, @@ -257,88 +260,88 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut } return ( - - - {isMyMessage ? ( - // -
- -
- - ) : null} - -
- {maybeRenderMedia(chatMessage)} - -
- -
-
- -
- -
-
- - + - - {intl.formatTime(chatMessage.created_at)} - + {isMyMessage ? ( +
+ +
+ ) : null} -
-
-
- - + +
+ {maybeRenderMedia(chatMessage)} + +
+ +
+
+ +
+ +
+
+ + + + + {intl.formatTime(chatMessage.created_at)} + + +
+
+
+ + +
); }; @@ -358,18 +361,18 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut }); // Stick scrollbar to bottom. - // useEffect(() => { - // if (isNearBottom()) { - // scrollToBottom(); - // } + useEffect(() => { + if (isNearBottom()) { + scrollToBottom(); + } - // // First load. - // // if (chatMessages.count() !== initialCount) { - // // setInitialLoad(false); - // // setIsLoading(false); - // // scrollToBottom(); - // // } - // }, [formattedChatMessages.length]); + // First load. + // if (chatMessages.count() !== initialCount) { + // setInitialLoad(false); + // setIsLoading(false); + // scrollToBottom(); + // } + }, [formattedChatMessages.length]); // useEffect(() => { // scrollToBottom(); @@ -381,9 +384,7 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut useEffect(() => { // Restore scroll bar position when loading old messages. - console.log('hii'); if (!initialLoad) { - restoreScrollPosition(); } }, [formattedChatMessages.length, initialLoad]); @@ -398,26 +399,39 @@ const ChatMessageList: React.FC = ({ chat, chatMessageIds, aut } return ( -
{/* style={{ height: autosize ? 'calc(100vh - 16rem)' : undefined }} */} - {formattedChatMessages.reduce((acc: any, curr: any, idx: number) => { - const lastMessage = formattedChatMessages[idx - 1]; +
{/* style={{ height: autosize ? 'calc(100vh - 16rem)' : undefined }} */} +
+ {isLoading ? ( + <> + + + + + + + ) : ( + formattedChatMessages.reduce((acc: any, curr: any, idx: number) => { + const lastMessage = formattedChatMessages[idx - 1]; - if (lastMessage) { - const key = `${curr.id}_divider`; - switch (timeChange(lastMessage, curr)) { - case 'today': - acc.push(renderDivider(key, intl.formatMessage(messages.today))); - break; - case 'date': - acc.push(renderDivider(key, intl.formatDate(new Date(curr.created_at), { weekday: 'short', hour: 'numeric', minute: '2-digit', month: 'short', day: 'numeric' }))); - break; - } - } + if (lastMessage) { + const key = `${curr.id}_divider`; + switch (timeChange(lastMessage, curr)) { + case 'today': + acc.push(renderDivider(key, intl.formatMessage(messages.today))); + break; + case 'date': + acc.push(renderDivider(key, intl.formatDate(new Date(curr.created_at), { weekday: 'short', hour: 'numeric', minute: '2-digit', month: 'short', day: 'numeric' }))); + break; + } + } - acc.push(renderMessage(curr)); - return acc; - }, [] as React.ReactNode[])} -
+ acc.push(renderMessage(curr)); + return acc; + }, [] as React.ReactNode[]) + )} +
+ +
); }; diff --git a/app/soapbox/features/chats/components/chat-window.tsx b/app/soapbox/features/chats/components/chat-window.tsx index 188fd5acb..90db21f08 100644 --- a/app/soapbox/features/chats/components/chat-window.tsx +++ b/app/soapbox/features/chats/components/chat-window.tsx @@ -1,27 +1,11 @@ -import React, { useEffect, useRef } from 'react'; -import { Link } from 'react-router-dom'; +import React from 'react'; -import { - closeChat, - toggleChat, -} from 'soapbox/actions/chats'; -import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; -import IconButton from 'soapbox/components/icon_button'; -import { Avatar, HStack, Counter, Icon, Stack, Text } from 'soapbox/components/ui'; +import { Avatar, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification_badge'; -import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { IChat } from 'soapbox/queries/chats'; -import { makeGetChat } from 'soapbox/selectors'; -import { getAcct } from 'soapbox/utils/accounts'; -import { displayFqn as getDisplayFqn } from 'soapbox/utils/state'; import ChatBox from './chat-box'; import ChatPaneHeader from './chat-pane-header'; -import { Pane, WindowState } from './ui'; - -import type { Account as AccountEntity } from 'soapbox/types/entities'; - -const getChat = makeGetChat(); interface IChatWindow { chat: IChat @@ -67,23 +51,6 @@ const ChatWindow: React.FC = ({ chat, closeChat, closePane }) => { null} /> - // - // - // {unreadCount > 0 ? unreadIcon : avatar } - // - //
- // - //
- //
- //
- // - //
- //
); }; diff --git a/app/soapbox/features/placeholder/components/placeholder_chat.tsx b/app/soapbox/features/placeholder/components/placeholder_chat.tsx index 6b587f03d..1cc9299fe 100644 --- a/app/soapbox/features/placeholder/components/placeholder_chat.tsx +++ b/app/soapbox/features/placeholder/components/placeholder_chat.tsx @@ -1,30 +1,69 @@ +import classNames from 'classnames'; import React from 'react'; +import { HStack, Stack, Text } from 'soapbox/components/ui'; + import { randomIntFromInterval, generateText } from '../utils'; import PlaceholderAvatar from './placeholder_avatar'; import PlaceholderDisplayName from './placeholder_display_name'; /** Fake chat to display while data is loading. */ -const PlaceholderChat: React.FC = () => { - const messageLength = randomIntFromInterval(5, 75); +const PlaceholderChat = ({ isMyMessage = false }: { isMyMessage?: boolean }) => { + const messageLength = randomIntFromInterval(160, 220); return ( -
-
-
-
-
- -
- - - {generateText(messageLength)} - -
+ + +
+
-
-
+ +
+ +
+ + + + +
+ + +
+
+
+ + ); }; diff --git a/app/soapbox/queries/chats.ts b/app/soapbox/queries/chats.ts index 8732ebc9b..bef239a82 100644 --- a/app/soapbox/queries/chats.ts +++ b/app/soapbox/queries/chats.ts @@ -32,7 +32,7 @@ const reverseOrder = (a: IChat, b: IChat): number => { const useChatMessages = (chatId: string) => { const api = useApi(); - const getChatMessages = async (chatId: string, pageParam?: any): Promise<{ result: IChatMessage[], maxId: string, hasMore: boolean }> => { + const getChatMessages = async(chatId: string, pageParam?: any): Promise<{ result: IChatMessage[], maxId: string, hasMore: boolean }> => { const { data, headers } = await api.get(`/api/v1/pleroma/chats/${chatId}/messages`, { params: { max_id: pageParam?.maxId, @@ -74,7 +74,7 @@ const useChatMessages = (chatId: string) => { const useChats = () => { const api = useApi(); - const getChats = async () => { + const getChats = async() => { const { data } = await api.get('/api/v1/pleroma/chats'); return data; }; @@ -97,7 +97,9 @@ const useChat = (chatId: string) => { return api.post(`/api/v1/pleroma/chats/${chatId}/messages`, { content }); }; - return { createChatMessage, markChatAsRead }; + const deleteChatMessage = (chatMessageId: string) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`); + + return { createChatMessage, markChatAsRead, deleteChatMessage }; }; export { useChat, useChats, useChatMessages };