UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

201 lines (200 loc) 7.89 kB
import { useCallback, useReducer, useState } from 'react'; import { nanoid } from 'nanoid'; import { useChannelStateContext, } from '../../../context/ChannelStateContext'; import { useAttachments } from './useAttachments'; import { useLinkPreviews } from './useLinkPreviews'; import { useMessageInputText } from './useMessageInputText'; import { useSubmitHandler } from './useSubmitHandler'; import { usePasteHandler } from './usePasteHandler'; import { useMediaRecorder, } from '../../MediaRecorder/hooks/useMediaRecorder'; import { LinkPreviewState, SetLinkPreviewMode } from '../types'; import { mergeDeep } from '../../../utils/mergeDeep'; const makeEmptyMessageInputState = () => ({ attachments: [], linkPreviews: new Map(), mentioned_users: [], setText: () => null, text: '', }); /** * Initializes the state. Empty if the message prop is falsy. */ const initState = (message) => { if (!message) { return makeEmptyMessageInputState(); } const linkPreviews = message.attachments?.reduce((acc, attachment) => { if (!attachment.og_scrape_url) return acc; acc.set(attachment.og_scrape_url, { ...attachment, state: LinkPreviewState.LOADED, }); return acc; }, new Map()) ?? new Map(); const attachments = message.attachments ?.filter(({ og_scrape_url }) => !og_scrape_url) .map((att) => ({ ...att, localMetadata: { id: nanoid() }, })) || []; const mentioned_users = message.mentioned_users || []; return { attachments, linkPreviews, mentioned_users, setText: () => null, text: message.text || '', }; }; /** * MessageInput state reducer */ const messageInputReducer = (state, action) => { switch (action.type) { case 'setText': return { ...state, text: action.getNewText(state.text) }; case 'clear': return makeEmptyMessageInputState(); case 'upsertAttachments': { const attachments = [...state.attachments]; action.attachments.forEach((actionAttachment) => { const attachmentIndex = state.attachments.findIndex((att) => att.localMetadata?.id && att.localMetadata?.id === actionAttachment.localMetadata?.id); if (attachmentIndex === -1) { attachments.push(actionAttachment); } else { const upsertedAttachment = mergeDeep(state.attachments[attachmentIndex] ?? {}, actionAttachment); attachments.splice(attachmentIndex, 1, upsertedAttachment); } }); return { ...state, attachments, }; } case 'removeAttachments': { return { ...state, attachments: state.attachments.filter((att) => !action.ids.includes(att.localMetadata?.id)), }; } case 'setLinkPreviews': { const linkPreviews = new Map(state.linkPreviews); if (action.mode === SetLinkPreviewMode.REMOVE) { Array.from(action.linkPreviews.keys()).forEach((key) => { linkPreviews.delete(key); }); } else { Array.from(action.linkPreviews.values()).reduce((acc, linkPreview) => { const existingPreview = acc.get(linkPreview.og_scrape_url); const alreadyEnqueued = linkPreview.state === LinkPreviewState.QUEUED && existingPreview?.state !== LinkPreviewState.FAILED; if (existingPreview && alreadyEnqueued) return acc; acc.set(linkPreview.og_scrape_url, linkPreview); return acc; }, linkPreviews); if (action.mode === SetLinkPreviewMode.SET) { Array.from(state.linkPreviews.keys()).forEach((key) => { if (!action.linkPreviews.get(key)) linkPreviews.delete(key); }); } } return { ...state, linkPreviews, }; } case 'addMentionedUser': return { ...state, mentioned_users: state.mentioned_users.concat(action.user), }; default: return state; } }; /** * hook for MessageInput state */ export const useMessageInputState = (props) => { const { additionalTextareaProps, asyncMessagesMultiSendEnabled, audioRecordingConfig, audioRecordingEnabled, getDefaultValue, message, urlEnrichmentConfig, } = props; const { channelCapabilities = {}, enrichURLForPreview: enrichURLForPreviewChannelContext, } = useChannelStateContext('useMessageInputState'); const defaultValue = getDefaultValue?.() || additionalTextareaProps?.defaultValue; const initialStateValue = message || (Array.isArray(defaultValue) ? { text: defaultValue.join('') } : { text: defaultValue?.toString() }); const [state, dispatch] = useReducer(messageInputReducer, initialStateValue, initState); const enrichURLsController = useLinkPreviews({ dispatch, linkPreviews: state.linkPreviews, ...urlEnrichmentConfig, enrichURLForPreview: urlEnrichmentConfig?.enrichURLForPreview ?? enrichURLForPreviewChannelContext, }); const { handleChange, insertText, textareaRef } = useMessageInputText(props, state, dispatch, enrichURLsController.findAndEnqueueURLsToEnrich); const [showCommandsList, setShowCommandsList] = useState(false); const [showMentionsList, setShowMentionsList] = useState(false); const openCommandsList = () => { dispatch({ getNewText: () => '/', type: 'setText', }); setShowCommandsList(true); }; const closeCommandsList = () => setShowCommandsList(false); const openMentionsList = () => { dispatch({ getNewText: (currentText) => currentText + '@', type: 'setText', }); setShowMentionsList(true); }; const closeMentionsList = () => setShowMentionsList(false); const { maxFilesLeft, numberOfUploads, removeAttachments, uploadAttachment, uploadNewFiles, upsertAttachments, } = useAttachments(props, state, dispatch, textareaRef); const { handleSubmit } = useSubmitHandler(props, state, dispatch, numberOfUploads, enrichURLsController); const recordingController = useMediaRecorder({ asyncMessagesMultiSendEnabled, enabled: !!audioRecordingEnabled, handleSubmit, recordingConfig: audioRecordingConfig, uploadAttachment, }); const isUploadEnabled = !!channelCapabilities['upload-file']; const { onPaste } = usePasteHandler(uploadNewFiles, insertText, isUploadEnabled, enrichURLsController.findAndEnqueueURLsToEnrich); const onSelectUser = useCallback((item) => { dispatch({ type: 'addMentionedUser', user: item }); }, []); const setText = useCallback((text) => { dispatch({ getNewText: () => text, type: 'setText' }); }, []); return { ...state, ...enrichURLsController, closeCommandsList, closeMentionsList, handleChange, handleSubmit, insertText, isUploadEnabled, maxFilesLeft, numberOfUploads, onPaste, onSelectUser, openCommandsList, openMentionsList, recordingController, removeAttachments, setText, showCommandsList, showMentionsList, textareaRef, uploadAttachment, uploadNewFiles, upsertAttachments, }; };