import { useCallback, useEffect, useId, useRef } from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; import type { Map } from 'immutable'; import { OrderedSet } from 'immutable'; import { shallowEqual } from 'react-redux'; import Toggle from 'react-toggle'; import { fetchAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; import type { Status } from 'mastodon/models/status'; import type { RootState } from 'mastodon/store'; import { createAppSelector, useAppDispatch, useAppSelector, } from 'mastodon/store'; const messages = defineMessages({ placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments', }, }); const selectRepliedToAccountIds = createAppSelector( [ (state: RootState) => state.statuses, (_: unknown, statusIds: string[]) => statusIds, ], (statusesMap: Map, statusIds: string[]) => statusIds.map( (statusId) => statusesMap.getIn([statusId, 'in_reply_to_account_id']) as string, ), { memoizeOptions: { resultEqualityCheck: shallowEqual, }, }, ); interface Props { modalTitle?: React.ReactNode; comment: string; domain?: string; statusIds: string[]; isRemote?: boolean; isSubmitting?: boolean; selectedDomains: string[]; submitError?: React.ReactNode; onSubmit: () => void; onChangeComment: (newComment: string) => void; onToggleDomain: (toggledDomain: string, checked: boolean) => void; } const Comment: React.FC = ({ modalTitle, comment, domain, statusIds, isRemote, isSubmitting, selectedDomains, submitError, onSubmit, onChangeComment, onToggleDomain, }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const loadedRef = useRef(false); const handleSubmit = useCallback(() => { onSubmit(); }, [onSubmit]); const handleChange = useCallback( (e: React.ChangeEvent) => { onChangeComment(e.target.value); }, [onChangeComment], ); const handleToggleDomain = useCallback( (e: React.ChangeEvent) => { onToggleDomain(e.target.value, e.target.checked); }, [onToggleDomain], ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { handleSubmit(); } }, [handleSubmit], ); // Memoize accountIds since we don't want it to trigger `useEffect` on each render const accountIds = useAppSelector((state) => domain ? selectRepliedToAccountIds(state, statusIds) : [], ); // While we could memoize `availableDomains`, it is pretty inexpensive to recompute const accountsMap = useAppSelector((state) => state.accounts); const availableDomains = domain ? OrderedSet([domain]).union( accountIds .map( (accountId) => (accountsMap.getIn([accountId, 'acct'], '') as string).split( '@', )[1], ) .filter((domain): domain is string => !!domain), ) : OrderedSet(); useEffect(() => { if (loadedRef.current) { return; } loadedRef.current = true; // First, pre-select known domains availableDomains.forEach((domain) => { onToggleDomain(domain, true); }); // Then, fetch missing replied-to accounts const unknownAccounts = OrderedSet( accountIds.filter( (accountId) => accountId && !accountsMap.has(accountId), ), ); unknownAccounts.forEach((accountId) => { dispatch(fetchAccount(accountId)); }); }); const titleId = useId(); return ( <>

{modalTitle ?? ( )}