import type React from 'react'; import { useCallback, useMemo } from 'react'; import classNames from 'classnames'; import type { Merge } from 'type-fest'; import { selectPlainAccount } from '@/mastodon/selectors/accounts'; import { selectStatusFilters } from '@/mastodon/selectors/filters'; import { selectExpandedStatus } from '@/mastodon/selectors/statuses'; import { createAppSelector, useAppSelector } from '@/mastodon/store'; import { ContentWarning } from '../content_warning'; import { FilterWarning } from '../filter_warning'; import { computeHashtagBarForStatus, HashtagBar } from '../hashtag_bar'; import { Hotkeys } from '../hotkeys'; import { StatusActionBar } from './action_bar'; import { StatusAttachments } from './attachments'; import { StatusContent } from './content'; import { StatusHeader } from './header'; import type { StatusHandlers } from './hooks'; import { useStatusHandlers, useTextForScreenReader } from './hooks'; import { StatusPrepend } from './prepend'; import type { StatusContainerProps, StatusContextType } from './types'; type StatusRedesignProps = Merge< Omit, { accountId?: string; contextType?: StatusContextType; onClick?: () => void; } >; const selectStatusReblog = createAppSelector( [(state, id?: string | null) => selectExpandedStatus(state, id ?? undefined)], (status) => { if (!status) { return {}; } if (!status.reblog) { return { status }; } const { reblog, ...statusRest } = status; return { status: reblog, parent: statusRest, }; }, ); export const StatusRedesign: React.FC = ({ id, muted, rootId, previousId, nextId, unread, skipPrepend, unfocusable, contextType, featured, isQuotedPost, accountId, hidden, shouldHighlightOnMount, showActions, scrollKey, children, headerRenderFn, avatarSize, withCounters, withDismiss, onClick, showThread, }) => { // Select data from store const { status, parent } = useAppSelector((state) => selectStatusReblog(state, id), ); const account = useAppSelector( (state) => parent?.account ?? selectPlainAccount(state, accountId) ?? undefined, ); const matchedFilters = useAppSelector((state) => selectStatusFilters(state, { contextType, statusId: parent?.id ?? id }), ); const statusId = status?.id; // Display const screenReaderText = useTextForScreenReader({ statusId, reblogAcct: parent?.account.acct, isQuote: isQuotedPost, }); const { statusContent, hashtagsInBar } = useMemo( (): Partial> => status ? computeHashtagBarForStatus(status) : {}, [status], ); // Handlers const { showDespiteFilter, onHeaderClick, onExpandedToggle, onFilterToggle, onOpenClick, onTranslate, ...handlers } = useStatusHandlers({ status, contextType, onClick }); if (!status) { return null; // loading state } const actualStatus = parent ?? status; const expanded = (matchedFilters.length === 0 || showDespiteFilter) && (!status.hidden || !status.spoiler_text); const hotkeysProps = { ...handlers, onTranslate, muted, unfocusable, } satisfies Omit, 'children'>; if (hidden) { return (
{status.account.display_name || status.account.username} {status.spoiler_text && {status.spoiler_text}} {expanded && {status.content}}
); } const header = headerRenderFn ? ( headerRenderFn({ statusId: status.id, account, avatarSize, onHeaderClick, featured, }) ) : ( ); return (
{!skipPrepend && ( )} {header} {matchedFilters.length > 0 && ( filter.title).join(', ')} expanded={showDespiteFilter} onClick={onFilterToggle} /> )} {(matchedFilters.length === 0 || showDespiteFilter) && ( )} {expanded && ( <> {hashtagsInBar && ( )} {children} )} {showActions && !isQuotedPost && ( )}
); }; const StatusHotkeys: React.FC< { muted?: boolean; unfocusable?: boolean; children: React.ReactNode; } & Omit< StatusHandlers, | 'showDespiteFilter' | 'onOpenClick' | 'onHeaderClick' | 'onExpandedToggle' | 'onFilterToggle' > > = ({ muted, unfocusable, children, ...handlers }) => { const onOpen = useCallback(() => { handlers.onOpen(); }, [handlers]); if (muted) { return children; } return ( {children} ); }; const StatusContentWrapper: React.FC< Pick & { statusId: string; inReplyToId?: string; className?: string; } > = ({ statusId, inReplyToId, rootId, previousId, nextId, className, children, }) => { const nextInReplyToId = useAppSelector((state) => nextId ? state.statuses.getIn([nextId, 'in_reply_to_id']) : null, ); const connectUp = !!previousId && previousId === inReplyToId; const connectToRoot = !!rootId && rootId === inReplyToId; const connectReply = !!nextInReplyToId && nextInReplyToId === statusId; return (
{(connectReply || connectUp || connectToRoot) && (
)} {children}
); };