Convert `<Directory>` to Typescript / function component (#30829)
parent
51f581e03e
commit
863c470a2b
|
@ -1,62 +0,0 @@
|
||||||
import api from '../api';
|
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
|
|
||||||
export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
|
|
||||||
export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
|
|
||||||
export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
|
|
||||||
export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
|
|
||||||
export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
|
|
||||||
|
|
||||||
export const fetchDirectory = params => (dispatch) => {
|
|
||||||
dispatch(fetchDirectoryRequest());
|
|
||||||
|
|
||||||
api().get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(fetchDirectorySuccess(data));
|
|
||||||
dispatch(fetchRelationships(data.map(x => x.id)));
|
|
||||||
}).catch(error => dispatch(fetchDirectoryFail(error)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchDirectoryRequest = () => ({
|
|
||||||
type: DIRECTORY_FETCH_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchDirectorySuccess = accounts => ({
|
|
||||||
type: DIRECTORY_FETCH_SUCCESS,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchDirectoryFail = error => ({
|
|
||||||
type: DIRECTORY_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const expandDirectory = params => (dispatch, getState) => {
|
|
||||||
dispatch(expandDirectoryRequest());
|
|
||||||
|
|
||||||
const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
|
|
||||||
|
|
||||||
api().get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(expandDirectorySuccess(data));
|
|
||||||
dispatch(fetchRelationships(data.map(x => x.id)));
|
|
||||||
}).catch(error => dispatch(expandDirectoryFail(error)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const expandDirectoryRequest = () => ({
|
|
||||||
type: DIRECTORY_EXPAND_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const expandDirectorySuccess = accounts => ({
|
|
||||||
type: DIRECTORY_EXPAND_SUCCESS,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const expandDirectoryFail = error => ({
|
|
||||||
type: DIRECTORY_EXPAND_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
|
import { apiGetDirectory } from 'mastodon/api/directory';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
|
export const fetchDirectory = createDataLoadingThunk(
|
||||||
|
'directory/fetch',
|
||||||
|
async (params: Parameters<typeof apiGetDirectory>[0]) =>
|
||||||
|
apiGetDirectory(params),
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(fetchRelationships(data.map((x) => x.id)));
|
||||||
|
|
||||||
|
return { accounts: data };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const expandDirectory = createDataLoadingThunk(
|
||||||
|
'directory/expand',
|
||||||
|
async (params: Parameters<typeof apiGetDirectory>[0], { getState }) => {
|
||||||
|
const loadedItems = getState().user_lists.getIn([
|
||||||
|
'directory',
|
||||||
|
'items',
|
||||||
|
]) as ImmutableList<unknown>;
|
||||||
|
|
||||||
|
return apiGetDirectory({ ...params, offset: loadedItems.size }, 20);
|
||||||
|
},
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(fetchRelationships(data.map((x) => x.id)));
|
||||||
|
|
||||||
|
return { accounts: data };
|
||||||
|
},
|
||||||
|
);
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { apiRequestGet } from 'mastodon/api';
|
||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
|
||||||
|
export const apiGetDirectory = (
|
||||||
|
params: {
|
||||||
|
order: string;
|
||||||
|
local: boolean;
|
||||||
|
offset?: number;
|
||||||
|
},
|
||||||
|
limit = 20,
|
||||||
|
) =>
|
||||||
|
apiRequestGet<ApiAccountJSON[]>('v1/directory', {
|
||||||
|
...params,
|
||||||
|
limit,
|
||||||
|
});
|
|
@ -1,234 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
followAccount,
|
|
||||||
unfollowAccount,
|
|
||||||
unblockAccount,
|
|
||||||
unmuteAccount,
|
|
||||||
} from 'mastodon/actions/accounts';
|
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
|
||||||
import { Button } from 'mastodon/components/button';
|
|
||||||
import { DisplayName } from 'mastodon/components/display_name';
|
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
|
||||||
import { autoPlayGif, me } from 'mastodon/initial_state';
|
|
||||||
import { makeGetAccount } from 'mastodon/selectors';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
|
||||||
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
|
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
|
||||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
|
||||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
|
||||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
|
||||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { id }) => ({
|
|
||||||
account: getAccount(state, id),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
|
||||||
onFollow(account) {
|
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
|
||||||
dispatch(
|
|
||||||
openModal({
|
|
||||||
modalType: 'CONFIRM',
|
|
||||||
modalProps: {
|
|
||||||
message: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='confirmations.unfollow.message'
|
|
||||||
defaultMessage='Are you sure you want to unfollow {name}?'
|
|
||||||
values={{ name: <strong>@{account.get('acct')}</strong> }}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
confirm: intl.formatMessage(messages.unfollowConfirm),
|
|
||||||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
|
||||||
} }),
|
|
||||||
);
|
|
||||||
} else if (account.getIn(['relationship', 'requested'])) {
|
|
||||||
dispatch(openModal({
|
|
||||||
modalType: 'CONFIRM',
|
|
||||||
modalProps: {
|
|
||||||
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
|
||||||
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
|
|
||||||
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onBlock(account) {
|
|
||||||
if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
dispatch(unblockAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMute(account) {
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
dispatch(unmuteAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
class AccountCard extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.record.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
|
||||||
onBlock: PropTypes.func.isRequired,
|
|
||||||
onMute: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseEnter = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFollow = () => {
|
|
||||||
this.props.onFollow(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBlock = () => {
|
|
||||||
this.props.onBlock(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMute = () => {
|
|
||||||
this.props.onMute(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEditProfile = () => {
|
|
||||||
window.open('/settings/profile', '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account, intl } = this.props;
|
|
||||||
|
|
||||||
let actionBtn;
|
|
||||||
|
|
||||||
if (me !== account.get('id')) {
|
|
||||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
|
||||||
actionBtn = '';
|
|
||||||
} else if (account.getIn(['relationship', 'requested'])) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
|
|
||||||
} else if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
|
|
||||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
|
||||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account-card'>
|
|
||||||
<Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
|
|
||||||
<div className='account-card__header'>
|
|
||||||
<img
|
|
||||||
src={
|
|
||||||
autoPlayGif ? account.get('header') : account.get('header_static')
|
|
||||||
}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account-card__title'>
|
|
||||||
<div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{account.get('note').length > 0 && (
|
|
||||||
<div
|
|
||||||
className='account-card__bio translate'
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='account-card__actions'>
|
|
||||||
<div className='account-card__counters'>
|
|
||||||
<div className='account-card__counters__item'>
|
|
||||||
<ShortNumber value={account.get('statuses_count')} />
|
|
||||||
<small>
|
|
||||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account-card__counters__item'>
|
|
||||||
<ShortNumber value={account.get('followers_count')} />{' '}
|
|
||||||
<small>
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.followers'
|
|
||||||
defaultMessage='Followers'
|
|
||||||
/>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account-card__counters__item'>
|
|
||||||
<ShortNumber value={account.get('following_count')} />{' '}
|
|
||||||
<small>
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.following'
|
|
||||||
defaultMessage='Following'
|
|
||||||
/>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account-card__actions__button'>
|
|
||||||
{actionBtn}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(AccountCard));
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
import type { MouseEventHandler } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
followAccount,
|
||||||
|
unfollowAccount,
|
||||||
|
unblockAccount,
|
||||||
|
unmuteAccount,
|
||||||
|
} from 'mastodon/actions/accounts';
|
||||||
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
|
import { autoPlayGif, me } from 'mastodon/initial_state';
|
||||||
|
import type { Account } from 'mastodon/models/account';
|
||||||
|
import { makeGetAccount } from 'mastodon/selectors';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
|
cancel_follow_request: {
|
||||||
|
id: 'account.cancel_follow_request',
|
||||||
|
defaultMessage: 'Withdraw follow request',
|
||||||
|
},
|
||||||
|
cancelFollowRequestConfirm: {
|
||||||
|
id: 'confirmations.cancel_follow_request.confirm',
|
||||||
|
defaultMessage: 'Withdraw request',
|
||||||
|
},
|
||||||
|
requested: {
|
||||||
|
id: 'account.requested',
|
||||||
|
defaultMessage: 'Awaiting approval. Click to cancel follow request',
|
||||||
|
},
|
||||||
|
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||||
|
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||||
|
unfollowConfirm: {
|
||||||
|
id: 'confirmations.unfollow.confirm',
|
||||||
|
defaultMessage: 'Unfollow',
|
||||||
|
},
|
||||||
|
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const account = useAppSelector((s) => getAccount(s, accountId));
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback<MouseEventHandler>(
|
||||||
|
({ currentTarget }) => {
|
||||||
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emojis =
|
||||||
|
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
||||||
|
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const original = emoji.getAttribute('data-original');
|
||||||
|
if (original) emoji.src = original;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseLeave = useCallback<MouseEventHandler>(
|
||||||
|
({ currentTarget }) => {
|
||||||
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis =
|
||||||
|
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
||||||
|
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const staticUrl = emoji.getAttribute('data-static');
|
||||||
|
if (staticUrl) emoji.src = staticUrl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFollow = useCallback(() => {
|
||||||
|
if (!account) return;
|
||||||
|
|
||||||
|
if (account.getIn(['relationship', 'following'])) {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM',
|
||||||
|
modalProps: {
|
||||||
|
message: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmations.unfollow.message'
|
||||||
|
defaultMessage='Are you sure you want to unfollow {name}?'
|
||||||
|
values={{ name: <strong>@{account.get('acct')}</strong> }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
confirm: intl.formatMessage(messages.unfollowConfirm),
|
||||||
|
onConfirm: () => {
|
||||||
|
dispatch(unfollowAccount(account.get('id')));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (account.getIn(['relationship', 'requested'])) {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM',
|
||||||
|
modalProps: {
|
||||||
|
message: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmations.cancel_follow_request.message'
|
||||||
|
defaultMessage='Are you sure you want to withdraw your request to follow {name}?'
|
||||||
|
values={{ name: <strong>@{account.get('acct')}</strong> }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
|
||||||
|
onConfirm: () => {
|
||||||
|
dispatch(unfollowAccount(account.get('id')));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dispatch(followAccount(account.get('id')));
|
||||||
|
}
|
||||||
|
}, [account, dispatch, intl]);
|
||||||
|
|
||||||
|
const handleBlock = useCallback(() => {
|
||||||
|
if (account?.relationship?.blocking) {
|
||||||
|
dispatch(unblockAccount(account.get('id')));
|
||||||
|
}
|
||||||
|
}, [account, dispatch]);
|
||||||
|
|
||||||
|
const handleMute = useCallback(() => {
|
||||||
|
if (account?.relationship?.muting) {
|
||||||
|
dispatch(unmuteAccount(account.get('id')));
|
||||||
|
}
|
||||||
|
}, [account, dispatch]);
|
||||||
|
|
||||||
|
const handleEditProfile = useCallback(() => {
|
||||||
|
window.open('/settings/profile', '_blank');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
let actionBtn;
|
||||||
|
|
||||||
|
if (me !== account.get('id')) {
|
||||||
|
if (!account.get('relationship')) {
|
||||||
|
// Wait until the relationship is loaded
|
||||||
|
actionBtn = '';
|
||||||
|
} else if (account.getIn(['relationship', 'requested'])) {
|
||||||
|
actionBtn = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.cancel_follow_request)}
|
||||||
|
title={intl.formatMessage(messages.requested)}
|
||||||
|
onClick={handleFollow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
actionBtn = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.unmute)}
|
||||||
|
onClick={handleMute}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||||
|
actionBtn = (
|
||||||
|
<Button
|
||||||
|
disabled={account.relationship?.blocked_by}
|
||||||
|
className={classNames({
|
||||||
|
'button--destructive': account.getIn(['relationship', 'following']),
|
||||||
|
})}
|
||||||
|
text={intl.formatMessage(
|
||||||
|
account.getIn(['relationship', 'following'])
|
||||||
|
? messages.unfollow
|
||||||
|
: messages.follow,
|
||||||
|
)}
|
||||||
|
onClick={handleFollow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||||
|
actionBtn = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.unblock)}
|
||||||
|
onClick={handleBlock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actionBtn = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.edit_profile)}
|
||||||
|
onClick={handleEditProfile}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account-card'>
|
||||||
|
<Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
|
||||||
|
<div className='account-card__header'>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
autoPlayGif ? account.get('header') : account.get('header_static')
|
||||||
|
}
|
||||||
|
alt=''
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account-card__title'>
|
||||||
|
<div className='account-card__title__avatar'>
|
||||||
|
<Avatar account={account as Account} size={56} />
|
||||||
|
</div>
|
||||||
|
<DisplayName account={account as Account} />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{account.get('note').length > 0 && (
|
||||||
|
<div
|
||||||
|
className='account-card__bio translate'
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='account-card__actions'>
|
||||||
|
<div className='account-card__counters'>
|
||||||
|
<div className='account-card__counters__item'>
|
||||||
|
<ShortNumber value={account.get('statuses_count')} />
|
||||||
|
<small>
|
||||||
|
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account-card__counters__item'>
|
||||||
|
<ShortNumber value={account.get('followers_count')} />{' '}
|
||||||
|
<small>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.followers'
|
||||||
|
defaultMessage='Followers'
|
||||||
|
/>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account-card__counters__item'>
|
||||||
|
<ShortNumber value={account.get('following_count')} />{' '}
|
||||||
|
<small>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.following'
|
||||||
|
defaultMessage='Following'
|
||||||
|
/>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account-card__actions__button'>{actionBtn}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,181 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
|
||||||
import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodon/actions/columns';
|
|
||||||
import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
|
|
||||||
import Column from 'mastodon/components/column';
|
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
|
||||||
import { LoadMore } from 'mastodon/components/load_more';
|
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|
||||||
import { RadioButton } from 'mastodon/components/radio_button';
|
|
||||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
|
||||||
|
|
||||||
import AccountCard from './components/account_card';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
|
|
||||||
recentlyActive: { id: 'directory.recently_active', defaultMessage: 'Recently active' },
|
|
||||||
newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' },
|
|
||||||
local: { id: 'directory.local', defaultMessage: 'From {domain} only' },
|
|
||||||
federated: { id: 'directory.federated', defaultMessage: 'From known fediverse' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
|
|
||||||
isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
|
|
||||||
domain: state.getIn(['meta', 'domain']),
|
|
||||||
});
|
|
||||||
|
|
||||||
class Directory extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
accountIds: ImmutablePropTypes.list.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
columnId: PropTypes.string,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
multiColumn: PropTypes.bool,
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
params: PropTypes.shape({
|
|
||||||
order: PropTypes.string,
|
|
||||||
local: PropTypes.bool,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
order: null,
|
|
||||||
local: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePin = () => {
|
|
||||||
const { columnId, dispatch } = this.props;
|
|
||||||
|
|
||||||
if (columnId) {
|
|
||||||
dispatch(removeColumn(columnId));
|
|
||||||
} else {
|
|
||||||
dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getParams = (props, state) => ({
|
|
||||||
order: state.order === null ? (props.params.order || 'active') : state.order,
|
|
||||||
local: state.local === null ? (props.params.local || false) : state.local,
|
|
||||||
});
|
|
||||||
|
|
||||||
handleMove = dir => {
|
|
||||||
const { columnId, dispatch } = this.props;
|
|
||||||
dispatch(moveColumn(columnId, dir));
|
|
||||||
};
|
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
|
||||||
this.column.scrollTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
dispatch(fetchDirectory(this.getParams(this.props, this.state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
const paramsOld = this.getParams(prevProps, prevState);
|
|
||||||
const paramsNew = this.getParams(this.props, this.state);
|
|
||||||
|
|
||||||
if (paramsOld.order !== paramsNew.order || paramsOld.local !== paramsNew.local) {
|
|
||||||
dispatch(fetchDirectory(paramsNew));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.column = c;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChangeOrder = e => {
|
|
||||||
const { dispatch, columnId } = this.props;
|
|
||||||
|
|
||||||
if (columnId) {
|
|
||||||
dispatch(changeColumnParams(columnId, ['order'], e.target.value));
|
|
||||||
} else {
|
|
||||||
this.setState({ order: e.target.value });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChangeLocal = e => {
|
|
||||||
const { dispatch, columnId } = this.props;
|
|
||||||
|
|
||||||
if (columnId) {
|
|
||||||
dispatch(changeColumnParams(columnId, ['local'], e.target.value === '1'));
|
|
||||||
} else {
|
|
||||||
this.setState({ local: e.target.value === '1' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleLoadMore = () => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
dispatch(expandDirectory(this.getParams(this.props, this.state)));
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props;
|
|
||||||
const { order, local } = this.getParams(this.props, this.state);
|
|
||||||
const pinned = !!columnId;
|
|
||||||
|
|
||||||
const scrollableArea = (
|
|
||||||
<div className='scrollable'>
|
|
||||||
<div className='filter-form'>
|
|
||||||
<div className='filter-form__column' role='group'>
|
|
||||||
<RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
|
|
||||||
<RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === 'new'} onChange={this.handleChangeOrder} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='filter-form__column' role='group'>
|
|
||||||
<RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain })} checked={local} onChange={this.handleChangeLocal} />
|
|
||||||
<RadioButton name='local' value='0' label={intl.formatMessage(messages.federated)} checked={!local} onChange={this.handleChangeLocal} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='directory__list'>
|
|
||||||
{isLoading ? <LoadingIndicator /> : accountIds.map(accountId => (
|
|
||||||
<AccountCard id={accountId} key={accountId} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<LoadMore onClick={this.handleLoadMore} visible={!isLoading} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
|
||||||
<ColumnHeader
|
|
||||||
icon='address-book-o'
|
|
||||||
iconComponent={PeopleIcon}
|
|
||||||
title={intl.formatMessage(messages.title)}
|
|
||||||
onPin={this.handlePin}
|
|
||||||
onMove={this.handleMove}
|
|
||||||
onClick={this.handleHeaderClick}
|
|
||||||
pinned={pinned}
|
|
||||||
multiColumn={multiColumn}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea}
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages.title)}</title>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(injectIntl(Directory));
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
import type { ChangeEventHandler } from 'react';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
|
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||||
|
import {
|
||||||
|
addColumn,
|
||||||
|
removeColumn,
|
||||||
|
moveColumn,
|
||||||
|
changeColumnParams,
|
||||||
|
} from 'mastodon/actions/columns';
|
||||||
|
import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
|
import { LoadMore } from 'mastodon/components/load_more';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import { RadioButton } from 'mastodon/components/radio_button';
|
||||||
|
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
import { AccountCard } from './components/account_card';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
|
||||||
|
recentlyActive: {
|
||||||
|
id: 'directory.recently_active',
|
||||||
|
defaultMessage: 'Recently active',
|
||||||
|
},
|
||||||
|
newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' },
|
||||||
|
local: { id: 'directory.local', defaultMessage: 'From {domain} only' },
|
||||||
|
federated: {
|
||||||
|
id: 'directory.federated',
|
||||||
|
defaultMessage: 'From known fediverse',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Directory: React.FC<{
|
||||||
|
columnId?: string;
|
||||||
|
multiColumn?: boolean;
|
||||||
|
params?: { order: string; local?: boolean };
|
||||||
|
}> = ({ columnId, multiColumn, params }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [state, setState] = useState<{
|
||||||
|
order: string | null;
|
||||||
|
local: boolean | null;
|
||||||
|
}>({
|
||||||
|
order: null,
|
||||||
|
local: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const column = useRef<Column>(null);
|
||||||
|
|
||||||
|
const order = state.order ?? params?.order ?? 'active';
|
||||||
|
const local = state.local ?? params?.local ?? false;
|
||||||
|
|
||||||
|
const handlePin = useCallback(() => {
|
||||||
|
if (columnId) {
|
||||||
|
dispatch(removeColumn(columnId));
|
||||||
|
} else {
|
||||||
|
dispatch(addColumn('DIRECTORY', { order, local }));
|
||||||
|
}
|
||||||
|
}, [dispatch, columnId, order, local]);
|
||||||
|
|
||||||
|
const domain = useAppSelector((s) => s.meta.get('domain') as string);
|
||||||
|
const accountIds = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
state.user_lists.getIn(
|
||||||
|
['directory', 'items'],
|
||||||
|
ImmutableList(),
|
||||||
|
) as ImmutableList<string>,
|
||||||
|
);
|
||||||
|
const isLoading = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
state.user_lists.getIn(['directory', 'isLoading'], true) as boolean,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void dispatch(fetchDirectory({ order, local }));
|
||||||
|
}, [dispatch, order, local]);
|
||||||
|
|
||||||
|
const handleMove = useCallback(
|
||||||
|
(dir: string) => {
|
||||||
|
dispatch(moveColumn(columnId, dir));
|
||||||
|
},
|
||||||
|
[dispatch, columnId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleHeaderClick = useCallback(() => {
|
||||||
|
column.current?.scrollTop();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChangeOrder = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||||
|
(e) => {
|
||||||
|
if (columnId) {
|
||||||
|
dispatch(changeColumnParams(columnId, ['order'], e.target.value));
|
||||||
|
} else {
|
||||||
|
setState((s) => ({ order: e.target.value, local: s.local }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, columnId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeLocal = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||||
|
(e) => {
|
||||||
|
if (columnId) {
|
||||||
|
dispatch(
|
||||||
|
changeColumnParams(columnId, ['local'], e.target.value === '1'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setState((s) => ({ local: e.target.value === '1', order: s.order }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, columnId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleLoadMore = useCallback(() => {
|
||||||
|
void dispatch(expandDirectory({ order, local }));
|
||||||
|
}, [dispatch, order, local]);
|
||||||
|
|
||||||
|
const pinned = !!columnId;
|
||||||
|
|
||||||
|
const scrollableArea = (
|
||||||
|
<div className='scrollable'>
|
||||||
|
<div className='filter-form'>
|
||||||
|
<div className='filter-form__column' role='group'>
|
||||||
|
<RadioButton
|
||||||
|
name='order'
|
||||||
|
value='active'
|
||||||
|
label={intl.formatMessage(messages.recentlyActive)}
|
||||||
|
checked={order === 'active'}
|
||||||
|
onChange={handleChangeOrder}
|
||||||
|
/>
|
||||||
|
<RadioButton
|
||||||
|
name='order'
|
||||||
|
value='new'
|
||||||
|
label={intl.formatMessage(messages.newArrivals)}
|
||||||
|
checked={order === 'new'}
|
||||||
|
onChange={handleChangeOrder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='filter-form__column' role='group'>
|
||||||
|
<RadioButton
|
||||||
|
name='local'
|
||||||
|
value='1'
|
||||||
|
label={intl.formatMessage(messages.local, { domain })}
|
||||||
|
checked={local}
|
||||||
|
onChange={handleChangeLocal}
|
||||||
|
/>
|
||||||
|
<RadioButton
|
||||||
|
name='local'
|
||||||
|
value='0'
|
||||||
|
label={intl.formatMessage(messages.federated)}
|
||||||
|
checked={!local}
|
||||||
|
onChange={handleChangeLocal}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='directory__list'>
|
||||||
|
{isLoading ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
accountIds.map((accountId) => (
|
||||||
|
<AccountCard accountId={accountId} key={accountId} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LoadMore onClick={handleLoadMore} visible={!isLoading} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
ref={column}
|
||||||
|
label={intl.formatMessage(messages.title)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
// @ts-expect-error ColumnHeader is not properly typed yet
|
||||||
|
icon='address-book-o'
|
||||||
|
iconComponent={PeopleIcon}
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
onPin={handlePin}
|
||||||
|
onMove={handleMove}
|
||||||
|
onClick={handleHeaderClick}
|
||||||
|
pinned={pinned}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{multiColumn && !pinned ? (
|
||||||
|
// @ts-expect-error ScrollContainer is not properly typed yet
|
||||||
|
<ScrollContainer scrollKey='directory'>
|
||||||
|
{scrollableArea}
|
||||||
|
</ScrollContainer>
|
||||||
|
) : (
|
||||||
|
scrollableArea
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)}</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export -- Needed because this is called as an async components
|
||||||
|
export default Directory;
|
|
@ -1,12 +1,8 @@
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DIRECTORY_FETCH_REQUEST,
|
expandDirectory,
|
||||||
DIRECTORY_FETCH_SUCCESS,
|
fetchDirectory
|
||||||
DIRECTORY_FETCH_FAIL,
|
|
||||||
DIRECTORY_EXPAND_REQUEST,
|
|
||||||
DIRECTORY_EXPAND_SUCCESS,
|
|
||||||
DIRECTORY_EXPAND_FAIL,
|
|
||||||
} from 'mastodon/actions/directory';
|
} from 'mastodon/actions/directory';
|
||||||
import {
|
import {
|
||||||
FEATURED_TAGS_FETCH_REQUEST,
|
FEATURED_TAGS_FETCH_REQUEST,
|
||||||
|
@ -117,6 +113,7 @@ const normalizeFeaturedTags = (state, path, featuredTags, accountId) => {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||||
export default function userLists(state = initialState, action) {
|
export default function userLists(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case FOLLOWERS_FETCH_SUCCESS:
|
case FOLLOWERS_FETCH_SUCCESS:
|
||||||
|
@ -194,16 +191,6 @@ export default function userLists(state = initialState, action) {
|
||||||
case MUTES_FETCH_FAIL:
|
case MUTES_FETCH_FAIL:
|
||||||
case MUTES_EXPAND_FAIL:
|
case MUTES_EXPAND_FAIL:
|
||||||
return state.setIn(['mutes', 'isLoading'], false);
|
return state.setIn(['mutes', 'isLoading'], false);
|
||||||
case DIRECTORY_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['directory'], action.accounts, action.next);
|
|
||||||
case DIRECTORY_EXPAND_SUCCESS:
|
|
||||||
return appendToList(state, ['directory'], action.accounts, action.next);
|
|
||||||
case DIRECTORY_FETCH_REQUEST:
|
|
||||||
case DIRECTORY_EXPAND_REQUEST:
|
|
||||||
return state.setIn(['directory', 'isLoading'], true);
|
|
||||||
case DIRECTORY_FETCH_FAIL:
|
|
||||||
case DIRECTORY_EXPAND_FAIL:
|
|
||||||
return state.setIn(['directory', 'isLoading'], false);
|
|
||||||
case FEATURED_TAGS_FETCH_SUCCESS:
|
case FEATURED_TAGS_FETCH_SUCCESS:
|
||||||
return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id);
|
return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id);
|
||||||
case FEATURED_TAGS_FETCH_REQUEST:
|
case FEATURED_TAGS_FETCH_REQUEST:
|
||||||
|
@ -211,6 +198,17 @@ export default function userLists(state = initialState, action) {
|
||||||
case FEATURED_TAGS_FETCH_FAIL:
|
case FEATURED_TAGS_FETCH_FAIL:
|
||||||
return state.setIn(['featured_tags', action.id, 'isLoading'], false);
|
return state.setIn(['featured_tags', action.id, 'isLoading'], false);
|
||||||
default:
|
default:
|
||||||
return state;
|
if(fetchDirectory.fulfilled.match(action))
|
||||||
|
return normalizeList(state, ['directory'], action.payload.accounts, undefined);
|
||||||
|
else if( expandDirectory.fulfilled.match(action))
|
||||||
|
return appendToList(state, ['directory'], action.payload.accounts, undefined);
|
||||||
|
else if(fetchDirectory.pending.match(action) ||
|
||||||
|
expandDirectory.pending.match(action))
|
||||||
|
return state.setIn(['directory', 'isLoading'], true);
|
||||||
|
else if(fetchDirectory.rejected.match(action) ||
|
||||||
|
expandDirectory.rejected.match(action))
|
||||||
|
return state.setIn(['directory', 'isLoading'], false);
|
||||||
|
else
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue