stream-chat-react
Version:
React components to create chat conversations or livestream style chat
126 lines (125 loc) • 6.29 kB
JavaScript
import { useEffect, useRef } from 'react';
import { useChannelActionContext } from '../../../context/ChannelActionContext';
import { useChannelStateContext } from '../../../context/ChannelStateContext';
import { useTranslationContext } from '../../../context/TranslationContext';
import { LinkPreviewState } from '../types';
export const useSubmitHandler = (props, state, dispatch, numberOfUploads, enrichURLsController) => {
const { clearEditingState, message, overrideSubmitHandler, parent, publishTypingEvent, } = props;
const { attachments, linkPreviews, mentioned_users, text } = state;
const { cancelURLEnrichment, findAndEnqueueURLsToEnrich } = enrichURLsController;
const { channel } = useChannelStateContext('useSubmitHandler');
const { addNotification, editMessage, sendMessage } = useChannelActionContext('useSubmitHandler');
const { t } = useTranslationContext('useSubmitHandler');
const textReference = useRef({ hasChanged: false, initialText: text });
useEffect(() => {
if (!textReference.current.initialText.length) {
textReference.current.initialText = text;
return;
}
textReference.current.hasChanged = text !== textReference.current.initialText;
}, [text]);
const handleSubmit = async (event, customMessageData, options) => {
event?.preventDefault();
const trimmedMessage = text.trim();
const isEmptyMessage = trimmedMessage === '' ||
trimmedMessage === '>' ||
trimmedMessage === '``````' ||
trimmedMessage === '``' ||
trimmedMessage === '**' ||
trimmedMessage === '____' ||
trimmedMessage === '__' ||
trimmedMessage === '****';
if (isEmptyMessage &&
numberOfUploads === 0 &&
attachments.length === 0 &&
!customMessageData?.poll_id)
return;
const someAttachmentsUploading = attachments.some((att) => att.localMetadata?.uploadState === 'uploading');
if (someAttachmentsUploading) {
return addNotification(t('Wait until all attachments have uploaded'), 'error');
}
const attachmentsFromUploads = attachments
.filter((att) => att.localMetadata?.uploadState !== 'failed' ||
(findAndEnqueueURLsToEnrich && !att.og_scrape_url))
.map((localAttachment) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { localMetadata: _, ...attachment } = localAttachment;
return attachment;
});
const sendOptions = { ...options };
let attachmentsFromLinkPreviews = [];
if (findAndEnqueueURLsToEnrich) {
// prevent showing link preview in MessageInput after the message has been sent
cancelURLEnrichment();
const someLinkPreviewsLoading = Array.from(linkPreviews.values()).some((linkPreview) => [LinkPreviewState.QUEUED, LinkPreviewState.LOADING].includes(linkPreview.state));
const someLinkPreviewsDismissed = Array.from(linkPreviews.values()).some((linkPreview) => linkPreview.state === LinkPreviewState.DISMISSED);
attachmentsFromLinkPreviews = someLinkPreviewsLoading
? []
: Array.from(linkPreviews.values())
.filter((linkPreview) => linkPreview.state === LinkPreviewState.LOADED &&
!attachmentsFromUploads.find((attFromUpload) => attFromUpload.og_scrape_url === linkPreview.og_scrape_url))
.map(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
({ state: linkPreviewState, ...ogAttachment }) => ogAttachment);
// scraped attachments are added only if all enrich queries has completed. Otherwise, the scraping has to be done server-side.
sendOptions.skip_enrich_url =
(!someLinkPreviewsLoading && attachmentsFromLinkPreviews.length > 0) ||
someLinkPreviewsDismissed;
}
const newAttachments = [...attachmentsFromUploads, ...attachmentsFromLinkPreviews];
// Instead of checking if a user is still mentioned every time the text changes,
// just filter out non-mentioned users before submit, which is cheaper
// and allows users to easily undo any accidental deletion
const actualMentionedUsers = Array.from(new Set(mentioned_users.filter(({ id, name }) => text.includes(`@${id}`) || text.includes(`@${name}`))));
const updatedMessage = {
attachments: newAttachments,
mentioned_users: actualMentionedUsers,
text,
};
if (message && message.type !== 'error') {
delete message.i18n;
try {
await editMessage({
...message,
...updatedMessage,
...customMessageData,
}, sendOptions);
clearEditingState?.();
dispatch({ type: 'clear' });
}
catch (err) {
addNotification(t('Edit message request failed'), 'error');
}
}
else {
try {
dispatch({ type: 'clear' });
if (overrideSubmitHandler) {
await overrideSubmitHandler({
...updatedMessage,
parent,
}, channel.cid, customMessageData, sendOptions);
}
else {
await sendMessage({
...updatedMessage,
parent,
}, customMessageData, sendOptions);
}
if (publishTypingEvent)
await channel.stopTyping();
}
catch (err) {
dispatch({
getNewText: () => text,
type: 'setText',
});
actualMentionedUsers?.forEach((user) => {
dispatch({ type: 'addMentionedUser', user });
});
addNotification(t('Send message request failed'), 'error');
}
}
};
return { handleSubmit };
};