Convert <Directory> to Typescript / function component (#30829)
				
					
				
			This commit is contained in:
		
							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,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										37
									
								
								app/javascript/mastodon/actions/directory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/javascript/mastodon/actions/directory.ts
									
									
									
									
									
										Normal file
									
								
							@ -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 };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										15
									
								
								app/javascript/mastodon/api/directory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/javascript/mastodon/api/directory.ts
									
									
									
									
									
										Normal file
									
								
							@ -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));
 | 
					 | 
				
			||||||
							
								
								
									
										217
									
								
								app/javascript/mastodon/features/directory/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								app/javascript/mastodon/features/directory/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user