import { useCallback, useEffect } from 'react'; import type { FC } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { useParams } from 'react-router'; import { List as ImmutableList } from 'immutable'; import { expandTimelineByKey, timelineKey, } from '@/mastodon/actions/timelines_typed'; import { AccountHeader } from '@/mastodon/components/account_header'; import { Column } from '@/mastodon/components/column'; import { ColumnBackButton } from '@/mastodon/components/column_back_button'; import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint'; import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; import { RemoteHint } from '@/mastodon/components/remote_hint'; import StatusList from '@/mastodon/components/status_list'; import { BundleColumnError } from '@/mastodon/features/ui/components/bundle_column_error'; import { useAccountId, useCurrentAccountId, } from '@/mastodon/hooks/useAccountId'; import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility'; import { selectTimelineByKey } from '@/mastodon/selectors/timelines'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import { FeaturedTags } from './components/featured_tags'; import { AccountFilters } from './components/filters'; import { renderPinnedStatusHeader } from './components/pinned_statuses'; import { TagSuggestions } from './components/tags_suggestions'; import { AccountTimelineContext, useAccountContext, useAccountContextValue, } from './hooks/useAccountContext'; import { usePinnedStatusIds } from './hooks/usePinned'; import classes from './styles.module.scss'; const emptyList = ImmutableList(); const AccountTimeline: FC<{ multiColumn: boolean }> = ({ multiColumn }) => { const accountId = useAccountId(); const accountContext = useAccountContextValue(accountId); // Null means accountId does not exist (e.g. invalid acct). Undefined means loading. if (accountId === null) { return ; } if (!accountId) { return ( ); } // Add this key to remount the timeline when accountId changes. return ( ); }; const InnerTimeline: FC<{ accountId: string; multiColumn: boolean }> = ({ accountId, multiColumn, }) => { const { tagged } = useParams<{ tagged?: string }>(); const { boosts, replies } = useAccountContext(); const key = timelineKey({ type: 'account', userId: accountId, tagged, boosts, replies, }); const timeline = useAppSelector((state) => selectTimelineByKey(state, key)); const { blockedBy, hidden, suspended } = useAccountVisibility(accountId); const forceEmptyState = blockedBy || hidden || suspended; const dispatch = useAppDispatch(); useEffect(() => { if (accountId) { dispatch(expandTimelineByKey({ key })); } }, [accountId, dispatch, key]); const handleLoadMore = useCallback( (maxId: number) => { if (accountId) { dispatch(expandTimelineByKey({ key, maxId })); } }, [accountId, dispatch, key], ); const { isLoading: isPinnedLoading, statusIds: pinnedStatusIds } = usePinnedStatusIds({ accountId, tagged, forceEmptyState }); const isLoading = !!timeline?.isLoading || isPinnedLoading; return ( } append={} scrollKey='account_timeline' // We want to have this component when timeline is undefined (loading), // because if we don't the prepended component will re-render with every filter change. statusIds={forceEmptyState ? emptyList : (timeline?.items ?? emptyList)} featuredStatusIds={pinnedStatusIds} isLoading={isLoading} hasMore={!forceEmptyState && !!timeline?.hasMore} onLoadMore={handleLoadMore} emptyMessage={} bindToDocument={!multiColumn} timelineId='account' withCounters className={classNames(classes.statusWrapper)} statusProps={{ headerRenderFn: renderPinnedStatusHeader }} /> ); }; const Prepend: FC<{ accountId: string; forceEmpty: boolean; }> = ({ forceEmpty, accountId }) => { const me = useCurrentAccountId(); if (forceEmpty) { return ; } return ( <> {me === accountId && } ); }; const EmptyMessage: FC<{ accountId: string }> = ({ accountId }) => { const { blockedBy, hidden, suspended } = useAccountVisibility(accountId); if (suspended) { return ( ); } else if (hidden) { return ; } else if (blockedBy) { return ( ); } return ( ); }; // eslint-disable-next-line import/no-default-export export default AccountTimeline;