mirror of
https://github.com/mastodon/mastodon.git
synced 2026-03-21 18:05:23 -05:00
Profile redesign: Profile tab settings (#38309)
This commit is contained in:
parent
00bcb014df
commit
6507a61d30
|
|
@ -158,7 +158,7 @@ export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } =
|
|||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia });
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withReplies, tagged, max_id: maxId });
|
||||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}:media${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40, exclude_replies: !withReplies });
|
||||
export const expandListTimeline = (id, { maxId } = {}) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId });
|
||||
export const expandLinkTimeline = (url, { maxId } = {}) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId });
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}) => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ export interface BaseApiAccountJSON {
|
|||
id: string;
|
||||
last_status_at: string;
|
||||
locked: boolean;
|
||||
show_media: boolean;
|
||||
show_media_replies: boolean;
|
||||
show_featured: boolean;
|
||||
noindex?: boolean;
|
||||
note: string;
|
||||
roles?: ApiAccountJSON[];
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import { useEffect } from 'react';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
|
||||
import { fetchFeaturedTags } from 'mastodon/actions/featured_tags';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
|
|
@ -35,21 +37,27 @@ import { EmptyMessage } from './components/empty_message';
|
|||
import { FeaturedTag } from './components/featured_tag';
|
||||
import type { TagMap } from './components/featured_tag';
|
||||
|
||||
interface Params {
|
||||
acct?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
}) => {
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
const forceEmptyState = suspended || blockedBy || hidden;
|
||||
const { acct = '' } = useParams<Params>();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const history = useHistory();
|
||||
useEffect(() => {
|
||||
if (
|
||||
account &&
|
||||
!account.show_featured &&
|
||||
isServerFeatureEnabled('profile_redesign')
|
||||
) {
|
||||
history.push(`/@${account.acct}`);
|
||||
}
|
||||
}, [account, history]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId) {
|
||||
void dispatch(fetchFeaturedTags({ accountId }));
|
||||
|
|
@ -166,7 +174,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||
aria-posinset={index + 1}
|
||||
aria-setsize={featuredTags.size}
|
||||
>
|
||||
<FeaturedTag tag={tag} account={acct} />
|
||||
<FeaturedTag tag={tag} account={account?.acct ?? ''} />
|
||||
</Article>
|
||||
))}
|
||||
</ItemList>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ import { useEffect, useCallback } from 'react';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { List as ImmutableList, isList } from 'immutable';
|
||||
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
|
|
@ -18,38 +17,69 @@ import Column from 'mastodon/features/ui/components/column';
|
|||
import { useAccountId } from 'mastodon/hooks/useAccountId';
|
||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import {
|
||||
useAppSelector,
|
||||
useAppDispatch,
|
||||
createAppSelector,
|
||||
} from 'mastodon/store';
|
||||
|
||||
import { MediaItem } from './components/media_item';
|
||||
|
||||
const getAccountGallery = createSelector(
|
||||
const emptyList = ImmutableList<MediaAttachment>();
|
||||
|
||||
const redesignEnabled = isServerFeatureEnabled('profile_redesign');
|
||||
|
||||
const selectGalleryTimeline = createAppSelector(
|
||||
[
|
||||
(state: RootState, accountId: string) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn(
|
||||
[`account:${accountId}:media`, 'items'],
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
(state: RootState) => state.statuses,
|
||||
(_state, accountId?: string | null) => accountId,
|
||||
(state) => state.timelines,
|
||||
(state) => state.accounts,
|
||||
(state) => state.statuses,
|
||||
],
|
||||
(statusIds, statuses) => {
|
||||
let items = ImmutableList<MediaAttachment>();
|
||||
(accountId, timelines, accounts, statuses) => {
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
const account = accounts.get(accountId);
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
statusIds.forEach((statusId) => {
|
||||
const status = statuses.get(statusId) as
|
||||
| ImmutableMap<string, unknown>
|
||||
| undefined;
|
||||
let items = emptyList;
|
||||
const { show_media, show_media_replies } = account;
|
||||
// If the account disabled showing media, don't display anything.
|
||||
if (!show_media && redesignEnabled) {
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
showingReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (status) {
|
||||
const showingReplies = show_media_replies && redesignEnabled;
|
||||
const timeline = timelines.get(
|
||||
`account:${accountId}:media${showingReplies ? ':with_replies' : ''}`,
|
||||
);
|
||||
const statusIds = timeline?.get('items');
|
||||
|
||||
if (isList(statusIds)) {
|
||||
for (const statusId of statusIds) {
|
||||
const status = statuses.get(statusId);
|
||||
items = items.concat(
|
||||
(
|
||||
status.get('media_attachments') as ImmutableList<MediaAttachment>
|
||||
status?.get('media_attachments') as ImmutableList<MediaAttachment>
|
||||
).map((media) => media.set('status', status)),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
return {
|
||||
items,
|
||||
hasMore: !!timeline?.get('hasMore'),
|
||||
isLoading: !!timeline?.get('isLoading'),
|
||||
showingReplies,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -58,27 +88,12 @@ export const AccountGallery: React.FC<{
|
|||
}> = ({ multiColumn }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useAccountId();
|
||||
const attachments = useAppSelector((state) =>
|
||||
accountId
|
||||
? getAccountGallery(state, accountId)
|
||||
: ImmutableList<MediaAttachment>(),
|
||||
);
|
||||
const isLoading = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'isLoading',
|
||||
]),
|
||||
);
|
||||
const hasMore = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'hasMore',
|
||||
]),
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
);
|
||||
const isAccount = !!account;
|
||||
const {
|
||||
isLoading = true,
|
||||
hasMore = false,
|
||||
items: attachments = emptyList,
|
||||
showingReplies: withReplies = false,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId)) ?? {};
|
||||
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
|
||||
|
|
@ -87,16 +102,18 @@ export const AccountGallery: React.FC<{
|
|||
| undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && isAccount) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId));
|
||||
if (accountId) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId, { withReplies }));
|
||||
}
|
||||
}, [dispatch, accountId, isAccount]);
|
||||
}, [dispatch, accountId, withReplies]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (maxId) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId, { maxId }));
|
||||
void dispatch(
|
||||
expandAccountMediaTimeline(accountId, { maxId, withReplies }),
|
||||
);
|
||||
}
|
||||
}, [dispatch, accountId, maxId]);
|
||||
}, [maxId, dispatch, accountId, withReplies]);
|
||||
|
||||
const handleOpenMedia = useCallback(
|
||||
(attachment: MediaAttachment) => {
|
||||
|
|
|
|||
|
|
@ -5,25 +5,16 @@ import { FormattedMessage } from 'react-intl';
|
|||
import type { NavLinkProps } from 'react-router-dom';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
|
||||
import { isRedesignEnabled } from '../common';
|
||||
|
||||
import classes from './redesign.module.scss';
|
||||
|
||||
export const AccountTabs: FC<{ acct: string }> = ({ acct }) => {
|
||||
if (isRedesignEnabled()) {
|
||||
return (
|
||||
<div className={classes.tabs}>
|
||||
<NavLink isActive={isActive} to={`/@${acct}`}>
|
||||
<FormattedMessage id='account.activity' defaultMessage='Activity' />
|
||||
</NavLink>
|
||||
<NavLink exact to={`/@${acct}/media`}>
|
||||
<FormattedMessage id='account.media' defaultMessage='Media' />
|
||||
</NavLink>
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
return <RedesignTabs />;
|
||||
}
|
||||
return (
|
||||
<div className='account__section-headline'>
|
||||
|
|
@ -49,3 +40,32 @@ export const AccountTabs: FC<{ acct: string }> = ({ acct }) => {
|
|||
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
||||
match?.url === location.pathname ||
|
||||
(!!match?.url && location.pathname.startsWith(`${match.url}/tagged/`));
|
||||
|
||||
const RedesignTabs: FC = () => {
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { acct, show_featured, show_media } = account;
|
||||
|
||||
return (
|
||||
<div className={classes.tabs}>
|
||||
<NavLink isActive={isActive} to={`/@${acct}`}>
|
||||
<FormattedMessage id='account.activity' defaultMessage='Activity' />
|
||||
</NavLink>
|
||||
{show_media && (
|
||||
<NavLink exact to={`/@${acct}/media`}>
|
||||
<FormattedMessage id='account.media' defaultMessage='Media' />
|
||||
</NavLink>
|
||||
)}
|
||||
{show_featured && (
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ export const LinkTimeline: React.FC<{
|
|||
const columnRef = useRef<ColumnRef>(null);
|
||||
const firstStatusId = useAppSelector((state) =>
|
||||
decodedUrl
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
(state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string)
|
||||
? (state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string)
|
||||
: undefined,
|
||||
);
|
||||
const story = useAppSelector((state) =>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ export const accountDefaultValues: AccountShape = {
|
|||
last_status_at: '',
|
||||
locked: false,
|
||||
noindex: false,
|
||||
show_featured: true,
|
||||
show_media: true,
|
||||
show_media_replies: true,
|
||||
note: '',
|
||||
note_emojified: '',
|
||||
note_plain: 'string',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
} from '../actions/timelines_typed';
|
||||
import { compareId } from '../compare_id';
|
||||
|
||||
/** @type {ImmutableMap<string, typeof initialTimeline>} */
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const initialTimeline = ImmutableMap({
|
||||
|
|
@ -36,7 +37,9 @@ const initialTimeline = ImmutableMap({
|
|||
top: true,
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
/** @type {ImmutableList<string>} */
|
||||
pendingItems: ImmutableList(),
|
||||
/** @type {ImmutableList<string>} */
|
||||
items: ImmutableList(),
|
||||
});
|
||||
|
||||
|
|
@ -197,6 +200,7 @@ const reconnectTimeline = (state, usePendingItems) => {
|
|||
});
|
||||
};
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
export default function timelines(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case TIMELINE_LOAD_PENDING:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ export const accountFactory: FactoryFunction<ApiAccountJSON> = ({
|
|||
indexable: true,
|
||||
last_status_at: '2023-01-01',
|
||||
locked: false,
|
||||
show_featured: true,
|
||||
show_media: true,
|
||||
show_media_replies: true,
|
||||
mute_expires_at: null,
|
||||
note: 'This is a test user account.',
|
||||
statuses_count: 0,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user