import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { unblockAccount } from 'soapbox/actions/accounts'; import { openModal } from 'soapbox/actions/modals'; import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui'; import { useChatContext } from 'soapbox/contexts/chat-context'; import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; const messages = defineMessages({ placeholder: { id: 'chat.input.placeholder', defaultMessage: 'Type a message' }, send: { id: 'chat.actions.send', defaultMessage: 'Send' }, retry: { id: 'chat.retry', defaultMessage: 'Retry?' }, blocked: { id: 'chat_message_list.blocked', defaultMessage: 'You blocked this user' }, unblock: { id: 'chat_composer.unblock', defaultMessage: 'Unblock' }, unblockMessage: { id: 'chat_settings.unblock.message', defaultMessage: 'Unblocking will allow this profile to direct message you and view your content.' }, unblockHeading: { id: 'chat_settings.unblock.heading', defaultMessage: 'Unblock @{acct}' }, unblockConfirm: { id: 'chat_settings.unblock.confirm', defaultMessage: 'Unblock' }, }); const initialSuggestionState = { list: [], tokenStart: 0, token: '', }; interface Suggestion { list: { native: string, colons: string }[], tokenStart: number, token: string, } interface IChatComposer extends Pick, 'onKeyDown' | 'onChange' | 'disabled'> { value: string onSubmit: () => void errorMessage: string | undefined } /** Textarea input for chats. */ const ChatComposer = React.forwardRef(({ onKeyDown, onChange, value, onSubmit, errorMessage = false, disabled = false, }, ref) => { const dispatch = useAppDispatch(); const intl = useIntl(); const { chat } = useChatContext(); const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocked_by'])); const isBlocking = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocking'])); const maxCharacterCount = useAppSelector((state) => state.instance.getIn(['configuration', 'chats', 'max_characters']) as number); const [suggestions, setSuggestions] = useState(initialSuggestionState); const isSuggestionsAvailable = suggestions.list.length > 0; const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount; const isSubmitDisabled = disabled || isOverCharacterLimit || value.length === 0; const overLimitText = maxCharacterCount ? maxCharacterCount - value?.length : ''; const renderSuggestionValue = (emoji: any) => { return `${(value).slice(0, suggestions.tokenStart)}${emoji.native} ${(value as string).slice(suggestions.tokenStart + suggestions.token.length)}`; }; const onSelectComboboxOption = (selection: string) => { const event = { target: { value: selection } } as React.ChangeEvent; if (onChange) { onChange(event); setSuggestions(initialSuggestionState); } }; const handleChange = (event: React.ChangeEvent) => { const [tokenStart, token] = textAtCursorMatchesToken( event.target.value, event.target.selectionStart, [':'], ); if (token && tokenStart) { const results = emojiSearch(token.replace(':', ''), { maxResults: 5 } as any); setSuggestions({ list: results, token, tokenStart: tokenStart - 1, }); } else { setSuggestions(initialSuggestionState); } if (onChange) { onChange(event); } }; const handleKeyDown: React.KeyboardEventHandler = (event) => { if (event.key === 'Enter' && !event.shiftKey && isSuggestionsAvailable) { return; } if (onKeyDown) { onKeyDown(event); } }; const handleUnblockUser = () => { dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.unblockHeading, { acct: chat?.account.acct }), message: intl.formatMessage(messages.unblockMessage), confirm: intl.formatMessage(messages.unblockConfirm), confirmationTheme: 'primary', onConfirm: () => dispatch(unblockAccount(chat?.account.id as string)), })); }; if (isBlocking) { return (
{intl.formatMessage(messages.blocked)}
); } if (isBlocked) { return null; } return (
{isSuggestionsAvailable ? ( {suggestions.list.map((emojiSuggestion) => ( {emojiSuggestion.native} {emojiSuggestion.colons} ))} ) : null} {isOverCharacterLimit ? ( {overLimitText} ) : null} {errorMessage && ( <> {errorMessage} )}
); }); export default ChatComposer;