communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
294 lines • 17.2 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useCallback, useMemo, useRef, useState } from 'react';
/* @conditional-compile-remove(file-sharing-acs) */
import { useEffect } from 'react';
import { RichTextInputBoxComponent } from './RichTextInputBoxComponent';
import { Icon, Stack } from '@fluentui/react';
import { useLocale } from '../../localization';
import { sendIconStyle } from '../styles/SendBox.styles';
/* @conditional-compile-remove(file-sharing-acs) */
import { useV9CustomStyles } from '../styles/SendBox.styles';
import { InputBoxButton } from '../InputBoxButton';
import { RichTextSendBoxErrors } from './RichTextSendBoxErrors';
import { isMessageTooLong, isSendBoxButtonDisabled, sanitizeText, modifyInlineImagesInContentString } from '../utils/SendBoxUtils';
/* @conditional-compile-remove(rich-text-editor-image-upload) */
import { hasInlineImageContent, getContentWithUpdatedInlineImagesInfo } from '../utils/SendBoxUtils';
import { useTheme } from '../../theming';
import { richTextActionButtonsStyle, sendBoxRichTextEditorStyle } from '../styles/RichTextEditor.styles';
/* @conditional-compile-remove(file-sharing-acs) */
import { _AttachmentUploadCards } from '../Attachment/AttachmentUploadCards';
/* @conditional-compile-remove(file-sharing-acs) */
import { isAttachmentUploadCompleted, hasIncompleteAttachmentUploads, toAttachmentMetadata } from '../utils/SendBoxUtils';
/* @conditional-compile-remove(rich-text-editor-image-upload) */
import { SendBoxErrorBarType } from '../SendBoxErrorBar';
/* @conditional-compile-remove(file-sharing-acs) */
import { attachmentUploadCardsStyles } from '../styles/SendBox.styles';
/* @conditional-compile-remove(file-sharing-acs) */
import { FluentV9ThemeProvider } from '../../theming/FluentV9ThemeProvider';
/* @conditional-compile-remove(rich-text-editor) */
import { richTextSendBoxIdentifier } from './RichTextSendBoxUtils';
/**
* A component to render SendBox with Rich Text Editor support.
*
* @beta
*/
export const RichTextSendBox = (props) => {
const { disabled = false, systemMessage, autoFocus, onSendMessage, onTyping,
/* @conditional-compile-remove(file-sharing-acs) */
attachments,
/* @conditional-compile-remove(file-sharing-acs) */
onCancelAttachmentUpload,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
onPaste,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
onInsertInlineImage,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
inlineImagesWithProgress,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
onRemoveInlineImage } = props;
const theme = useTheme();
const locale = useLocale();
const localeStrings = useMemo(() => {
/* @conditional-compile-remove(rich-text-editor) */
return locale.strings.richTextSendBox;
return locale.strings.sendBox;
}, [/* @conditional-compile-remove(rich-text-editor) */ locale.strings.richTextSendBox, locale.strings.sendBox]);
const strings = useMemo(() => {
return Object.assign(Object.assign({}, localeStrings), props.strings);
}, [localeStrings, props.strings]);
const [contentValue, setContentValue] = useState('');
const [contentValueOverflow, setContentValueOverflow] = useState(false);
/* @conditional-compile-remove(file-sharing-acs) */
const [attachmentUploadsPendingError, setAttachmentUploadsPendingError] = useState(undefined);
const editorComponentRef = useRef(null);
/* @conditional-compile-remove(file-sharing-acs) */
const customV9Styles = useV9CustomStyles();
const contentTooLongMessage = useMemo(() => (contentValueOverflow ? strings.textTooLong : undefined), [contentValueOverflow, strings.textTooLong]);
/* @conditional-compile-remove(file-sharing-acs) */
useEffect(() => {
if (!hasIncompleteAttachmentUploads(attachments)) {
setAttachmentUploadsPendingError(undefined);
}
}, [attachments]);
const setContent = useCallback((newValue) => {
if (newValue === undefined) {
return;
}
setContentValueOverflow(isMessageTooLong(newValue.length));
setContentValue(newValue);
}, []);
const onChangeHandler = useCallback((newValue,
/* @conditional-compile-remove(rich-text-editor-image-upload) */ removedInlineImages) => {
/* @conditional-compile-remove(rich-text-editor-image-upload) */
removedInlineImages === null || removedInlineImages === void 0 ? void 0 : removedInlineImages.forEach((removedInlineImage) => onRemoveInlineImage && onRemoveInlineImage(removedInlineImage));
setContent(newValue);
}, [setContent, /* @conditional-compile-remove(rich-text-editor-image-upload) */ onRemoveInlineImage]);
const hasContent = useMemo(() => {
var _a;
// get plain text content from the editor to check if the message is empty
// as the content may contain tags even when the content is empty
const plainTextContent = (_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.getPlainContent();
const hasPlainText = sanitizeText(contentValue !== null && contentValue !== void 0 ? contentValue : '').length > 0 && sanitizeText(plainTextContent !== null && plainTextContent !== void 0 ? plainTextContent : '').length > 0;
/* @conditional-compile-remove(rich-text-editor-image-upload) */
const hasInlineImages = hasInlineImageContent(contentValue);
return hasPlainText || /* @conditional-compile-remove(rich-text-editor-image-upload) */ hasInlineImages;
}, [contentValue]);
const sendMessageOnClick = useCallback(() => {
/* @conditional-compile-remove(rich-text-editor-image-upload) */
if (inlineImagesWithProgress && inlineImagesWithProgress.length > 0) {
const contentWithUpdatedInlineImagesInfo = getContentWithUpdatedInlineImagesInfo(contentValue, inlineImagesWithProgress);
const messageTooLong = isMessageTooLong(contentWithUpdatedInlineImagesInfo.length);
// Set contentValueOverflow state to display the error bar
setContentValueOverflow(messageTooLong);
// The change from the setContentValueOverflow in the previous line will not kick in yet.
// We need to relay on the local value of messageTooLong to return early if the message is too long.
if (messageTooLong) {
return;
}
}
if (disabled || contentValueOverflow) {
return;
}
// Don't send message until all attachments have been uploaded successfully
/* @conditional-compile-remove(file-sharing-acs) */
setAttachmentUploadsPendingError(undefined);
/* @conditional-compile-remove(rich-text-editor-image-upload) */
const hasIncompleteImageUploads = hasIncompleteAttachmentUploads(inlineImagesWithProgress);
/* @conditional-compile-remove(file-sharing-acs) */
/* @conditional-compile-remove(rich-text-editor-image-upload) */
if (
/* @conditional-compile-remove(file-sharing-acs) */ hasIncompleteAttachmentUploads(attachments) ||
/* @conditional-compile-remove(rich-text-editor-image-upload) */ hasIncompleteImageUploads) {
/* @conditional-compile-remove(file-sharing-acs) */
let errorMessage = strings.attachmentUploadsPendingError;
/* @conditional-compile-remove(rich-text-editor-image-upload) */
if (hasIncompleteImageUploads) {
errorMessage = strings.imageUploadsPendingError || '';
}
setAttachmentUploadsPendingError({
message: errorMessage,
timestamp: Date.now(),
/* @conditional-compile-remove(rich-text-editor-image-upload) */
errorBarType: SendBoxErrorBarType.info
});
return;
}
// we don't want to send empty messages including spaces, newlines, tabs
// Message can be empty if there is a valid attachment upload
if (hasContent || /* @conditional-compile-remove(file-sharing-acs) */ isAttachmentUploadCompleted(attachments)) {
const sendMessage = (content) => {
var _a, _b;
onSendMessage(content,
/* @conditional-compile-remove(file-sharing-acs) */ /* @conditional-compile-remove(rich-text-editor-composite-support) */
{
/* @conditional-compile-remove(file-sharing-acs) */
attachments: toAttachmentMetadata(attachments),
/* @conditional-compile-remove(rich-text-editor-composite-support) */
type: 'html'
});
setContentValue('');
(_a = editorComponentRef.current) === null || _a === void 0 ? void 0 : _a.setEmptyContent();
(_b = editorComponentRef.current) === null || _b === void 0 ? void 0 : _b.focus();
};
modifyInlineImagesInContentString(contentValue, [], (content) => {
sendMessage(content);
});
}
}, [
disabled,
contentValueOverflow,
/* @conditional-compile-remove(file-sharing-acs) */
attachments,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
inlineImagesWithProgress,
contentValue,
hasContent,
/* @conditional-compile-remove(file-sharing-acs) */
strings.attachmentUploadsPendingError,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
strings.imageUploadsPendingError,
onSendMessage
]);
// ignore attachments error (errored attachment will not be added, shouldn't disable SendButton)
const hasBlockingErrorMessages = useMemo(() => {
var _a;
return (!!systemMessage ||
!!contentTooLongMessage ||
/* @conditional-compile-remove(file-sharing-acs) */
!!attachmentUploadsPendingError ||
/* @conditional-compile-remove(rich-text-editor-image-upload) */
!!((_a = inlineImagesWithProgress === null || inlineImagesWithProgress === void 0 ? void 0 : inlineImagesWithProgress.filter((image) => image.error).pop()) === null || _a === void 0 ? void 0 : _a.error));
}, [
contentTooLongMessage,
/* @conditional-compile-remove(file-sharing-acs) */
attachmentUploadsPendingError,
systemMessage,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
inlineImagesWithProgress
]);
const isSendBoxButtonDisabledValue = useMemo(() => {
return isSendBoxButtonDisabled({
hasContent,
/* @conditional-compile-remove(file-sharing-acs) */ hasCompletedAttachmentUploads: isAttachmentUploadCompleted(attachments),
hasError: hasBlockingErrorMessages,
disabled
});
}, [/* @conditional-compile-remove(file-sharing-acs) */ attachments, disabled, hasContent, hasBlockingErrorMessages]);
const onRenderSendIcon = useCallback((isHover) => {
return (React.createElement(Icon, { iconName: isHover && hasContent ? 'SendBoxSendHovered' : 'SendBoxSend', className: sendIconStyle({
theme,
isSendBoxButtonDisabled: isSendBoxButtonDisabledValue,
defaultTextColor: theme.palette.neutralSecondary
}) }));
}, [theme, isSendBoxButtonDisabledValue, hasContent]);
const sendBoxErrorsProps = useMemo(() => {
var _a, _b, _c, _d;
/* @conditional-compile-remove(file-sharing-acs) */
const uploadErrorMessage = (_b = (_a = attachments === null || attachments === void 0 ? void 0 : attachments.filter((attachmentUpload) => attachmentUpload.error).pop()) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.message;
/* @conditional-compile-remove(rich-text-editor-image-upload) */
const imageUploadErrorMessage = (_d = (_c = inlineImagesWithProgress === null || inlineImagesWithProgress === void 0 ? void 0 : inlineImagesWithProgress.filter((image) => image.error).pop()) === null || _c === void 0 ? void 0 : _c.error) === null || _d === void 0 ? void 0 : _d.message;
/* @conditional-compile-remove(file-sharing-acs) */
const errorMessage = uploadErrorMessage || /* @conditional-compile-remove(rich-text-editor-image-upload) */ imageUploadErrorMessage;
/* @conditional-compile-remove(rich-text-editor-image-upload) */
let errorBarType = SendBoxErrorBarType.error;
/* @conditional-compile-remove(file-sharing-acs) */
if (uploadErrorMessage) {
errorBarType = SendBoxErrorBarType.warning;
}
return {
/* @conditional-compile-remove(file-sharing-acs) */
attachmentUploadsPendingError: attachmentUploadsPendingError,
/* @conditional-compile-remove(file-sharing-acs) */
attachmentProgressError: errorMessage
? {
message: errorMessage,
timestamp: Date.now(),
/* @conditional-compile-remove(rich-text-editor-image-upload) */
errorBarType: errorBarType
}
: undefined,
systemMessage: systemMessage,
textValidationErrorMessage: contentTooLongMessage
};
}, [
/* @conditional-compile-remove(file-sharing-acs) */
attachments,
contentTooLongMessage,
/* @conditional-compile-remove(file-sharing-acs) */
attachmentUploadsPendingError,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
inlineImagesWithProgress,
systemMessage
]);
/* @conditional-compile-remove(file-sharing-acs) */
const onRenderAttachmentUploads = useCallback(() => {
return (React.createElement(Stack, { className: attachmentUploadCardsStyles },
React.createElement(FluentV9ThemeProvider, { v8Theme: theme, className: customV9Styles.clearBackground },
React.createElement(_AttachmentUploadCards, { attachments: attachments, onCancelAttachmentUpload: onCancelAttachmentUpload, strings: {
removeAttachment: strings.removeAttachment,
uploading: strings.uploading,
uploadProgress: strings.uploadProgress,
uploadCompleted: strings.uploadCompleted,
attachmentMoreMenu: strings.attachmentMoreMenu
}, disabled: disabled }))));
}, [
theme,
customV9Styles.clearBackground,
attachments,
onCancelAttachmentUpload,
strings.removeAttachment,
strings.uploading,
strings.uploadProgress,
strings.uploadCompleted,
strings.attachmentMoreMenu,
disabled
]);
const sendButton = useMemo(() => {
return (React.createElement(InputBoxButton, { onRenderIcon: onRenderSendIcon, onClick: (e) => {
sendMessageOnClick();
e.stopPropagation(); // Prevents the click from bubbling up and triggering a focus event on the chat.
}, className: richTextActionButtonsStyle, ariaLabel: localeStrings.sendButtonAriaLabel, tooltipContent: localeStrings.sendButtonAriaLabel, disabled: isSendBoxButtonDisabledValue }));
}, [isSendBoxButtonDisabledValue, localeStrings.sendButtonAriaLabel, onRenderSendIcon, sendMessageOnClick]);
/* @conditional-compile-remove(file-sharing-acs) */
const hasAttachmentUploads = useMemo(() => {
return isAttachmentUploadCompleted(attachments) || hasIncompleteAttachmentUploads(attachments);
}, [attachments]);
return (React.createElement(Stack, null,
React.createElement(RichTextSendBoxErrors, Object.assign({}, sendBoxErrorsProps)),
React.createElement(RichTextInputBoxComponent, { placeholderText: strings.placeholderText, autoFocus: autoFocus, onChange: onChangeHandler, onEnterKeyDown: sendMessageOnClick, onTyping: onTyping, editorComponentRef: editorComponentRef, strings: strings, disabled: disabled, actionComponents: sendButton, richTextEditorStyleProps: sendBoxRichTextEditorStyle,
/* @conditional-compile-remove(file-sharing-acs) */
onRenderAttachmentUploads: onRenderAttachmentUploads,
/* @conditional-compile-remove(file-sharing-acs) */
hasAttachments: hasAttachmentUploads,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
onPaste: onPaste,
/* @conditional-compile-remove(rich-text-editor-image-upload) */
onInsertInlineImage: onInsertInlineImage })));
};
/* @conditional-compile-remove(rich-text-editor) */
// Add component type check to assist in identification for usePropsFor
// to avoid issue where production build does not have the component name
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RichTextSendBox[richTextSendBoxIdentifier] = true;
//# sourceMappingURL=RichTextSendBox.js.map