import PropTypes from 'prop-types'; import { useRef, useCallback, useEffect } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import { useSelector, useDispatch } from 'react-redux'; import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; import DoneIcon from '@/material-icons/400-24px/done.svg?react'; import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; import { fetchNotificationRequest, fetchNotificationsForRequest, expandNotificationsForRequest, acceptNotificationRequest, dismissNotificationRequest } from 'mastodon/actions/notifications'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import { IconButton } from 'mastodon/components/icon_button'; import ScrollableList from 'mastodon/components/scrollable_list'; import { SensitiveMediaContextProvider } from 'mastodon/features/ui/util/sensitive_media_context'; import NotificationContainer from './containers/notification_container'; const messages = defineMessages({ title: { id: 'notification_requests.notifications_from', defaultMessage: 'Notifications from {name}' }, accept: { id: 'notification_requests.accept', defaultMessage: 'Accept' }, dismiss: { id: 'notification_requests.dismiss', defaultMessage: 'Dismiss' }, }); const selectChild = (ref, index, alignTop) => { const container = ref.current.node; const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); if (element) { if (alignTop && container.scrollTop > element.offsetTop) { element.scrollIntoView(true); } else if (!alignTop && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { element.scrollIntoView(false); } element.focus(); } }; export const NotificationRequest = ({ multiColumn, params: { id } }) => { const columnRef = useRef(); const intl = useIntl(); const dispatch = useDispatch(); const notificationRequest = useSelector(state => state.getIn(['notificationRequests', 'current', 'item', 'id']) === id ? state.getIn(['notificationRequests', 'current', 'item']) : null); const accountId = notificationRequest?.get('account'); const account = useSelector(state => state.getIn(['accounts', accountId])); const notifications = useSelector(state => state.getIn(['notificationRequests', 'current', 'notifications', 'items'])); const isLoading = useSelector(state => state.getIn(['notificationRequests', 'current', 'notifications', 'isLoading'])); const hasMore = useSelector(state => !!state.getIn(['notificationRequests', 'current', 'notifications', 'next'])); const removed = useSelector(state => state.getIn(['notificationRequests', 'current', 'removed'])); const handleHeaderClick = useCallback(() => { columnRef.current?.scrollTop(); }, [columnRef]); const handleLoadMore = useCallback(() => { dispatch(expandNotificationsForRequest()); }, [dispatch]); const handleDismiss = useCallback(() => { dispatch(dismissNotificationRequest(id)); }, [dispatch, id]); const handleAccept = useCallback(() => { dispatch(acceptNotificationRequest(id)); }, [dispatch, id]); const handleMoveUp = useCallback(id => { const elementIndex = notifications.findIndex(item => item !== null && item.get('id') === id) - 1; selectChild(columnRef, elementIndex, true); }, [columnRef, notifications]); const handleMoveDown = useCallback(id => { const elementIndex = notifications.findIndex(item => item !== null && item.get('id') === id) + 1; selectChild(columnRef, elementIndex, false); }, [columnRef, notifications]); useEffect(() => { dispatch(fetchNotificationRequest(id)); }, [dispatch, id]); useEffect(() => { if (accountId) { dispatch(fetchNotificationsForRequest(accountId)); } }, [dispatch, accountId]); const columnTitle = intl.formatMessage(messages.title, { name: account?.get('display_name') || account?.get('username') }); let explainer = null; if (account?.limited) { const isLocal = account.acct.indexOf('@') === -1; explainer = (