import { Box } from '@rocket.chat/fuselage'; import { isTruthy } from '@rocket.chat/tools'; import { CustomVirtuaScrollbars, useEmbeddedLayout } from '@rocket.chat/ui-client'; import { usePermission, useRole, useSetting, useTranslation, useUser, useUserPreference, useRoomToolbox } from '@rocket.chat/ui-contexts'; import type { MouseEvent } from 'react'; import { memo, useCallback, useMemo, useState } from 'react'; import { useMergedRefsV2 } from '../../../hooks/useMergedRefsV2'; import { BubbleDate } from '../BubbleDate'; import { MessageList } from '../MessageList'; import DropTargetOverlay from './DropTargetOverlay'; import JumpToRecentMessageButton from './JumpToRecentMessageButton'; import UnreadMessagesIndicator from './UnreadMessagesIndicator'; import MessageListErrorBoundary from '../MessageList/MessageListErrorBoundary'; import RoomAnnouncement from '../RoomAnnouncement'; import UploadProgressIndicator from './UploadProgress'; import ComposerContainer from '../composer/ComposerContainer'; import { useFileUpload } from './hooks/useFileUpload'; import { useGoToHomeOnRemoved } from './hooks/useGoToHomeOnRemoved'; import { useIsAtBottomRef } from './hooks/useIsAtBottomRef'; import { useQuoteMessageByUrl } from './hooks/useQuoteMessageByUrl'; import { useReadMessageWindowEvents } from './hooks/useReadMessageWindowEvents'; import RoomComposer from '../composer/RoomComposer/RoomComposer'; import { useChat } from '../contexts/ChatContext'; import { useRoom, useRoomSubscription, useRoomMessages } from '../contexts/RoomContext'; import { useDateScroll } from '../hooks/useDateScroll'; import { useMessageListNavigation } from '../hooks/useMessageListNavigation'; import { useRetentionPolicy } from '../hooks/useRetentionPolicy'; import { useFileUploadDropTarget } from './hooks/useFileUploadDropTarget'; import { useGetMore } from './hooks/useGetMore'; import { useHasNewMessages } from './hooks/useHasNewMessages'; import { useSelectAllAndScrollToTop } from './hooks/useSelectAllAndScrollToTop'; import { useHandleUnread } from './hooks/useUnreadMessages'; import { useKeepAtBottom } from '../MessageList/hooks/useKeepAtBottom'; import useTryToJumpToThreadMessage from '../MessageList/hooks/useTryToJumpToThreadMessage'; const RoomBody = () => { const chat = useChat(); if (!chat) { throw new Error('No ChatContext provided'); } const t = useTranslation(); const isLayoutEmbedded = useEmbeddedLayout(); const room = useRoom(); const user = useUser(); const toolbox = useRoomToolbox(); const admin = useRole('admin'); const subscription = useRoomSubscription(); const [shouldJumpToBottom, setShouldJumpToBottom] = useState(false); const isAtBottom = useIsAtBottomRef(room._id); const [isJumpingToMessage, setIsJumpingToMessage] = useState(false); const retentionPolicy = useRetentionPolicy(room); useTryToJumpToThreadMessage(); const hideFlexTab = useUserPreference('hideFlexTab') || undefined; const hideUsernames = useUserPreference('hideUsernames'); const displayAvatars = useUserPreference('displayAvatars'); const { hasMorePreviousMessages, hasMoreNextMessages, isLoadingMoreMessages } = useRoomMessages(); const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead', false); const canPreviewChannelRoom = usePermission('preview-c-room'); const subscribed = !!subscription; const canPreview = useMemo(() => { if (room && room.t !== 'c') { return true; } if (allowAnonymousRead === true) { return true; } if (canPreviewChannelRoom) { return true; } return subscribed; }, [allowAnonymousRead, canPreviewChannelRoom, room, subscribed]); const { handleUnreadBarJumpToButtonClick, handleMarkAsReadButtonClick, counter: [unread], setUnreadCount, setLastMessageDate, debouncedMessageRead, } = useHandleUnread(room, subscription); const { handleDateScroll, bubbleRef, listStyle, ...bubbleDate } = useDateScroll(); const { innerRef: getMoreInnerRef } = useGetMore(room._id, isJumpingToMessage); const [fileUploadTriggerProps, fileUploadOverlayProps] = useFileUploadDropTarget(); const { uploads, isUploading } = useFileUpload(); const { messageListRef } = useMessageListNavigation(); const { innerRef: selectAndScrollRef, selectAllAndScrollToTop } = useSelectAllAndScrollToTop(); const { handleNewMessageButtonClick, handleJumpToRecentButtonClick, handleComposerResize, hasNewMessages, debouncedClearNewMessagesOnScroll, } = useHasNewMessages(room._id, user?._id, setShouldJumpToBottom, isAtBottom); const { keepAtBottomRef, setKeepAtBottom } = useKeepAtBottom(isAtBottom); const innerRef = useMergedRefsV2(getMoreInnerRef, selectAndScrollRef, messageListRef, keepAtBottomRef); const handleNavigateToPreviousMessage = useCallback((): void => { chat.messageEditing.toPreviousMessage(); }, [chat.messageEditing]); const handleNavigateToNextMessage = useCallback((): void => { chat.messageEditing.toNextMessage(); }, [chat.messageEditing]); const handleCloseFlexTab = useCallback( (e: MouseEvent): void => { /* * check if the element is a button or anchor * it considers the role as well * usually, the flex tab is closed when clicking outside of it * but if the user clicks on a button or anchor, we don't want to close the flex tab * because the user could be actually trying to open the flex tab through those elements */ const checkElement = (element: HTMLElement | null): boolean => { if (!element) { return false; } if (element instanceof HTMLButtonElement || element.getAttribute('role') === 'button') { return true; } if (element instanceof HTMLAnchorElement || element.getAttribute('role') === 'link') { return true; } return checkElement(element.parentElement); }; if (checkElement(e.target as HTMLElement)) { return; } toolbox.closeTab(); }, [toolbox], ); useGoToHomeOnRemoved(room, user?._id); useReadMessageWindowEvents(); useQuoteMessageByUrl(); return ( <> {!isLayoutEmbedded && room.announcement && } ); }; export default memo(RoomBody);