Remove and move profile code (#38863)
This commit is contained in:
parent
3021cd8002
commit
945ac23910
@ -4,6 +4,10 @@ import type { FC } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchRelationships } from '@/mastodon/actions/accounts';
|
import { fetchRelationships } from '@/mastodon/actions/accounts';
|
||||||
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
|
import type { AccountRole } from '@/mastodon/models/account';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AdminBadge,
|
AdminBadge,
|
||||||
AutomatedBadge,
|
AutomatedBadge,
|
||||||
@ -11,10 +15,7 @@ import {
|
|||||||
BlockedBadge,
|
BlockedBadge,
|
||||||
GroupBadge,
|
GroupBadge,
|
||||||
MutedBadge,
|
MutedBadge,
|
||||||
} from '@/mastodon/components/badge';
|
} from '../badge';
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
|
||||||
import type { AccountRole } from '@/mastodon/models/account';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
@ -6,9 +6,6 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { followAccount } from '@/mastodon/actions/accounts';
|
import { followAccount } from '@/mastodon/actions/accounts';
|
||||||
import { CopyIconButton } from '@/mastodon/components/copy_icon_button';
|
|
||||||
import { FollowButton } from '@/mastodon/components/follow_button';
|
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
@ -16,6 +13,10 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react
|
|||||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
|
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
|
|
||||||
|
import { CopyIconButton } from '../copy_icon_button';
|
||||||
|
import { FollowButton } from '../follow_button';
|
||||||
|
import { IconButton } from '../icon_button';
|
||||||
|
|
||||||
import { AccountMenu } from './menu';
|
import { AccountMenu } from './menu';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -7,21 +7,21 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
import { useFieldHtml } from '@/mastodon/features/account_timeline/hooks/useFieldHtml';
|
||||||
import type { EmojiHTMLProps } from '@/mastodon/components/emoji/html';
|
import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize';
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
|
||||||
import { MiniCard } from '@/mastodon/components/mini_card';
|
|
||||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useResizeObserver } from '@/mastodon/hooks/useObserver';
|
import { useResizeObserver } from '@/mastodon/hooks/useObserver';
|
||||||
|
import type { AccountFieldShape } from '@/mastodon/models/account';
|
||||||
import { useAppDispatch } from '@/mastodon/store';
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
import MoreIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
import MoreIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
|
||||||
import { cleanExtraEmojis } from '../../emoji/normalize';
|
import { CustomEmojiProvider } from '../emoji/context';
|
||||||
import type { AccountField } from '../common';
|
import type { EmojiHTMLProps } from '../emoji/html';
|
||||||
import { useFieldHtml } from '../hooks/useFieldHtml';
|
import { EmojiHTML } from '../emoji/html';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
import { IconButton } from '../icon_button';
|
||||||
|
import { MiniCard } from '../mini_card';
|
||||||
|
import { useElementHandledLink } from '../status/handled_link';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
@ -37,6 +37,12 @@ const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
|||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface AccountField extends AccountFieldShape {
|
||||||
|
nameHasEmojis: boolean;
|
||||||
|
value_plain: string;
|
||||||
|
valueHasEmojis: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const AccountHeaderFields: FC<{ accountId: string }> = ({
|
export const AccountHeaderFields: FC<{ accountId: string }> = ({
|
||||||
accountId,
|
accountId,
|
||||||
}) => {
|
}) => {
|
||||||
@ -4,9 +4,6 @@ import classNames from 'classnames';
|
|||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { AccountBio } from '@/mastodon/components/account_bio';
|
|
||||||
import { Avatar } from '@/mastodon/components/avatar';
|
|
||||||
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
|
||||||
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
|
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
|
||||||
import { useLayout } from '@/mastodon/hooks/useLayout';
|
import { useLayout } from '@/mastodon/hooks/useLayout';
|
||||||
import { useVisibility } from '@/mastodon/hooks/useVisibility';
|
import { useVisibility } from '@/mastodon/hooks/useVisibility';
|
||||||
@ -19,17 +16,20 @@ import type { Account } from '@/mastodon/models/account';
|
|||||||
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
import { getAccountHidden } from '@/mastodon/selectors/accounts';
|
||||||
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
|
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
|
||||||
|
|
||||||
import { FamiliarFollowers } from '../../../components/familiar_followers';
|
import { AccountBio } from '../account_bio';
|
||||||
|
import { Avatar } from '../avatar';
|
||||||
|
import { AnimateEmojiProvider } from '../emoji/context';
|
||||||
|
import { FamiliarFollowers } from '../familiar_followers';
|
||||||
|
|
||||||
import { AccountName } from './account_name';
|
|
||||||
import { AccountSubscriptionForm } from './account_subscription_form';
|
|
||||||
import { AccountButtons } from './buttons';
|
import { AccountButtons } from './buttons';
|
||||||
import { AccountHeaderFields } from './fields';
|
import { AccountHeaderFields } from './fields';
|
||||||
import { MemorialNote } from './memorial_note';
|
import { MemorialNote } from './memorial_note';
|
||||||
import { MovedNote } from './moved_note';
|
import { MovedNote } from './moved_note';
|
||||||
|
import { AccountName } from './name';
|
||||||
import { AccountNote } from './note';
|
import { AccountNote } from './note';
|
||||||
import { AccountNumberFields } from './number_fields';
|
import { AccountNumberFields } from './number_fields';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
import { AccountSubscriptionForm } from './subscription_form';
|
||||||
import { AccountTabs } from './tabs';
|
import { AccountTabs } from './tabs';
|
||||||
|
|
||||||
const titleFromAccount = (account: Account) => {
|
const titleFromAccount = (account: Account) => {
|
||||||
@ -21,7 +21,6 @@ import {
|
|||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { initMuteModal } from '@/mastodon/actions/mutes';
|
import { initMuteModal } from '@/mastodon/actions/mutes';
|
||||||
import { initReport } from '@/mastodon/actions/reports';
|
import { initReport } from '@/mastodon/actions/reports';
|
||||||
import { Dropdown } from '@/mastodon/components/dropdown_menu';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useIdentity } from '@/mastodon/identity_context';
|
import { useIdentity } from '@/mastodon/identity_context';
|
||||||
import type { Account } from '@/mastodon/models/account';
|
import type { Account } from '@/mastodon/models/account';
|
||||||
@ -40,6 +39,8 @@ import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react'
|
|||||||
import ReportIcon from '@/material-icons/400-24px/report.svg?react';
|
import ReportIcon from '@/material-icons/400-24px/report.svg?react';
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
|
|
||||||
|
import { Dropdown } from '../dropdown_menu';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
@ -2,9 +2,10 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { DisplayName } from '@/mastodon/components/display_name';
|
import { useAppSelector } from '@/mastodon/store';
|
||||||
import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
|
|
||||||
import { useAppSelector } from 'mastodon/store';
|
import { AvatarOverlay } from '../avatar_overlay';
|
||||||
|
import { DisplayName } from '../display_name';
|
||||||
|
|
||||||
export const MovedNote: React.FC<{
|
export const MovedNote: React.FC<{
|
||||||
accountId: string;
|
accountId: string;
|
||||||
@ -8,10 +8,6 @@ import classNames from 'classnames';
|
|||||||
import Overlay from 'react-overlays/esm/Overlay';
|
import Overlay from 'react-overlays/esm/Overlay';
|
||||||
|
|
||||||
import { showAlert } from '@/mastodon/actions/alerts';
|
import { showAlert } from '@/mastodon/actions/alerts';
|
||||||
import { FollowsYouBadge } from '@/mastodon/components/badge';
|
|
||||||
import { Button } from '@/mastodon/components/button';
|
|
||||||
import { DisplayName } from '@/mastodon/components/display_name';
|
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
@ -20,6 +16,11 @@ import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
|||||||
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
|
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
|
||||||
import DomainIcon from '@/material-icons/400-24px/language.svg?react';
|
import DomainIcon from '@/material-icons/400-24px/language.svg?react';
|
||||||
|
|
||||||
|
import { FollowsYouBadge } from '../badge';
|
||||||
|
import { Button } from '../button';
|
||||||
|
import { DisplayName } from '../display_name';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
import { AccountBadges } from './badges';
|
import { AccountBadges } from './badges';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
@ -5,11 +5,12 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import { fetchRelationships } from '@/mastodon/actions/accounts';
|
import { fetchRelationships } from '@/mastodon/actions/accounts';
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { Callout } from '@/mastodon/components/callout';
|
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
|
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
|
||||||
|
|
||||||
|
import { Callout } from '../callout';
|
||||||
|
import { IconButton } from '../icon_button';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -4,15 +4,13 @@ import type { FC } from 'react';
|
|||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
|
|
||||||
import {
|
|
||||||
NumberFields,
|
|
||||||
NumberFieldsItem,
|
|
||||||
} from '@/mastodon/components/number_fields';
|
|
||||||
import { ShortNumber } from '@/mastodon/components/short_number';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useAppDispatch } from '@/mastodon/store';
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
|
|
||||||
|
import { FormattedDateWrapper } from '../formatted_date';
|
||||||
|
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
||||||
|
import { ShortNumber } from '../short_number';
|
||||||
|
|
||||||
export const AccountNumberFields: FC<{ accountId: string }> = ({
|
export const AccountNumberFields: FC<{ accountId: string }> = ({
|
||||||
accountId,
|
accountId,
|
||||||
}) => {
|
}) => {
|
||||||
@ -8,16 +8,17 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
import { apiSubscribeByEmail } from 'mastodon/api/accounts';
|
import { apiSubscribeByEmail } from '@/mastodon/api/accounts';
|
||||||
import type {
|
import type {
|
||||||
ValidationErrorResponse,
|
ValidationErrorResponse,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
} from 'mastodon/api_types/errors';
|
} from '@/mastodon/api_types/errors';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { useAppSelector } from '@/mastodon/store';
|
||||||
import { DisplayName } from 'mastodon/components/display_name';
|
|
||||||
import type { FieldStatus } from 'mastodon/components/form_fields';
|
import { Button } from '../button';
|
||||||
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
import { DisplayName } from '../display_name';
|
||||||
import { useAppSelector } from 'mastodon/store';
|
import type { FieldStatus } from '../form_fields';
|
||||||
|
import { TextInputField } from '../form_fields/text_input_field';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
@ -4,10 +4,11 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import type { NavLinkProps } from 'react-router-dom';
|
import type { NavLinkProps } from 'react-router-dom';
|
||||||
|
|
||||||
import { TabLink, TabList } from '@/mastodon/components/tab_list';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
|
|
||||||
|
import { TabLink, TabList } from '../tab_list';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
||||||
@ -2,10 +2,11 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { revealAccount } from 'mastodon/actions/accounts_typed';
|
import { revealAccount } from '@/mastodon/actions/accounts_typed';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { domain } from '@/mastodon/initial_state';
|
||||||
import { domain } from 'mastodon/initial_state';
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
|
||||||
|
import { Button } from './button';
|
||||||
|
|
||||||
export const LimitedAccountHint: React.FC<{ accountId: string }> = ({
|
export const LimitedAccountHint: React.FC<{ accountId: string }> = ({
|
||||||
accountId,
|
accountId,
|
||||||
@ -8,7 +8,7 @@ import { debounce } from 'lodash';
|
|||||||
import { TIMELINE_GAP, TIMELINE_PINNED_VIEW_ALL, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
|
import { TIMELINE_GAP, TIMELINE_PINNED_VIEW_ALL, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
|
||||||
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
|
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
|
||||||
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
|
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
|
||||||
import { PinnedShowAllButton } from '@/mastodon/features/account_timeline/v2/pinned_statuses';
|
import { PinnedShowAllButton } from '@/mastodon/features/account_timeline/components/pinned_statuses';
|
||||||
|
|
||||||
import { StatusQuoteManager } from '../components/status_quoted';
|
import { StatusQuoteManager } from '../components/status_quoted';
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { Button } from '@/mastodon/components/button';
|
import { Button } from '@/mastodon/components/button';
|
||||||
import { EmptyState } from '@/mastodon/components/empty_state';
|
import { EmptyState } from '@/mastodon/components/empty_state';
|
||||||
import { LimitedAccountHint } from '@/mastodon/features/account_timeline/components/limited_account_hint';
|
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||||
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
||||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
import { useAppDispatch } from '@/mastodon/store';
|
import { useAppDispatch } from '@/mastodon/store';
|
||||||
|
|||||||
@ -6,26 +6,26 @@ import { useHistory } from 'react-router';
|
|||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
import { fetchEndorsedAccounts } from '@/mastodon/actions/accounts';
|
||||||
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
|
import { AccountHeader } from '@/mastodon/components/account_header';
|
||||||
import { AccountListItem } from 'mastodon/components/account_list_item';
|
import { AccountListItem } from '@/mastodon/components/account_list_item';
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
import { RemoteHint } from '@/mastodon/components/remote_hint';
|
||||||
import {
|
import {
|
||||||
Article,
|
Article,
|
||||||
ItemList,
|
ItemList,
|
||||||
Scrollable,
|
Scrollable,
|
||||||
} from 'mastodon/components/scrollable_list/components';
|
} from '@/mastodon/components/scrollable_list/components';
|
||||||
import type { TruncatedListItemInfo } from 'mastodon/components/truncated_list';
|
import type { TruncatedListItemInfo } from '@/mastodon/components/truncated_list';
|
||||||
import { TruncatedListItems } from 'mastodon/components/truncated_list';
|
import { TruncatedListItems } from '@/mastodon/components/truncated_list';
|
||||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import Column from '@/mastodon/features/ui/components/column';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
import { useAccountId } from 'mastodon/hooks/useAccountId';
|
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
||||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||||
|
|
||||||
import { useAccountCollections } from '../collections';
|
import { useAccountCollections } from '../collections';
|
||||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||||
|
|||||||
@ -4,23 +4,23 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import { List as ImmutableList, isList } from 'immutable';
|
import { List as ImmutableList, isList } from 'immutable';
|
||||||
|
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
|
import { expandAccountMediaTimeline } from '@/mastodon/actions/timelines';
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
import { AccountHeader } from '@/mastodon/components/account_header';
|
||||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
import { RemoteHint } from '@/mastodon/components/remote_hint';
|
||||||
import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint';
|
import ScrollableList from '@/mastodon/components/scrollable_list';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import Column from '@/mastodon/features/ui/components/column';
|
||||||
import { useAccountId } from 'mastodon/hooks/useAccountId';
|
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
|
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
||||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
import type { MediaAttachment } from '@/mastodon/models/media_attachment';
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
createAppSelector,
|
createAppSelector,
|
||||||
} from 'mastodon/store';
|
} from '@/mastodon/store';
|
||||||
|
|
||||||
import { MediaItem } from './components/media_item';
|
import { MediaItem } from './components/media_item';
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import type { AccountFieldShape } from '@/mastodon/models/account';
|
|
||||||
|
|
||||||
export interface AccountField extends AccountFieldShape {
|
|
||||||
nameHasEmojis: boolean;
|
|
||||||
value_plain: string;
|
|
||||||
valueHasEmojis: boolean;
|
|
||||||
}
|
|
||||||
@ -13,8 +13,8 @@ import { useOverflowButton } from '@/mastodon/hooks/useOverflow';
|
|||||||
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { useAccountContext } from './context';
|
import { useAccountContext } from '../hooks/useAccountContext';
|
||||||
import classes from './styles.module.scss';
|
import classes from '../styles.module.scss';
|
||||||
|
|
||||||
export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
// Fetch tags.
|
// Fetch tags.
|
||||||
@ -7,14 +7,13 @@ import { useParams } from 'react-router';
|
|||||||
|
|
||||||
import Overlay from 'react-overlays/esm/Overlay';
|
import Overlay from 'react-overlays/esm/Overlay';
|
||||||
|
|
||||||
|
import { AccountTabs } from '@/mastodon/components/account_header/tabs';
|
||||||
import { Toggle } from '@/mastodon/components/form_fields';
|
import { Toggle } from '@/mastodon/components/form_fields';
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
import { Icon } from '@/mastodon/components/icon';
|
||||||
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
||||||
|
|
||||||
import { AccountTabs } from '../components/tabs';
|
import { useAccountContext } from '../hooks/useAccountContext';
|
||||||
|
import classes from '../styles.module.scss';
|
||||||
import { useAccountContext } from './context';
|
|
||||||
import classes from './styles.module.scss';
|
|
||||||
|
|
||||||
export const AccountFilters: FC = () => {
|
export const AccountFilters: FC = () => {
|
||||||
const { acct } = useParams<{ acct: string }>();
|
const { acct } = useParams<{ acct: string }>();
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import type { Relationship } from '@/mastodon/models/relationship';
|
|
||||||
|
|
||||||
export const AccountInfo: FC<{ relationship?: Relationship }> = ({
|
|
||||||
relationship,
|
|
||||||
}) => {
|
|
||||||
if (!relationship) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='account__header__info'>
|
|
||||||
{(relationship.followed_by || relationship.requested_by) && (
|
|
||||||
<span className='relationship-tag'>
|
|
||||||
<AccountInfoFollower relationship={relationship} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{relationship.blocking && (
|
|
||||||
<span className='relationship-tag'>
|
|
||||||
<FormattedMessage id='account.blocking' defaultMessage='Blocking' />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{relationship.muting && (
|
|
||||||
<span key='muting' className='relationship-tag'>
|
|
||||||
<FormattedMessage id='account.muting' defaultMessage='Muting' />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{relationship.domain_blocking && (
|
|
||||||
<span key='domain_blocking' className='relationship-tag'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.domain_blocking'
|
|
||||||
defaultMessage='Blocking domain'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountInfoFollower: FC<{ relationship: Relationship }> = ({
|
|
||||||
relationship,
|
|
||||||
}) => {
|
|
||||||
if (
|
|
||||||
relationship.followed_by &&
|
|
||||||
(relationship.following || relationship.requested)
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.mutual'
|
|
||||||
defaultMessage='You follow each other'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (relationship.followed_by) {
|
|
||||||
return (
|
|
||||||
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
|
|
||||||
);
|
|
||||||
} else if (relationship.requested_by) {
|
|
||||||
return (
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.requests_to_follow_you'
|
|
||||||
defaultMessage='Requests to follow you'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import IconPinned from '@/images/icons/icon_pinned.svg?react';
|
||||||
|
import { Badge } from '@/mastodon/components/badge';
|
||||||
|
import { Button } from '@/mastodon/components/button';
|
||||||
|
import { Icon } from '@/mastodon/components/icon';
|
||||||
|
import { StatusHeader } from '@/mastodon/components/status/header';
|
||||||
|
import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header';
|
||||||
|
|
||||||
|
import { useAccountContext } from '../hooks/useAccountContext';
|
||||||
|
import classes from '../styles.module.scss';
|
||||||
|
|
||||||
|
export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
|
||||||
|
featured,
|
||||||
|
...args
|
||||||
|
}) => {
|
||||||
|
if (!featured) {
|
||||||
|
return <StatusHeader {...args} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<StatusHeader {...args} className={classes.pinnedStatusHeader}>
|
||||||
|
<Badge
|
||||||
|
className={classes.pinnedBadge}
|
||||||
|
icon={<Icon id='pinned' icon={IconPinned} />}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.timeline.pinned'
|
||||||
|
defaultMessage='Pinned'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StatusHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PinnedShowAllButton: FC = () => {
|
||||||
|
const { onShowAllPinned } = useAccountContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={onShowAllPinned}
|
||||||
|
className={classNames(classes.pinnedViewAllButton, 'focusable')}
|
||||||
|
>
|
||||||
|
<Icon id='pinned' icon={IconPinned} />
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.timeline.pinned.view_all'
|
||||||
|
defaultMessage='View all pinned posts'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
createAppSelector,
|
createAppSelector,
|
||||||
} from '@/mastodon/store';
|
} from '@/mastodon/store';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from '../styles.module.scss';
|
||||||
|
|
||||||
const MAX_SUGGESTED_TAGS = 3;
|
const MAX_SUGGESTED_TAGS = 3;
|
||||||
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -10,7 +9,7 @@ import {
|
|||||||
import { useStorageState } from '@/mastodon/hooks/useStorage';
|
import { useStorageState } from '@/mastodon/hooks/useStorage';
|
||||||
|
|
||||||
interface AccountTimelineContextValue {
|
interface AccountTimelineContextValue {
|
||||||
accountId: string;
|
accountId: string | null;
|
||||||
boosts: boolean;
|
boosts: boolean;
|
||||||
replies: boolean;
|
replies: boolean;
|
||||||
showAllPinned: boolean;
|
showAllPinned: boolean;
|
||||||
@ -19,13 +18,20 @@ interface AccountTimelineContextValue {
|
|||||||
onShowAllPinned: () => void;
|
onShowAllPinned: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountTimelineContext =
|
export const AccountTimelineContext =
|
||||||
createContext<AccountTimelineContextValue | null>(null);
|
createContext<AccountTimelineContextValue | null>(null);
|
||||||
|
|
||||||
export const AccountTimelineProvider: FC<{
|
export function useAccountContext() {
|
||||||
accountId: string;
|
const values = useContext(AccountTimelineContext);
|
||||||
children: ReactNode;
|
if (!values) {
|
||||||
}> = ({ accountId, children }) => {
|
throw new Error(
|
||||||
|
'useAccountFilters must be used within an AccountTimelineProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAccountContextValue = (accountId?: string | null) => {
|
||||||
const storageOptions = {
|
const storageOptions = {
|
||||||
type: 'local',
|
type: 'local',
|
||||||
prefix: 'account-filters',
|
prefix: 'account-filters',
|
||||||
@ -62,9 +68,9 @@ export const AccountTimelineProvider: FC<{
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Memoize the context value to avoid unnecessary re-renders.
|
// Memoize the context value to avoid unnecessary re-renders.
|
||||||
const value = useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
accountId,
|
accountId: accountId ?? null,
|
||||||
boosts,
|
boosts,
|
||||||
replies,
|
replies,
|
||||||
showAllPinned,
|
showAllPinned,
|
||||||
@ -82,20 +88,4 @@ export const AccountTimelineProvider: FC<{
|
|||||||
showAllPinned,
|
showAllPinned,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
|
||||||
<AccountTimelineContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</AccountTimelineContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useAccountContext() {
|
|
||||||
const values = useContext(AccountTimelineContext);
|
|
||||||
if (!values) {
|
|
||||||
throw new Error(
|
|
||||||
'useAccountFilters must be used within an AccountTimelineProvider',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { TIMELINE_PINNED_VIEW_ALL } from '@/mastodon/actions/timelines';
|
||||||
|
import {
|
||||||
|
expandTimelineByKey,
|
||||||
|
timelineKey,
|
||||||
|
} from '@/mastodon/actions/timelines_typed';
|
||||||
|
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
|
import { useAccountContext } from './useAccountContext';
|
||||||
|
|
||||||
|
export function usePinnedStatusIds({
|
||||||
|
accountId,
|
||||||
|
tagged,
|
||||||
|
forceEmptyState = false,
|
||||||
|
}: {
|
||||||
|
accountId: string;
|
||||||
|
tagged?: string;
|
||||||
|
forceEmptyState?: boolean;
|
||||||
|
}) {
|
||||||
|
const pinnedKey = timelineKey({
|
||||||
|
type: 'account',
|
||||||
|
userId: accountId,
|
||||||
|
tagged,
|
||||||
|
pinned: true,
|
||||||
|
replies: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(expandTimelineByKey({ key: pinnedKey }));
|
||||||
|
}, [dispatch, pinnedKey]);
|
||||||
|
|
||||||
|
const pinnedTimeline = useAppSelector((state) =>
|
||||||
|
selectTimelineByKey(state, pinnedKey),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { showAllPinned } = useAccountContext();
|
||||||
|
|
||||||
|
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
|
||||||
|
const pinnedStatusIds = useMemo(() => {
|
||||||
|
if (!pinnedTimelineItems || forceEmptyState) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pinnedTimelineItems.size <= 1 || showAllPinned) {
|
||||||
|
return pinnedTimelineItems;
|
||||||
|
}
|
||||||
|
return pinnedTimelineItems.slice(0, 1).push(TIMELINE_PINNED_VIEW_ALL);
|
||||||
|
}, [forceEmptyState, pinnedTimelineItems, showAllPinned]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusIds: pinnedStatusIds,
|
||||||
|
isLoading: !!pinnedTimeline?.isLoading,
|
||||||
|
showAllPinned,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,197 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
|
||||||
import { me } from 'mastodon/initial_state';
|
|
||||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
|
||||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
|
||||||
|
|
||||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
|
||||||
import { ColumnBackButton } from '../../components/column_back_button';
|
|
||||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
|
||||||
import StatusList from '../../components/status_list';
|
|
||||||
import Column from '../ui/components/column';
|
|
||||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
|
||||||
|
|
||||||
import { AccountHeader } from './components/account_header';
|
|
||||||
import { LimitedAccountHint } from './components/limited_account_hint';
|
|
||||||
import { FeaturedCarousel } from '@/mastodon/components/featured_carousel';
|
|
||||||
|
|
||||||
const emptyList = ImmutableList();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => {
|
|
||||||
const accountId = id || state.accounts_map[normalizeForLookup(acct)];
|
|
||||||
|
|
||||||
if (accountId === null) {
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
isAccount: false,
|
|
||||||
statusIds: emptyList,
|
|
||||||
};
|
|
||||||
} else if (!accountId) {
|
|
||||||
return {
|
|
||||||
isLoading: true,
|
|
||||||
statusIds: emptyList,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = withReplies ? `${accountId}:with_replies` : `${accountId}${tagged ? `:${tagged}` : ''}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
accountId,
|
|
||||||
isAccount: !!state.getIn(['accounts', accountId]),
|
|
||||||
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList),
|
|
||||||
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
|
|
||||||
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
|
|
||||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
|
||||||
hidden: getAccountHidden(state, accountId),
|
|
||||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class AccountTimeline extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
params: PropTypes.shape({
|
|
||||||
acct: PropTypes.string,
|
|
||||||
id: PropTypes.string,
|
|
||||||
tagged: PropTypes.string,
|
|
||||||
}).isRequired,
|
|
||||||
accountId: PropTypes.string,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
statusIds: ImmutablePropTypes.list,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
hasMore: PropTypes.bool,
|
|
||||||
withReplies: PropTypes.bool,
|
|
||||||
blockedBy: PropTypes.bool,
|
|
||||||
isAccount: PropTypes.bool,
|
|
||||||
suspended: PropTypes.bool,
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
multiColumn: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
_load () {
|
|
||||||
const { accountId, withReplies, params: { tagged }, dispatch } = this.props;
|
|
||||||
|
|
||||||
dispatch(fetchAccount(accountId));
|
|
||||||
|
|
||||||
if (!withReplies) {
|
|
||||||
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
|
||||||
|
|
||||||
if (accountId === me) {
|
|
||||||
dispatch(connectTimeline(`account:${me}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const { params: { acct }, accountId, dispatch } = this.props;
|
|
||||||
|
|
||||||
if (accountId) {
|
|
||||||
this._load();
|
|
||||||
} else {
|
|
||||||
dispatch(lookupAccount(acct));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
const { params: { acct, tagged }, accountId, withReplies, dispatch } = this.props;
|
|
||||||
|
|
||||||
if (prevProps.accountId !== accountId && accountId) {
|
|
||||||
this._load();
|
|
||||||
} else if (prevProps.params.acct !== acct) {
|
|
||||||
dispatch(lookupAccount(acct));
|
|
||||||
} else if (prevProps.params.tagged !== tagged) {
|
|
||||||
if (!withReplies) {
|
|
||||||
dispatch(expandAccountFeaturedTimeline(accountId, { tagged }));
|
|
||||||
}
|
|
||||||
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevProps.accountId === me && accountId !== me) {
|
|
||||||
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
const { dispatch, accountId } = this.props;
|
|
||||||
|
|
||||||
if (accountId === me) {
|
|
||||||
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
|
||||||
this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged }));
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl, params: { tagged } } = this.props;
|
|
||||||
|
|
||||||
if (isLoading && statusIds.isEmpty()) {
|
|
||||||
return (
|
|
||||||
<Column>
|
|
||||||
<LoadingIndicator />
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
} else if (!isLoading && !isAccount) {
|
|
||||||
return (
|
|
||||||
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let emptyMessage;
|
|
||||||
|
|
||||||
const forceEmptyState = suspended || blockedBy || hidden;
|
|
||||||
|
|
||||||
if (suspended) {
|
|
||||||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
|
||||||
} else if (hidden) {
|
|
||||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
|
||||||
} else if (blockedBy) {
|
|
||||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
|
||||||
} else if (remote && statusIds.isEmpty()) {
|
|
||||||
emptyMessage = <RemoteHint accountId={accountId} url={remoteUrl} />;
|
|
||||||
} else {
|
|
||||||
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts found' />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column>
|
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<StatusList
|
|
||||||
prepend={
|
|
||||||
<>
|
|
||||||
<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={tagged} />
|
|
||||||
{!forceEmptyState && <FeaturedCarousel accountId={this.props.accountId} tagged={tagged} />}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
alwaysPrepend
|
|
||||||
append={<RemoteHint accountId={accountId} />}
|
|
||||||
scrollKey='account_timeline'
|
|
||||||
statusIds={forceEmptyState ? emptyList : statusIds}
|
|
||||||
isLoading={isLoading}
|
|
||||||
hasMore={!forceEmptyState && hasMore}
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
emptyMessage={emptyMessage}
|
|
||||||
bindToDocument={!multiColumn}
|
|
||||||
timelineId='account'
|
|
||||||
withCounters
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(AccountTimeline);
|
|
||||||
@ -12,8 +12,10 @@ import {
|
|||||||
expandTimelineByKey,
|
expandTimelineByKey,
|
||||||
timelineKey,
|
timelineKey,
|
||||||
} from '@/mastodon/actions/timelines_typed';
|
} from '@/mastodon/actions/timelines_typed';
|
||||||
|
import { AccountHeader } from '@/mastodon/components/account_header';
|
||||||
import { Column } from '@/mastodon/components/column';
|
import { Column } from '@/mastodon/components/column';
|
||||||
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
|
import { ColumnBackButton } from '@/mastodon/components/column_back_button';
|
||||||
|
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
import { RemoteHint } from '@/mastodon/components/remote_hint';
|
import { RemoteHint } from '@/mastodon/components/remote_hint';
|
||||||
import StatusList from '@/mastodon/components/status_list';
|
import StatusList from '@/mastodon/components/status_list';
|
||||||
@ -26,23 +28,23 @@ import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
|||||||
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
|
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { AccountHeader } from '../components/account_header';
|
import { FeaturedTags } from './components/featured_tags';
|
||||||
import { LimitedAccountHint } from '../components/limited_account_hint';
|
import { AccountFilters } from './components/filters';
|
||||||
|
import { renderPinnedStatusHeader } from './components/pinned_statuses';
|
||||||
import { AccountTimelineProvider, useAccountContext } from './context';
|
import { TagSuggestions } from './components/tags_suggestions';
|
||||||
import { FeaturedTags } from './featured_tags';
|
|
||||||
import { AccountFilters } from './filters';
|
|
||||||
import {
|
import {
|
||||||
renderPinnedStatusHeader,
|
AccountTimelineContext,
|
||||||
usePinnedStatusIds,
|
useAccountContext,
|
||||||
} from './pinned_statuses';
|
useAccountContextValue,
|
||||||
|
} from './hooks/useAccountContext';
|
||||||
|
import { usePinnedStatusIds } from './hooks/usePinned';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
import { TagSuggestions } from './tags_suggestions';
|
|
||||||
|
|
||||||
const emptyList = ImmutableList<string>();
|
const emptyList = ImmutableList<string>();
|
||||||
|
|
||||||
const AccountTimelineV2: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
const AccountTimeline: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||||
const accountId = useAccountId();
|
const accountId = useAccountId();
|
||||||
|
const accountContext = useAccountContextValue(accountId);
|
||||||
|
|
||||||
// Null means accountId does not exist (e.g. invalid acct). Undefined means loading.
|
// Null means accountId does not exist (e.g. invalid acct). Undefined means loading.
|
||||||
if (accountId === null) {
|
if (accountId === null) {
|
||||||
@ -59,13 +61,13 @@ const AccountTimelineV2: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
|||||||
|
|
||||||
// Add this key to remount the timeline when accountId changes.
|
// Add this key to remount the timeline when accountId changes.
|
||||||
return (
|
return (
|
||||||
<AccountTimelineProvider accountId={accountId}>
|
<AccountTimelineContext.Provider value={accountContext}>
|
||||||
<InnerTimeline
|
<InnerTimeline
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
key={accountId}
|
key={accountId}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
/>
|
/>
|
||||||
</AccountTimelineProvider>
|
</AccountTimelineContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -183,4 +185,4 @@ const EmptyMessage: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default AccountTimelineV2;
|
export default AccountTimeline;
|
||||||
@ -2,6 +2,7 @@ import type { FC } from 'react';
|
|||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import type { AccountField } from '@/mastodon/components/account_header/fields';
|
||||||
import { Button } from '@/mastodon/components/button';
|
import { Button } from '@/mastodon/components/button';
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import {
|
import {
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
ModalShellBody,
|
ModalShellBody,
|
||||||
} from '@/mastodon/components/modal_shell';
|
} from '@/mastodon/components/modal_shell';
|
||||||
|
|
||||||
import type { AccountField } from '../common';
|
|
||||||
import { useFieldHtml } from '../hooks/useFieldHtml';
|
import { useFieldHtml } from '../hooks/useFieldHtml';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|||||||
@ -1,110 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import IconPinned from '@/images/icons/icon_pinned.svg?react';
|
|
||||||
import { TIMELINE_PINNED_VIEW_ALL } from '@/mastodon/actions/timelines';
|
|
||||||
import {
|
|
||||||
expandTimelineByKey,
|
|
||||||
timelineKey,
|
|
||||||
} from '@/mastodon/actions/timelines_typed';
|
|
||||||
import { Badge } from '@/mastodon/components/badge';
|
|
||||||
import { Button } from '@/mastodon/components/button';
|
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
|
||||||
import { StatusHeader } from '@/mastodon/components/status/header';
|
|
||||||
import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header';
|
|
||||||
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
|
||||||
|
|
||||||
import { useAccountContext } from './context';
|
|
||||||
import classes from './styles.module.scss';
|
|
||||||
|
|
||||||
export function usePinnedStatusIds({
|
|
||||||
accountId,
|
|
||||||
tagged,
|
|
||||||
forceEmptyState = false,
|
|
||||||
}: {
|
|
||||||
accountId: string;
|
|
||||||
tagged?: string;
|
|
||||||
forceEmptyState?: boolean;
|
|
||||||
}) {
|
|
||||||
const pinnedKey = timelineKey({
|
|
||||||
type: 'account',
|
|
||||||
userId: accountId,
|
|
||||||
tagged,
|
|
||||||
pinned: true,
|
|
||||||
replies: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(expandTimelineByKey({ key: pinnedKey }));
|
|
||||||
}, [dispatch, pinnedKey]);
|
|
||||||
|
|
||||||
const pinnedTimeline = useAppSelector((state) =>
|
|
||||||
selectTimelineByKey(state, pinnedKey),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { showAllPinned } = useAccountContext();
|
|
||||||
|
|
||||||
const pinnedTimelineItems = pinnedTimeline?.items; // Make a const to avoid the React Compiler complaining.
|
|
||||||
const pinnedStatusIds = useMemo(() => {
|
|
||||||
if (!pinnedTimelineItems || forceEmptyState) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pinnedTimelineItems.size <= 1 || showAllPinned) {
|
|
||||||
return pinnedTimelineItems;
|
|
||||||
}
|
|
||||||
return pinnedTimelineItems.slice(0, 1).push(TIMELINE_PINNED_VIEW_ALL);
|
|
||||||
}, [forceEmptyState, pinnedTimelineItems, showAllPinned]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusIds: pinnedStatusIds,
|
|
||||||
isLoading: !!pinnedTimeline?.isLoading,
|
|
||||||
showAllPinned,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
|
|
||||||
featured,
|
|
||||||
...args
|
|
||||||
}) => {
|
|
||||||
if (!featured) {
|
|
||||||
return <StatusHeader {...args} />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<StatusHeader {...args} className={classes.pinnedStatusHeader}>
|
|
||||||
<Badge
|
|
||||||
className={classes.pinnedBadge}
|
|
||||||
icon={<Icon id='pinned' icon={IconPinned} />}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.timeline.pinned'
|
|
||||||
defaultMessage='Pinned'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StatusHeader>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PinnedShowAllButton: FC = () => {
|
|
||||||
const { onShowAllPinned } = useAccountContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={onShowAllPinned}
|
|
||||||
className={classNames(classes.pinnedViewAllButton, 'focusable')}
|
|
||||||
>
|
|
||||||
<Icon id='pinned' icon={IconPinned} />
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.timeline.pinned.view_all'
|
|
||||||
defaultMessage='View all pinned posts'
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { RelativeTimestamp } from '@/mastodon/components/relative_timestamp';
|
|
||||||
import type { StatusHeaderProps } from '@/mastodon/components/status/header';
|
|
||||||
import {
|
|
||||||
StatusDisplayName,
|
|
||||||
StatusEditedAt,
|
|
||||||
StatusVisibility,
|
|
||||||
} from '@/mastodon/components/status/header';
|
|
||||||
import type { Account } from '@/mastodon/models/account';
|
|
||||||
|
|
||||||
export const AccountStatusHeader: FC<StatusHeaderProps> = ({
|
|
||||||
status,
|
|
||||||
account,
|
|
||||||
children,
|
|
||||||
avatarSize = 48,
|
|
||||||
wrapperProps,
|
|
||||||
onHeaderClick,
|
|
||||||
}) => {
|
|
||||||
const statusAccount = status.get('account') as Account | undefined;
|
|
||||||
const editedAt = status.get('edited_at') as string;
|
|
||||||
|
|
||||||
return (
|
|
||||||
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
|
|
||||||
<div
|
|
||||||
onClick={onHeaderClick}
|
|
||||||
onAuxClick={onHeaderClick}
|
|
||||||
{...wrapperProps}
|
|
||||||
className='status__info'
|
|
||||||
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
to={`/@${statusAccount?.acct}/${status.get('id') as string}`}
|
|
||||||
className='status__relative-time'
|
|
||||||
>
|
|
||||||
<StatusVisibility visibility={status.get('visibility')} />
|
|
||||||
<RelativeTimestamp timestamp={status.get('created_at') as string} />
|
|
||||||
{editedAt && <StatusEditedAt editedAt={editedAt} />}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<StatusDisplayName
|
|
||||||
statusAccount={statusAccount}
|
|
||||||
friendAccount={account}
|
|
||||||
avatarSize={avatarSize}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -2,7 +2,7 @@ import type { FC, ReactNode } from 'react';
|
|||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { LimitedAccountHint } from '@/mastodon/features/account_timeline/components/limited_account_hint';
|
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||||
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
||||||
import type { Account } from '@/mastodon/models/account';
|
import type { Account } from '@/mastodon/models/account';
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export function PinnedStatuses () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AccountTimeline () {
|
export function AccountTimeline () {
|
||||||
return import('../../account_timeline/v2');
|
return import('../../account_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountGallery () {
|
export function AccountGallery () {
|
||||||
|
|||||||
@ -28,12 +28,10 @@
|
|||||||
"account.block_domain": "Block domain {domain}",
|
"account.block_domain": "Block domain {domain}",
|
||||||
"account.block_short": "Block",
|
"account.block_short": "Block",
|
||||||
"account.blocked": "Blocked",
|
"account.blocked": "Blocked",
|
||||||
"account.blocking": "Blocking",
|
|
||||||
"account.cancel_follow_request": "Cancel follow",
|
"account.cancel_follow_request": "Cancel follow",
|
||||||
"account.copy": "Copy link to profile",
|
"account.copy": "Copy link to profile",
|
||||||
"account.direct": "Privately mention @{name}",
|
"account.direct": "Privately mention @{name}",
|
||||||
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
||||||
"account.domain_blocking": "Blocking domain",
|
|
||||||
"account.edit_note": "Edit personal note",
|
"account.edit_note": "Edit personal note",
|
||||||
"account.edit_profile": "Edit profile",
|
"account.edit_profile": "Edit profile",
|
||||||
"account.edit_profile_short": "Edit",
|
"account.edit_profile_short": "Edit",
|
||||||
@ -111,7 +109,6 @@
|
|||||||
"account.mute_notifications_short": "Mute notifications",
|
"account.mute_notifications_short": "Mute notifications",
|
||||||
"account.mute_short": "Mute",
|
"account.mute_short": "Mute",
|
||||||
"account.muted": "Muted",
|
"account.muted": "Muted",
|
||||||
"account.muting": "Muting",
|
|
||||||
"account.mutual": "You follow each other",
|
"account.mutual": "You follow each other",
|
||||||
"account.name.copy": "Copy handle",
|
"account.name.copy": "Copy handle",
|
||||||
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
|
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
|
||||||
@ -136,7 +133,6 @@
|
|||||||
"account.remove_from_followers": "Remove {name} from followers",
|
"account.remove_from_followers": "Remove {name} from followers",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested_follow": "{name} has requested to follow you",
|
"account.requested_follow": "{name} has requested to follow you",
|
||||||
"account.requests_to_follow_you": "Requests to follow you",
|
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.show_reblogs": "Show boosts from @{name}",
|
"account.show_reblogs": "Show boosts from @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user