You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			139 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			139 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
| import _, { noop } from 'lodash';
 | |
| import React, { useCallback } from 'react';
 | |
| import { InView } from 'react-intersection-observer';
 | |
| import { useDispatch, useSelector } from 'react-redux';
 | |
| import { getMessageById } from '../../data/data';
 | |
| import { Constants } from '../../session';
 | |
| import { getConversationController } from '../../session/conversations';
 | |
| import {
 | |
|   fetchMessagesForConversation,
 | |
|   markConversationFullyRead,
 | |
|   showScrollToBottomButton,
 | |
| } from '../../state/ducks/conversations';
 | |
| import {
 | |
|   areMoreMessagesBeingFetched,
 | |
|   getHaveDoneFirstScroll,
 | |
|   getLoadedMessagesLength,
 | |
|   getMostRecentMessageId,
 | |
|   getOldestMessageId,
 | |
|   getSelectedConversationKey,
 | |
| } from '../../state/selectors/conversations';
 | |
| import { getIsAppFocused } from '../../state/selectors/section';
 | |
| 
 | |
| type ReadableMessageProps = {
 | |
|   children: React.ReactNode;
 | |
|   messageId: string;
 | |
|   className?: string;
 | |
|   receivedAt: number | undefined;
 | |
|   isUnread: boolean;
 | |
|   onContextMenu?: (e: React.MouseEvent<HTMLElement>) => void;
 | |
| };
 | |
| 
 | |
| const debouncedTriggerLoadMore = _.debounce(
 | |
|   (loadedMessagesLength: number, selectedConversationKey: string | undefined) => {
 | |
|     const numMessages = loadedMessagesLength + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
 | |
|     (window.inboxStore?.dispatch as any)(
 | |
|       fetchMessagesForConversation({
 | |
|         conversationKey: selectedConversationKey as string,
 | |
|         count: numMessages,
 | |
|       })
 | |
|     );
 | |
|   },
 | |
|   100
 | |
| );
 | |
| 
 | |
| export const ReadableMessage = (props: ReadableMessageProps) => {
 | |
|   const { messageId, onContextMenu, className, receivedAt, isUnread } = props;
 | |
| 
 | |
|   const isAppFocused = useSelector(getIsAppFocused);
 | |
|   const dispatch = useDispatch();
 | |
| 
 | |
|   const selectedConversationKey = useSelector(getSelectedConversationKey);
 | |
|   const loadedMessagesLength = useSelector(getLoadedMessagesLength);
 | |
|   const haveDoneFirstScroll = useSelector(getHaveDoneFirstScroll);
 | |
|   const mostRecentMessageId = useSelector(getMostRecentMessageId);
 | |
|   const oldestMessageId = useSelector(getOldestMessageId);
 | |
|   const fetchingMore = useSelector(areMoreMessagesBeingFetched);
 | |
|   const shouldMarkReadWhenVisible = isUnread;
 | |
| 
 | |
|   const onVisible = useCallback(
 | |
|     // tslint:disable-next-line: cyclomatic-complexity
 | |
|     async (inView: boolean | Object) => {
 | |
|       // when the view first loads, it needs to scroll to the unread messages.
 | |
|       // we need to disable the inview on the first loading
 | |
|       if (!haveDoneFirstScroll) {
 | |
|         if (inView === true) {
 | |
|           window.log.info('onVisible but waiting for first scroll event');
 | |
|         }
 | |
|         return;
 | |
|       }
 | |
|       // we are the most recent message
 | |
|       if (mostRecentMessageId === messageId) {
 | |
|         // make sure the app is focused, because we mark message as read here
 | |
|         if (inView === true && isAppFocused) {
 | |
|           dispatch(showScrollToBottomButton(false));
 | |
|           void getConversationController()
 | |
|             .get(selectedConversationKey as string)
 | |
|             ?.markRead(receivedAt || 0)
 | |
|             .then(() => {
 | |
|               dispatch(markConversationFullyRead(selectedConversationKey as string));
 | |
|             });
 | |
|         } else if (inView === false) {
 | |
|           dispatch(showScrollToBottomButton(true));
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (inView === true && isAppFocused && oldestMessageId === messageId && !fetchingMore) {
 | |
|         debouncedTriggerLoadMore(loadedMessagesLength, selectedConversationKey);
 | |
|       }
 | |
| 
 | |
|       // this part is just handling the marking of the message as read if needed
 | |
|       if (
 | |
|         (inView === true ||
 | |
|           ((inView as any).type === 'focus' && (inView as any).returnValue === true)) &&
 | |
|         isAppFocused
 | |
|       ) {
 | |
|         if (shouldMarkReadWhenVisible) {
 | |
|           const found = await getMessageById(messageId);
 | |
| 
 | |
|           if (found && Boolean(found.get('unread'))) {
 | |
|             // mark the message as read.
 | |
|             // this will trigger the expire timer.
 | |
|             await found.markRead(Date.now());
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|     [
 | |
|       selectedConversationKey,
 | |
|       haveDoneFirstScroll,
 | |
|       mostRecentMessageId,
 | |
|       oldestMessageId,
 | |
|       fetchingMore,
 | |
|       isAppFocused,
 | |
|       loadedMessagesLength,
 | |
|       receivedAt,
 | |
|       shouldMarkReadWhenVisible,
 | |
|       messageId,
 | |
|       debouncedTriggerLoadMore,
 | |
|     ]
 | |
|   );
 | |
| 
 | |
|   return (
 | |
|     // tslint:disable-next-line: use-simple-attributes
 | |
|     <InView
 | |
|       id={`msg-${messageId}`}
 | |
|       onContextMenu={onContextMenu}
 | |
|       className={className}
 | |
|       as="div"
 | |
|       threshold={0.5}
 | |
|       delay={haveDoneFirstScroll && isAppFocused ? 100 : 200}
 | |
|       onChange={haveDoneFirstScroll && isAppFocused ? onVisible : noop}
 | |
|       triggerOnce={false}
 | |
|       trackVisibility={true}
 | |
|     >
 | |
|       {props.children}
 | |
|     </InView>
 | |
|   );
 | |
| };
 |