diff --git a/web/components/message-result.css b/web/components/message-result.css index 2032beeb7..c655a60ab 100644 --- a/web/components/message-result.css +++ b/web/components/message-result.css @@ -1,28 +1,28 @@ .messageContainer { overflow-y: scroll; border: 1px solid var(--pin-message-modal-border-color); border-radius: 7px; max-height: 400px; - margin: 16px 32px; + margin: 0 32px 16px 32px; } .messageDate { color: var(--chat-timestamp-color); font-size: var(--xs-font-12); padding: 0px 0px 6px 0px; line-height: var(--line-height-text); text-align: left; margin-left: 16px; } .creator { font-size: small; color: var(--shades-white-60); font-size: var(--s-font-14); padding: 4px 24px; text-align: left; } .messageContent { margin-bottom: 1px; } diff --git a/web/modals/chat/message-results-modal.css b/web/modals/chat/message-results-modal.css index 060bee9b9..c7cb8b9b6 100644 --- a/web/modals/chat/message-results-modal.css +++ b/web/modals/chat/message-results-modal.css @@ -1,17 +1,24 @@ hr.separator { border: 0; - margin: 20px 0; + margin: 20px 0 0 0; width: 100%; height: 2px; border: none; border-top: var(--shades-black-70) solid 1px; } .messageResultsContainer { overflow-y: scroll; + padding-bottom: 8px; } .loadingIndicator { text-align: center; - margin-bottom: 10px; +} + +.topSpace { + height: 48px; + align-items: center; + justify-content: center; + display: flex; } diff --git a/web/modals/chat/message-results-modal.react.js b/web/modals/chat/message-results-modal.react.js index fcdd4e764..07d1b1621 100644 --- a/web/modals/chat/message-results-modal.react.js +++ b/web/modals/chat/message-results-modal.react.js @@ -1,163 +1,169 @@ // @flow import * as React from 'react'; import { fetchPinnedMessages, fetchPinnedMessageActionTypes, } from 'lib/actions/message-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { messageListData } from 'lib/selectors/chat-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { createMessageInfo } from 'lib/shared/message-utils.js'; import { type ThreadInfo } from 'lib/types/thread-types.js'; import { useServerCall, useDispatchActionPromise, } from 'lib/utils/action-utils.js'; import css from './message-results-modal.css'; import MessageResult from '../../components/message-result.react.js'; import LoadingIndicator from '../../loading-indicator.react.js'; import { useSelector } from '../../redux/redux-utils.js'; import Modal from '../modal.react.js'; type MessageResultsModalProps = { +threadInfo: ThreadInfo, +modalName: string, }; const loadingStatusSelector = createLoadingStatusSelector( fetchPinnedMessageActionTypes, ); function MessageResultsModal(props: MessageResultsModalProps): React.Node { const { threadInfo, modalName } = props; const { id: threadID } = threadInfo; const { popModal } = useModalContext(); const [rawMessageResults, setRawMessageResults] = React.useState([]); const callFetchPinnedMessages = useServerCall(fetchPinnedMessages); const dispatchActionPromise = useDispatchActionPromise(); const userInfos = useSelector(state => state.userStore.userInfos); const loadingStatus = useSelector(loadingStatusSelector); React.useEffect(() => { dispatchActionPromise( fetchPinnedMessageActionTypes, (async () => { const result = await callFetchPinnedMessages({ threadID }); setRawMessageResults(result.pinnedMessages); })(), ); }, [dispatchActionPromise, callFetchPinnedMessages, threadID]); const translatedMessageResults = React.useMemo(() => { const threadInfos = { [threadID]: threadInfo }; return rawMessageResults .map(messageInfo => createMessageInfo(messageInfo, null, userInfos, threadInfos), ) .filter(Boolean); }, [rawMessageResults, userInfos, threadID, threadInfo]); const chatMessageInfos = useSelector( messageListData(threadInfo.id, translatedMessageResults), ); const sortedUniqueChatMessageInfoItems = React.useMemo(() => { if (!chatMessageInfos) { return []; } const chatMessageInfoItems = chatMessageInfos.filter( item => item.itemType === 'message' && item.isPinned, ); // By the nature of using messageListData and passing in // the desired translatedMessageResults as additional // messages, we will have duplicate ChatMessageInfoItems. const uniqueChatMessageInfoItemsMap = new Map(); chatMessageInfoItems.forEach( item => item.messageInfo && item.messageInfo.id && uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item), ); // Push the items in the order they appear in the rawMessageResults // since the messages fetched from the server are already sorted // in the order of pin_time (newest first). const sortedChatMessageInfoItems = []; for (let i = 0; i < rawMessageResults.length; i++) { sortedChatMessageInfoItems.push( uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id), ); } return sortedChatMessageInfoItems; }, [chatMessageInfos, rawMessageResults]); const modifiedItems = React.useMemo( () => sortedUniqueChatMessageInfoItems .map(item => { if (!item) { return null; } // We need to modify the item to make sure that the message does // not render with the date header and that the creator // is not considered the viewer. let modifiedItem = item; if (item.messageInfoType === 'composable') { modifiedItem = { ...item, startsConversation: false, startsCluster: true, endsCluster: true, messageInfo: { ...item.messageInfo, creator: { ...item.messageInfo.creator, isViewer: false, }, }, }; } return modifiedItem; }) .filter(Boolean), [sortedUniqueChatMessageInfoItems], ); const messageResultsToDisplay = React.useMemo(() => { + const items = modifiedItems.map(item => ( + + )); + return <>{items}; + }, [modifiedItems, threadInfo]); + + const loadingIndicator = React.useMemo(() => { if (loadingStatus === 'loading') { return (
); } - return modifiedItems.map(item => ( - - )); - }, [modifiedItems, threadInfo, loadingStatus]); + return null; + }, [loadingStatus]); return (
+
{loadingIndicator}
{messageResultsToDisplay}
); } export default MessageResultsModal;