Add ability to search for all accounts when creating a list in web UI (#33036)
This commit is contained in:
		
							parent
							
								
									6cf87762a4
								
							
						
					
					
						commit
						7135f513a4
					
				| 
						 | 
					@ -1,66 +0,0 @@
 | 
				
			||||||
import { defineMessages } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { AxiosError } from 'axios';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const messages = defineMessages({
 | 
					 | 
				
			||||||
  unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
 | 
					 | 
				
			||||||
  unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
 | 
					 | 
				
			||||||
  rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' },
 | 
					 | 
				
			||||||
  rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ALERT_SHOW    = 'ALERT_SHOW';
 | 
					 | 
				
			||||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
 | 
					 | 
				
			||||||
export const ALERT_CLEAR   = 'ALERT_CLEAR';
 | 
					 | 
				
			||||||
export const ALERT_NOOP    = 'ALERT_NOOP';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const dismissAlert = alert => ({
 | 
					 | 
				
			||||||
  type: ALERT_DISMISS,
 | 
					 | 
				
			||||||
  alert,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const clearAlert = () => ({
 | 
					 | 
				
			||||||
  type: ALERT_CLEAR,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const showAlert = alert => ({
 | 
					 | 
				
			||||||
  type: ALERT_SHOW,
 | 
					 | 
				
			||||||
  alert,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const showAlertForError = (error, skipNotFound = false) => {
 | 
					 | 
				
			||||||
  if (error.response) {
 | 
					 | 
				
			||||||
    const { data, status, statusText, headers } = error.response;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Skip these errors as they are reflected in the UI
 | 
					 | 
				
			||||||
    if (skipNotFound && (status === 404 || status === 410)) {
 | 
					 | 
				
			||||||
      return { type: ALERT_NOOP };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Rate limit errors
 | 
					 | 
				
			||||||
    if (status === 429 && headers['x-ratelimit-reset']) {
 | 
					 | 
				
			||||||
      return showAlert({
 | 
					 | 
				
			||||||
        title: messages.rateLimitedTitle,
 | 
					 | 
				
			||||||
        message: messages.rateLimitedMessage,
 | 
					 | 
				
			||||||
        values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return showAlert({
 | 
					 | 
				
			||||||
      title: `${status}`,
 | 
					 | 
				
			||||||
      message: data.error || statusText,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // An aborted request, e.g. due to reloading the browser window, it not really error
 | 
					 | 
				
			||||||
  if (error.code === AxiosError.ECONNABORTED) {
 | 
					 | 
				
			||||||
    return { type: ALERT_NOOP };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.error(error);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return showAlert({
 | 
					 | 
				
			||||||
    title: messages.unexpectedTitle,
 | 
					 | 
				
			||||||
    message: messages.unexpectedMessage,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					import { defineMessages } from 'react-intl';
 | 
				
			||||||
 | 
					import type { MessageDescriptor } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AxiosError } from 'axios';
 | 
				
			||||||
 | 
					import type { AxiosResponse } from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Alert {
 | 
				
			||||||
 | 
					  title: string | MessageDescriptor;
 | 
				
			||||||
 | 
					  message: string | MessageDescriptor;
 | 
				
			||||||
 | 
					  values?: Record<string, string | number | Date>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ApiErrorResponse {
 | 
				
			||||||
 | 
					  error?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messages = defineMessages({
 | 
				
			||||||
 | 
					  unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
 | 
				
			||||||
 | 
					  unexpectedMessage: {
 | 
				
			||||||
 | 
					    id: 'alert.unexpected.message',
 | 
				
			||||||
 | 
					    defaultMessage: 'An unexpected error occurred.',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  rateLimitedTitle: {
 | 
				
			||||||
 | 
					    id: 'alert.rate_limited.title',
 | 
				
			||||||
 | 
					    defaultMessage: 'Rate limited',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  rateLimitedMessage: {
 | 
				
			||||||
 | 
					    id: 'alert.rate_limited.message',
 | 
				
			||||||
 | 
					    defaultMessage: 'Please retry after {retry_time, time, medium}.',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ALERT_SHOW = 'ALERT_SHOW';
 | 
				
			||||||
 | 
					export const ALERT_DISMISS = 'ALERT_DISMISS';
 | 
				
			||||||
 | 
					export const ALERT_CLEAR = 'ALERT_CLEAR';
 | 
				
			||||||
 | 
					export const ALERT_NOOP = 'ALERT_NOOP';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const dismissAlert = (alert: Alert) => ({
 | 
				
			||||||
 | 
					  type: ALERT_DISMISS,
 | 
				
			||||||
 | 
					  alert,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const clearAlert = () => ({
 | 
				
			||||||
 | 
					  type: ALERT_CLEAR,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showAlert = (alert: Alert) => ({
 | 
				
			||||||
 | 
					  type: ALERT_SHOW,
 | 
				
			||||||
 | 
					  alert,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showAlertForError = (error: unknown, skipNotFound = false) => {
 | 
				
			||||||
 | 
					  if (error instanceof AxiosError && error.response) {
 | 
				
			||||||
 | 
					    const { status, statusText, headers } = error.response;
 | 
				
			||||||
 | 
					    const { data } = error.response as AxiosResponse<ApiErrorResponse>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Skip these errors as they are reflected in the UI
 | 
				
			||||||
 | 
					    if (skipNotFound && (status === 404 || status === 410)) {
 | 
				
			||||||
 | 
					      return { type: ALERT_NOOP };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Rate limit errors
 | 
				
			||||||
 | 
					    if (status === 429 && headers['x-ratelimit-reset']) {
 | 
				
			||||||
 | 
					      return showAlert({
 | 
				
			||||||
 | 
					        title: messages.rateLimitedTitle,
 | 
				
			||||||
 | 
					        message: messages.rateLimitedMessage,
 | 
				
			||||||
 | 
					        values: {
 | 
				
			||||||
 | 
					          retry_time: new Date(headers['x-ratelimit-reset'] as string),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return showAlert({
 | 
				
			||||||
 | 
					      title: `${status}`,
 | 
				
			||||||
 | 
					      message: data.error ?? statusText,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // An aborted request, e.g. due to reloading the browser window, it not really error
 | 
				
			||||||
 | 
					  if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) {
 | 
				
			||||||
 | 
					    return { type: ALERT_NOOP };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.error(error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return showAlert({
 | 
				
			||||||
 | 
					    title: messages.unexpectedTitle,
 | 
				
			||||||
 | 
					    message: messages.unexpectedMessage,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,16 @@ export const apiSubmitAccountNote = (id: string, value: string) =>
 | 
				
			||||||
  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
 | 
					  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
 | 
				
			||||||
    comment: value,
 | 
					    comment: value,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const apiFollowAccount = (
 | 
				
			||||||
 | 
					  id: string,
 | 
				
			||||||
 | 
					  params?: {
 | 
				
			||||||
 | 
					    reblogs: boolean;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					) =>
 | 
				
			||||||
 | 
					  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/follow`, {
 | 
				
			||||||
 | 
					    ...params,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const apiUnfollowAccount = (id: string) =>
 | 
				
			||||||
 | 
					  apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/unfollow`);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,175 +0,0 @@
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { useCallback } from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import classNames from 'classnames';
 | 
					 | 
				
			||||||
import { Link } from 'react-router-dom';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
 | 
					 | 
				
			||||||
import { EmptyAccount } from 'mastodon/components/empty_account';
 | 
					 | 
				
			||||||
import { FollowButton } from 'mastodon/components/follow_button';
 | 
					 | 
				
			||||||
import { ShortNumber } from 'mastodon/components/short_number';
 | 
					 | 
				
			||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
 | 
					 | 
				
			||||||
import { me } from '../initial_state';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Avatar } from './avatar';
 | 
					 | 
				
			||||||
import { Button } from './button';
 | 
					 | 
				
			||||||
import { FollowersCounter } from './counters';
 | 
					 | 
				
			||||||
import { DisplayName } from './display_name';
 | 
					 | 
				
			||||||
import { RelativeTimestamp } from './relative_timestamp';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const messages = defineMessages({
 | 
					 | 
				
			||||||
  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
 | 
					 | 
				
			||||||
  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
 | 
					 | 
				
			||||||
  mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
 | 
					 | 
				
			||||||
  unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
 | 
					 | 
				
			||||||
  mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
 | 
					 | 
				
			||||||
  block: { id: 'account.block_short', defaultMessage: 'Block' },
 | 
					 | 
				
			||||||
  more: { id: 'status.more', defaultMessage: 'More' },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
 | 
					 | 
				
			||||||
  const intl = useIntl();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleBlock = useCallback(() => {
 | 
					 | 
				
			||||||
    onBlock(account);
 | 
					 | 
				
			||||||
  }, [onBlock, account]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMute = useCallback(() => {
 | 
					 | 
				
			||||||
    onMute(account);
 | 
					 | 
				
			||||||
  }, [onMute, account]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleMuteNotifications = useCallback(() => {
 | 
					 | 
				
			||||||
    onMuteNotifications(account, true);
 | 
					 | 
				
			||||||
  }, [onMuteNotifications, account]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleUnmuteNotifications = useCallback(() => {
 | 
					 | 
				
			||||||
    onMuteNotifications(account, false);
 | 
					 | 
				
			||||||
  }, [onMuteNotifications, account]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!account) {
 | 
					 | 
				
			||||||
    return <EmptyAccount size={size} minimal={minimal} />;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (hidden) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <>
 | 
					 | 
				
			||||||
        {account.get('display_name')}
 | 
					 | 
				
			||||||
        {account.get('username')}
 | 
					 | 
				
			||||||
      </>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let buttons;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (account.get('id') !== me && account.get('relationship', null) !== null) {
 | 
					 | 
				
			||||||
    const requested = account.getIn(['relationship', 'requested']);
 | 
					 | 
				
			||||||
    const blocking  = account.getIn(['relationship', 'blocking']);
 | 
					 | 
				
			||||||
    const muting  = account.getIn(['relationship', 'muting']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (requested) {
 | 
					 | 
				
			||||||
      buttons = <FollowButton accountId={account.get('id')} />;
 | 
					 | 
				
			||||||
    } else if (blocking) {
 | 
					 | 
				
			||||||
      buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
 | 
					 | 
				
			||||||
    } else if (muting) {
 | 
					 | 
				
			||||||
      let menu;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (account.getIn(['relationship', 'muting_notifications'])) {
 | 
					 | 
				
			||||||
        menu = [{ text: intl.formatMessage(messages.unmute_notifications), action: handleUnmuteNotifications }];
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        menu = [{ text: intl.formatMessage(messages.mute_notifications), action: handleMuteNotifications }];
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      buttons = (
 | 
					 | 
				
			||||||
        <>
 | 
					 | 
				
			||||||
          <DropdownMenuContainer
 | 
					 | 
				
			||||||
            items={menu}
 | 
					 | 
				
			||||||
            icon='ellipsis-h'
 | 
					 | 
				
			||||||
            iconComponent={MoreHorizIcon}
 | 
					 | 
				
			||||||
            direction='right'
 | 
					 | 
				
			||||||
            title={intl.formatMessage(messages.more)}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <Button text={intl.formatMessage(messages.unmute)} onClick={handleMute} />
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else if (defaultAction === 'mute') {
 | 
					 | 
				
			||||||
      buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
 | 
					 | 
				
			||||||
    } else if (defaultAction === 'block') {
 | 
					 | 
				
			||||||
      buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      buttons = <FollowButton accountId={account.get('id')} />;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    buttons = <FollowButton accountId={account.get('id')} />;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let muteTimeRemaining;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (account.get('mute_expires_at')) {
 | 
					 | 
				
			||||||
    muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let verification;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (firstVerifiedField) {
 | 
					 | 
				
			||||||
    verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className={classNames('account', { 'account--minimal': minimal })}>
 | 
					 | 
				
			||||||
      <div className='account__wrapper'>
 | 
					 | 
				
			||||||
        <Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`} data-hover-card-account={account.get('id')}>
 | 
					 | 
				
			||||||
          <div className='account__avatar-wrapper'>
 | 
					 | 
				
			||||||
            <Avatar account={account} size={size} />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div className='account__contents'>
 | 
					 | 
				
			||||||
            <DisplayName account={account} />
 | 
					 | 
				
			||||||
            {!minimal && (
 | 
					 | 
				
			||||||
              <div className='account__details'>
 | 
					 | 
				
			||||||
                <ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </Link>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {!minimal && (
 | 
					 | 
				
			||||||
          <div className='account__relationship'>
 | 
					 | 
				
			||||||
            {buttons}
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      {withBio && (account.get('note').length > 0 ? (
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          className='account__note translate'
 | 
					 | 
				
			||||||
          dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      ) : (
 | 
					 | 
				
			||||||
        <div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
 | 
					 | 
				
			||||||
      ))}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Account.propTypes = {
 | 
					 | 
				
			||||||
  size: PropTypes.number,
 | 
					 | 
				
			||||||
  account: ImmutablePropTypes.record,
 | 
					 | 
				
			||||||
  onBlock: PropTypes.func,
 | 
					 | 
				
			||||||
  onMute: PropTypes.func,
 | 
					 | 
				
			||||||
  onMuteNotifications: PropTypes.func,
 | 
					 | 
				
			||||||
  hidden: PropTypes.bool,
 | 
					 | 
				
			||||||
  minimal: PropTypes.bool,
 | 
					 | 
				
			||||||
  defaultAction: PropTypes.string,
 | 
					 | 
				
			||||||
  withBio: PropTypes.bool,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Account;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,235 @@
 | 
				
			||||||
 | 
					import { useCallback } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  blockAccount,
 | 
				
			||||||
 | 
					  unblockAccount,
 | 
				
			||||||
 | 
					  muteAccount,
 | 
				
			||||||
 | 
					  unmuteAccount,
 | 
				
			||||||
 | 
					} from 'mastodon/actions/accounts';
 | 
				
			||||||
 | 
					import { initMuteModal } from 'mastodon/actions/mutes';
 | 
				
			||||||
 | 
					import { Avatar } from 'mastodon/components/avatar';
 | 
				
			||||||
 | 
					import { Button } from 'mastodon/components/button';
 | 
				
			||||||
 | 
					import { FollowersCounter } from 'mastodon/components/counters';
 | 
				
			||||||
 | 
					import { DisplayName } from 'mastodon/components/display_name';
 | 
				
			||||||
 | 
					import { FollowButton } from 'mastodon/components/follow_button';
 | 
				
			||||||
 | 
					import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
 | 
				
			||||||
 | 
					import { ShortNumber } from 'mastodon/components/short_number';
 | 
				
			||||||
 | 
					import { Skeleton } from 'mastodon/components/skeleton';
 | 
				
			||||||
 | 
					import { VerifiedBadge } from 'mastodon/components/verified_badge';
 | 
				
			||||||
 | 
					import DropdownMenu from 'mastodon/containers/dropdown_menu_container';
 | 
				
			||||||
 | 
					import { me } from 'mastodon/initial_state';
 | 
				
			||||||
 | 
					import { useAppSelector, useAppDispatch } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messages = defineMessages({
 | 
				
			||||||
 | 
					  follow: { id: 'account.follow', defaultMessage: 'Follow' },
 | 
				
			||||||
 | 
					  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
 | 
				
			||||||
 | 
					  cancel_follow_request: {
 | 
				
			||||||
 | 
					    id: 'account.cancel_follow_request',
 | 
				
			||||||
 | 
					    defaultMessage: 'Withdraw follow request',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
 | 
				
			||||||
 | 
					  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
 | 
				
			||||||
 | 
					  mute_notifications: {
 | 
				
			||||||
 | 
					    id: 'account.mute_notifications_short',
 | 
				
			||||||
 | 
					    defaultMessage: 'Mute notifications',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  unmute_notifications: {
 | 
				
			||||||
 | 
					    id: 'account.unmute_notifications_short',
 | 
				
			||||||
 | 
					    defaultMessage: 'Unmute notifications',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
 | 
				
			||||||
 | 
					  block: { id: 'account.block_short', defaultMessage: 'Block' },
 | 
				
			||||||
 | 
					  more: { id: 'status.more', defaultMessage: 'More' },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Account: React.FC<{
 | 
				
			||||||
 | 
					  size?: number;
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  hidden?: boolean;
 | 
				
			||||||
 | 
					  minimal?: boolean;
 | 
				
			||||||
 | 
					  defaultAction?: 'block' | 'mute';
 | 
				
			||||||
 | 
					  withBio?: boolean;
 | 
				
			||||||
 | 
					}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
 | 
				
			||||||
 | 
					  const intl = useIntl();
 | 
				
			||||||
 | 
					  const account = useAppSelector((state) => state.accounts.get(id));
 | 
				
			||||||
 | 
					  const relationship = useAppSelector((state) => state.relationships.get(id));
 | 
				
			||||||
 | 
					  const dispatch = useAppDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleBlock = useCallback(() => {
 | 
				
			||||||
 | 
					    if (relationship?.blocking) {
 | 
				
			||||||
 | 
					      dispatch(unblockAccount(id));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(blockAccount(id));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [dispatch, id, relationship]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMute = useCallback(() => {
 | 
				
			||||||
 | 
					    if (relationship?.muting) {
 | 
				
			||||||
 | 
					      dispatch(unmuteAccount(id));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      dispatch(initMuteModal(account));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [dispatch, id, account, relationship]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMuteNotifications = useCallback(() => {
 | 
				
			||||||
 | 
					    dispatch(muteAccount(id, true));
 | 
				
			||||||
 | 
					  }, [dispatch, id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleUnmuteNotifications = useCallback(() => {
 | 
				
			||||||
 | 
					    dispatch(muteAccount(id, false));
 | 
				
			||||||
 | 
					  }, [dispatch, id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (hidden) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        {account?.display_name}
 | 
				
			||||||
 | 
					        {account?.username}
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let buttons;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (account && account.id !== me && relationship) {
 | 
				
			||||||
 | 
					    const { requested, blocking, muting } = relationship;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (requested) {
 | 
				
			||||||
 | 
					      buttons = <FollowButton accountId={id} />;
 | 
				
			||||||
 | 
					    } else if (blocking) {
 | 
				
			||||||
 | 
					      buttons = (
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          text={intl.formatMessage(messages.unblock)}
 | 
				
			||||||
 | 
					          onClick={handleBlock}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (muting) {
 | 
				
			||||||
 | 
					      const menu = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          text: intl.formatMessage(
 | 
				
			||||||
 | 
					            relationship.muting_notifications
 | 
				
			||||||
 | 
					              ? messages.unmute_notifications
 | 
				
			||||||
 | 
					              : messages.mute_notifications,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          action: relationship.muting_notifications
 | 
				
			||||||
 | 
					            ? handleUnmuteNotifications
 | 
				
			||||||
 | 
					            : handleMuteNotifications,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      buttons = (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					          <DropdownMenu
 | 
				
			||||||
 | 
					            items={menu}
 | 
				
			||||||
 | 
					            icon='ellipsis-h'
 | 
				
			||||||
 | 
					            iconComponent={MoreHorizIcon}
 | 
				
			||||||
 | 
					            direction='right'
 | 
				
			||||||
 | 
					            title={intl.formatMessage(messages.more)}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Button
 | 
				
			||||||
 | 
					            text={intl.formatMessage(messages.unmute)}
 | 
				
			||||||
 | 
					            onClick={handleMute}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (defaultAction === 'mute') {
 | 
				
			||||||
 | 
					      buttons = (
 | 
				
			||||||
 | 
					        <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (defaultAction === 'block') {
 | 
				
			||||||
 | 
					      buttons = (
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					          text={intl.formatMessage(messages.block)}
 | 
				
			||||||
 | 
					          onClick={handleBlock}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      buttons = <FollowButton accountId={id} />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    buttons = <FollowButton accountId={id} />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let muteTimeRemaining;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (account?.mute_expires_at) {
 | 
				
			||||||
 | 
					    muteTimeRemaining = (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        · <RelativeTimestamp timestamp={account.mute_expires_at} futureDate />
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let verification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (firstVerifiedField) {
 | 
				
			||||||
 | 
					    verification = <VerifiedBadge link={firstVerifiedField.value} />;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={classNames('account', { 'account--minimal': minimal })}>
 | 
				
			||||||
 | 
					      <div className='account__wrapper'>
 | 
				
			||||||
 | 
					        <Link
 | 
				
			||||||
 | 
					          className='account__display-name'
 | 
				
			||||||
 | 
					          title={account?.acct}
 | 
				
			||||||
 | 
					          to={`/@${account?.acct}`}
 | 
				
			||||||
 | 
					          data-hover-card-account={id}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <div className='account__avatar-wrapper'>
 | 
				
			||||||
 | 
					            {account ? (
 | 
				
			||||||
 | 
					              <Avatar account={account} size={size} />
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <Skeleton width={size} height={size} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='account__contents'>
 | 
				
			||||||
 | 
					            <DisplayName account={account} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {!minimal && (
 | 
				
			||||||
 | 
					              <div className='account__details'>
 | 
				
			||||||
 | 
					                {account ? (
 | 
				
			||||||
 | 
					                  <>
 | 
				
			||||||
 | 
					                    <ShortNumber
 | 
				
			||||||
 | 
					                      value={account.followers_count}
 | 
				
			||||||
 | 
					                      renderer={FollowersCounter}
 | 
				
			||||||
 | 
					                    />{' '}
 | 
				
			||||||
 | 
					                    {verification} {muteTimeRemaining}
 | 
				
			||||||
 | 
					                  </>
 | 
				
			||||||
 | 
					                ) : (
 | 
				
			||||||
 | 
					                  <Skeleton width='7ch' />
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </Link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {!minimal && <div className='account__relationship'>{buttons}</div>}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {account &&
 | 
				
			||||||
 | 
					        withBio &&
 | 
				
			||||||
 | 
					        (account.note.length > 0 ? (
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					            className='account__note translate'
 | 
				
			||||||
 | 
					            dangerouslySetInnerHTML={{ __html: account.note_emojified }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <div className='account__note account__note--missing'>
 | 
				
			||||||
 | 
					            <FormattedMessage
 | 
				
			||||||
 | 
					              id='account.no_bio'
 | 
				
			||||||
 | 
					              defaultMessage='No description provided.'
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
import React from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import classNames from 'classnames';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { DisplayName } from 'mastodon/components/display_name';
 | 
					 | 
				
			||||||
import { Skeleton } from 'mastodon/components/skeleton';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  size?: number;
 | 
					 | 
				
			||||||
  minimal?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const EmptyAccount: React.FC<Props> = ({
 | 
					 | 
				
			||||||
  size = 46,
 | 
					 | 
				
			||||||
  minimal = false,
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className={classNames('account', { 'account--minimal': minimal })}>
 | 
					 | 
				
			||||||
      <div className='account__wrapper'>
 | 
					 | 
				
			||||||
        <div className='account__display-name'>
 | 
					 | 
				
			||||||
          <div className='account__avatar-wrapper'>
 | 
					 | 
				
			||||||
            <Skeleton width={size} height={size} />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <DisplayName />
 | 
					 | 
				
			||||||
            <Skeleton width='7ch' />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -8,10 +8,10 @@ import { Link } from 'react-router-dom';
 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					import { connect } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fetchServer } from 'mastodon/actions/server';
 | 
					import { fetchServer } from 'mastodon/actions/server';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 | 
					import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 | 
				
			||||||
import { ShortNumber } from 'mastodon/components/short_number';
 | 
					import { ShortNumber } from 'mastodon/components/short_number';
 | 
				
			||||||
import { Skeleton } from 'mastodon/components/skeleton';
 | 
					import { Skeleton } from 'mastodon/components/skeleton';
 | 
				
			||||||
import Account from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import { domain } from 'mastodon/initial_state';
 | 
					import { domain } from 'mastodon/initial_state';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
import { injectIntl } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { connect } from 'react-redux';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { openModal } from 'mastodon/actions/modal';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  followAccount,
 | 
					 | 
				
			||||||
  blockAccount,
 | 
					 | 
				
			||||||
  unblockAccount,
 | 
					 | 
				
			||||||
  muteAccount,
 | 
					 | 
				
			||||||
  unmuteAccount,
 | 
					 | 
				
			||||||
} from '../actions/accounts';
 | 
					 | 
				
			||||||
import { initMuteModal } from '../actions/mutes';
 | 
					 | 
				
			||||||
import Account from '../components/account';
 | 
					 | 
				
			||||||
import { makeGetAccount } from '../selectors';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const makeMapStateToProps = () => {
 | 
					 | 
				
			||||||
  const getAccount = makeGetAccount();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const mapStateToProps = (state, props) => ({
 | 
					 | 
				
			||||||
    account: getAccount(state, props.id),
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return mapStateToProps;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const mapDispatchToProps = (dispatch) => ({
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onFollow (account) {
 | 
					 | 
				
			||||||
    if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
 | 
					 | 
				
			||||||
      dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      dispatch(followAccount(account.get('id')));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onBlock (account) {
 | 
					 | 
				
			||||||
    if (account.getIn(['relationship', 'blocking'])) {
 | 
					 | 
				
			||||||
      dispatch(unblockAccount(account.get('id')));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      dispatch(blockAccount(account.get('id')));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onMute (account) {
 | 
					 | 
				
			||||||
    if (account.getIn(['relationship', 'muting'])) {
 | 
					 | 
				
			||||||
      dispatch(unmuteAccount(account.get('id')));
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      dispatch(initMuteModal(account));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onMuteNotifications (account, notifications) {
 | 
					 | 
				
			||||||
    dispatch(muteAccount(account.get('id'), notifications));
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
 | 
					 | 
				
			||||||
| 
						 | 
					@ -13,11 +13,11 @@ import { connect } from 'react-redux';
 | 
				
			||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
 | 
					import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
 | 
				
			||||||
import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
 | 
					import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
 | 
				
			||||||
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks  } from 'mastodon/actions/server';
 | 
					import { fetchServer, fetchExtendedDescription, fetchDomainBlocks  } from 'mastodon/actions/server';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import Column from 'mastodon/components/column';
 | 
					import Column from 'mastodon/components/column';
 | 
				
			||||||
import { Icon  }  from 'mastodon/components/icon';
 | 
					import { Icon  }  from 'mastodon/components/icon';
 | 
				
			||||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 | 
					import { ServerHeroImage } from 'mastodon/components/server_hero_image';
 | 
				
			||||||
import { Skeleton } from 'mastodon/components/skeleton';
 | 
					import { Skeleton } from 'mastodon/components/skeleton';
 | 
				
			||||||
import Account from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import LinkFooter from 'mastodon/features/ui/components/link_footer';
 | 
					import LinkFooter from 'mastodon/features/ui/components/link_footer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,11 @@ import { connect } from 'react-redux';
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
 | 
					import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
 | 
					import { fetchBlocks, expandBlocks } from '../../actions/blocks';
 | 
				
			||||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
					import { LoadingIndicator } from '../../components/loading_indicator';
 | 
				
			||||||
import ScrollableList from '../../components/scrollable_list';
 | 
					import ScrollableList from '../../components/scrollable_list';
 | 
				
			||||||
import AccountContainer from '../../containers/account_container';
 | 
					 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					@ -70,7 +70,7 @@ class Blocks extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {accountIds.map(id =>
 | 
					          {accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} defaultAction='block' />,
 | 
					            <Account key={id} id={id} defaultAction='block' />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
      </Column>
 | 
					      </Column>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ import { useSelector, useDispatch } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
 | 
					import CloseIcon from '@/material-icons/400-24px/close.svg?react';
 | 
				
			||||||
import { cancelReplyCompose } from 'mastodon/actions/compose';
 | 
					import { cancelReplyCompose } from 'mastodon/actions/compose';
 | 
				
			||||||
import Account from 'mastodon/components/account';
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { IconButton } from 'mastodon/components/icon_button';
 | 
					import { IconButton } from 'mastodon/components/icon_button';
 | 
				
			||||||
import { me } from 'mastodon/initial_state';
 | 
					import { me } from 'mastodon/initial_state';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,6 @@ const messages = defineMessages({
 | 
				
			||||||
export const NavigationBar = () => {
 | 
					export const NavigationBar = () => {
 | 
				
			||||||
  const dispatch = useDispatch();
 | 
					  const dispatch = useDispatch();
 | 
				
			||||||
  const intl = useIntl();
 | 
					  const intl = useIntl();
 | 
				
			||||||
  const account = useSelector(state => state.getIn(['accounts', me]));
 | 
					 | 
				
			||||||
  const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
 | 
					  const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleCancelClick = useCallback(() => {
 | 
					  const handleCancelClick = useCallback(() => {
 | 
				
			||||||
| 
						 | 
					@ -29,7 +28,7 @@ export const NavigationBar = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className='navigation-bar'>
 | 
					    <div className='navigation-bar'>
 | 
				
			||||||
      <Account account={account} minimal />
 | 
					      <Account id={me} minimal />
 | 
				
			||||||
      {isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
 | 
					      {isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
 | 
				
			||||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 | 
					import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 | 
				
			||||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
 | 
					import TagIcon from '@/material-icons/400-24px/tag.svg?react';
 | 
				
			||||||
import { expandSearch } from 'mastodon/actions/search';
 | 
					import { expandSearch } from 'mastodon/actions/search';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { Icon }  from 'mastodon/components/icon';
 | 
					import { Icon }  from 'mastodon/components/icon';
 | 
				
			||||||
import { LoadMore } from 'mastodon/components/load_more';
 | 
					import { LoadMore } from 'mastodon/components/load_more';
 | 
				
			||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
					import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
				
			||||||
| 
						 | 
					@ -13,7 +14,6 @@ import { SearchSection } from 'mastodon/features/explore/components/search_secti
 | 
				
			||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
					import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
 | 
					import { ImmutableHashtag as Hashtag } from '../../../components/hashtag';
 | 
				
			||||||
import AccountContainer from '../../../containers/account_container';
 | 
					 | 
				
			||||||
import StatusContainer from '../../../containers/status_container';
 | 
					import StatusContainer from '../../../containers/status_container';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const INITIAL_PAGE_LIMIT = 10;
 | 
					const INITIAL_PAGE_LIMIT = 10;
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ export const SearchResults = () => {
 | 
				
			||||||
  if (results.get('accounts') && results.get('accounts').size > 0) {
 | 
					  if (results.get('accounts') && results.get('accounts').size > 0) {
 | 
				
			||||||
    accounts = (
 | 
					    accounts = (
 | 
				
			||||||
      <SearchSection title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
 | 
					      <SearchSection title={<><Icon id='users' icon={PeopleIcon} /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></>}>
 | 
				
			||||||
        {withoutLastResult(results.get('accounts')).map(accountId => <AccountContainer key={accountId} id={accountId} />)}
 | 
					        {withoutLastResult(results.get('accounts')).map(accountId => <Account key={accountId} id={accountId} />)}
 | 
				
			||||||
        {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreAccounts} />}
 | 
					        {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && <LoadMore visible onClick={handleLoadMoreAccounts} />}
 | 
				
			||||||
      </SearchSection>
 | 
					      </SearchSection>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,10 +13,10 @@ import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
 | 
				
			||||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 | 
					import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
 | 
				
			||||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
 | 
					import TagIcon from '@/material-icons/400-24px/tag.svg?react';
 | 
				
			||||||
import { submitSearch, expandSearch } from 'mastodon/actions/search';
 | 
					import { submitSearch, expandSearch } from 'mastodon/actions/search';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
 | 
					import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
 | 
				
			||||||
import { Icon } from 'mastodon/components/icon';
 | 
					import { Icon } from 'mastodon/components/icon';
 | 
				
			||||||
import ScrollableList from 'mastodon/components/scrollable_list';
 | 
					import ScrollableList from 'mastodon/components/scrollable_list';
 | 
				
			||||||
import Account from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import Status from 'mastodon/containers/status_container';
 | 
					import Status from 'mastodon/containers/status_container';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { SearchSection } from './components/search_section';
 | 
					import { SearchSection } from './components/search_section';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,11 +12,11 @@ import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 | 
					import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 | 
				
			||||||
import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions';
 | 
					import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import ColumnHeader from 'mastodon/components/column_header';
 | 
					import ColumnHeader from 'mastodon/components/column_header';
 | 
				
			||||||
import { Icon }  from 'mastodon/components/icon';
 | 
					import { Icon }  from 'mastodon/components/icon';
 | 
				
			||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
					import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
				
			||||||
import ScrollableList from 'mastodon/components/scrollable_list';
 | 
					import ScrollableList from 'mastodon/components/scrollable_list';
 | 
				
			||||||
import AccountContainer from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import Column from 'mastodon/features/ui/components/column';
 | 
					import Column from 'mastodon/features/ui/components/column';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					@ -87,7 +87,7 @@ class Favourites extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {accountIds.map(id =>
 | 
					          {accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} withNote={false} />,
 | 
					            <Account key={id} id={id} />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ import { connect } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
 | 
					import { TimelineHint } from 'mastodon/components/timeline_hint';
 | 
				
			||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 | 
					import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 | 
				
			||||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
 | 
					import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
 | 
				
			||||||
| 
						 | 
					@ -23,7 +24,6 @@ import {
 | 
				
			||||||
import { ColumnBackButton } from '../../components/column_back_button';
 | 
					import { ColumnBackButton } from '../../components/column_back_button';
 | 
				
			||||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
					import { LoadingIndicator } from '../../components/loading_indicator';
 | 
				
			||||||
import ScrollableList from '../../components/scrollable_list';
 | 
					import ScrollableList from '../../components/scrollable_list';
 | 
				
			||||||
import AccountContainer from '../../containers/account_container';
 | 
					 | 
				
			||||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
 | 
					import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
 | 
				
			||||||
import HeaderContainer from '../account_timeline/containers/header_container';
 | 
					import HeaderContainer from '../account_timeline/containers/header_container';
 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
| 
						 | 
					@ -175,7 +175,7 @@ class Followers extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {forceEmptyState ? [] : accountIds.map(id =>
 | 
					          {forceEmptyState ? [] : accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} withNote={false} />,
 | 
					            <Account key={id} id={id} />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
      </Column>
 | 
					      </Column>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ import { connect } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
 | 
					import { TimelineHint } from 'mastodon/components/timeline_hint';
 | 
				
			||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 | 
					import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
 | 
				
			||||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
 | 
					import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
 | 
				
			||||||
| 
						 | 
					@ -23,7 +24,6 @@ import {
 | 
				
			||||||
import { ColumnBackButton } from '../../components/column_back_button';
 | 
					import { ColumnBackButton } from '../../components/column_back_button';
 | 
				
			||||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
					import { LoadingIndicator } from '../../components/loading_indicator';
 | 
				
			||||||
import ScrollableList from '../../components/scrollable_list';
 | 
					import ScrollableList from '../../components/scrollable_list';
 | 
				
			||||||
import AccountContainer from '../../containers/account_container';
 | 
					 | 
				
			||||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
 | 
					import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
 | 
				
			||||||
import HeaderContainer from '../account_timeline/containers/header_container';
 | 
					import HeaderContainer from '../account_timeline/containers/header_container';
 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
| 
						 | 
					@ -175,7 +175,7 @@ class Following extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {forceEmptyState ? [] : accountIds.map(id =>
 | 
					          {forceEmptyState ? [] : accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} withNote={false} />,
 | 
					            <Account key={id} id={id} />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
      </Column>
 | 
					      </Column>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,9 +9,13 @@ import { useDebouncedCallback } from 'use-debounce';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
 | 
					import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
 | 
				
			||||||
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
 | 
					import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
 | 
				
			||||||
 | 
					import { fetchRelationships } from 'mastodon/actions/accounts';
 | 
				
			||||||
 | 
					import { showAlertForError } from 'mastodon/actions/alerts';
 | 
				
			||||||
import { importFetchedAccounts } from 'mastodon/actions/importer';
 | 
					import { importFetchedAccounts } from 'mastodon/actions/importer';
 | 
				
			||||||
import { fetchList } from 'mastodon/actions/lists';
 | 
					import { fetchList } from 'mastodon/actions/lists';
 | 
				
			||||||
 | 
					import { openModal } from 'mastodon/actions/modal';
 | 
				
			||||||
import { apiRequest } from 'mastodon/api';
 | 
					import { apiRequest } from 'mastodon/api';
 | 
				
			||||||
 | 
					import { apiFollowAccount } from 'mastodon/api/accounts';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  apiGetAccounts,
 | 
					  apiGetAccounts,
 | 
				
			||||||
  apiAddAccountToList,
 | 
					  apiAddAccountToList,
 | 
				
			||||||
| 
						 | 
					@ -28,13 +32,14 @@ import { DisplayName } from 'mastodon/components/display_name';
 | 
				
			||||||
import ScrollableList from 'mastodon/components/scrollable_list';
 | 
					import ScrollableList from 'mastodon/components/scrollable_list';
 | 
				
			||||||
import { ShortNumber } from 'mastodon/components/short_number';
 | 
					import { ShortNumber } from 'mastodon/components/short_number';
 | 
				
			||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
 | 
					import { VerifiedBadge } from 'mastodon/components/verified_badge';
 | 
				
			||||||
 | 
					import { me } from 'mastodon/initial_state';
 | 
				
			||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
					import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
 | 
					  heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
 | 
				
			||||||
  placeholder: {
 | 
					  placeholder: {
 | 
				
			||||||
    id: 'lists.search_placeholder',
 | 
					    id: 'lists.search',
 | 
				
			||||||
    defaultMessage: 'Search people you follow',
 | 
					    defaultMessage: 'Search',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
 | 
					  enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
 | 
				
			||||||
  add: { id: 'lists.add_member', defaultMessage: 'Add' },
 | 
					  add: { id: 'lists.add_member', defaultMessage: 'Add' },
 | 
				
			||||||
| 
						 | 
					@ -51,17 +56,51 @@ const AccountItem: React.FC<{
 | 
				
			||||||
  onToggle: (accountId: string) => void;
 | 
					  onToggle: (accountId: string) => void;
 | 
				
			||||||
}> = ({ accountId, listId, partOfList, onToggle }) => {
 | 
					}> = ({ accountId, listId, partOfList, onToggle }) => {
 | 
				
			||||||
  const intl = useIntl();
 | 
					  const intl = useIntl();
 | 
				
			||||||
 | 
					  const dispatch = useAppDispatch();
 | 
				
			||||||
  const account = useAppSelector((state) => state.accounts.get(accountId));
 | 
					  const account = useAppSelector((state) => state.accounts.get(accountId));
 | 
				
			||||||
 | 
					  const relationship = useAppSelector((state) =>
 | 
				
			||||||
 | 
					    accountId ? state.relationships.get(accountId) : undefined,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const following =
 | 
				
			||||||
 | 
					    accountId === me || relationship?.following || relationship?.requested;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (accountId) {
 | 
				
			||||||
 | 
					      dispatch(fetchRelationships([accountId]));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [dispatch, accountId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleClick = useCallback(() => {
 | 
					  const handleClick = useCallback(() => {
 | 
				
			||||||
    if (partOfList) {
 | 
					    if (partOfList) {
 | 
				
			||||||
      void apiRemoveAccountFromList(listId, accountId);
 | 
					      void apiRemoveAccountFromList(listId, accountId);
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      void apiAddAccountToList(listId, accountId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      onToggle(accountId);
 | 
					      onToggle(accountId);
 | 
				
			||||||
  }, [accountId, listId, partOfList, onToggle]);
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (following) {
 | 
				
			||||||
 | 
					        void apiAddAccountToList(listId, accountId);
 | 
				
			||||||
 | 
					        onToggle(accountId);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dispatch(
 | 
				
			||||||
 | 
					          openModal({
 | 
				
			||||||
 | 
					            modalType: 'CONFIRM_FOLLOW_TO_LIST',
 | 
				
			||||||
 | 
					            modalProps: {
 | 
				
			||||||
 | 
					              accountId,
 | 
				
			||||||
 | 
					              onConfirm: () => {
 | 
				
			||||||
 | 
					                apiFollowAccount(accountId)
 | 
				
			||||||
 | 
					                  .then(() => apiAddAccountToList(listId, accountId))
 | 
				
			||||||
 | 
					                  .then(() => {
 | 
				
			||||||
 | 
					                    onToggle(accountId);
 | 
				
			||||||
 | 
					                    return '';
 | 
				
			||||||
 | 
					                  })
 | 
				
			||||||
 | 
					                  .catch((err: unknown) => {
 | 
				
			||||||
 | 
					                    dispatch(showAlertForError(err));
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [dispatch, accountId, following, listId, partOfList, onToggle]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!account) {
 | 
					  if (!account) {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
| 
						 | 
					@ -186,8 +225,7 @@ const ListMembers: React.FC<{
 | 
				
			||||||
        signal: searchRequestRef.current.signal,
 | 
					        signal: searchRequestRef.current.signal,
 | 
				
			||||||
        params: {
 | 
					        params: {
 | 
				
			||||||
          q: value,
 | 
					          q: value,
 | 
				
			||||||
          resolve: false,
 | 
					          resolve: true,
 | 
				
			||||||
          following: true,
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
        .then((data) => {
 | 
					        .then((data) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,11 +11,11 @@ import { connect } from 'react-redux';
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
 | 
					import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fetchMutes, expandMutes } from '../../actions/mutes';
 | 
					import { fetchMutes, expandMutes } from '../../actions/mutes';
 | 
				
			||||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
					import { LoadingIndicator } from '../../components/loading_indicator';
 | 
				
			||||||
import ScrollableList from '../../components/scrollable_list';
 | 
					import ScrollableList from '../../components/scrollable_list';
 | 
				
			||||||
import AccountContainer from '../../containers/account_container';
 | 
					 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@ class Mutes extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {accountIds.map(id =>
 | 
					          {accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} defaultAction='mute' />,
 | 
					            <Account key={id} id={id} defaultAction='mute' />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,8 +18,8 @@ import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
 | 
				
			||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
 | 
					import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
 | 
				
			||||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
 | 
					import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
 | 
				
			||||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
 | 
					import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { Icon }  from 'mastodon/components/icon';
 | 
					import { Icon }  from 'mastodon/components/icon';
 | 
				
			||||||
import AccountContainer from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import StatusContainer from 'mastodon/containers/status_container';
 | 
					import StatusContainer from 'mastodon/containers/status_container';
 | 
				
			||||||
import { me } from 'mastodon/initial_state';
 | 
					import { me } from 'mastodon/initial_state';
 | 
				
			||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 | 
					import { WithRouterPropTypes } from 'mastodon/utils/react_router';
 | 
				
			||||||
| 
						 | 
					@ -147,7 +147,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
 | 
					          <Account id={account.get('id')} hidden={this.props.hidden} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </HotKeys>
 | 
					      </HotKeys>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <FollowRequestContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
 | 
					          <FollowRequestContainer id={account.get('id')} hidden={this.props.hidden} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </HotKeys>
 | 
					      </HotKeys>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					@ -420,7 +420,7 @@ class Notification extends ImmutablePureComponent {
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
 | 
					          <Account id={account.get('id')} hidden={this.props.hidden} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </HotKeys>
 | 
					      </HotKeys>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,11 +14,11 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions';
 | 
				
			||||||
import { markAsPartial } from 'mastodon/actions/timelines';
 | 
					import { markAsPartial } from 'mastodon/actions/timelines';
 | 
				
			||||||
import { apiRequest } from 'mastodon/api';
 | 
					import { apiRequest } from 'mastodon/api';
 | 
				
			||||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
					import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { Column } from 'mastodon/components/column';
 | 
					import { Column } from 'mastodon/components/column';
 | 
				
			||||||
import { ColumnHeader } from 'mastodon/components/column_header';
 | 
					import { ColumnHeader } from 'mastodon/components/column_header';
 | 
				
			||||||
import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
 | 
					import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
 | 
				
			||||||
import ScrollableList from 'mastodon/components/scrollable_list';
 | 
					import ScrollableList from 'mastodon/components/scrollable_list';
 | 
				
			||||||
import Account from 'mastodon/containers/account_container';
 | 
					 | 
				
			||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
 | 
					import { useAppSelector, useAppDispatch } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					@ -170,12 +170,7 @@ export const Follows: React.FC<{
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {displayedAccountIds.map((accountId) => (
 | 
					        {displayedAccountIds.map((accountId) => (
 | 
				
			||||||
          <Account
 | 
					          <Account id={accountId} key={accountId} withBio />
 | 
				
			||||||
            /* @ts-expect-error inferred props are wrong */
 | 
					 | 
				
			||||||
            id={accountId}
 | 
					 | 
				
			||||||
            key={accountId}
 | 
					 | 
				
			||||||
            withBio
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
      </ScrollableList>
 | 
					      </ScrollableList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,13 +11,13 @@ import { connect } from 'react-redux';
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 | 
					import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
 | 
				
			||||||
 | 
					import { Account } from 'mastodon/components/account';
 | 
				
			||||||
import { Icon }  from 'mastodon/components/icon';
 | 
					import { Icon }  from 'mastodon/components/icon';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fetchReblogs, expandReblogs } from '../../actions/interactions';
 | 
					import { fetchReblogs, expandReblogs } from '../../actions/interactions';
 | 
				
			||||||
import ColumnHeader from '../../components/column_header';
 | 
					import ColumnHeader from '../../components/column_header';
 | 
				
			||||||
import { LoadingIndicator } from '../../components/loading_indicator';
 | 
					import { LoadingIndicator } from '../../components/loading_indicator';
 | 
				
			||||||
import ScrollableList from '../../components/scrollable_list';
 | 
					import ScrollableList from '../../components/scrollable_list';
 | 
				
			||||||
import AccountContainer from '../../containers/account_container';
 | 
					 | 
				
			||||||
import Column from '../ui/components/column';
 | 
					import Column from '../ui/components/column';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
| 
						 | 
					@ -88,7 +88,7 @@ class Reblogs extends ImmutablePureComponent {
 | 
				
			||||||
          bindToDocument={!multiColumn}
 | 
					          bindToDocument={!multiColumn}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {accountIds.map(id =>
 | 
					          {accountIds.map(id =>
 | 
				
			||||||
            <AccountContainer key={id} id={id} withNote={false} />,
 | 
					            <Account key={id} id={id} />,
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </ScrollableList>
 | 
					        </ScrollableList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useAppSelector } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { BaseConfirmationModalProps } from './confirmation_modal';
 | 
				
			||||||
 | 
					import { ConfirmationModal } from './confirmation_modal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const messages = defineMessages({
 | 
				
			||||||
 | 
					  title: {
 | 
				
			||||||
 | 
					    id: 'confirmations.follow_to_list.title',
 | 
				
			||||||
 | 
					    defaultMessage: 'Follow user?',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  confirm: {
 | 
				
			||||||
 | 
					    id: 'confirmations.follow_to_list.confirm',
 | 
				
			||||||
 | 
					    defaultMessage: 'Follow and add to list',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ConfirmFollowToListModal: React.FC<
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    accountId: string;
 | 
				
			||||||
 | 
					    onConfirm: () => void;
 | 
				
			||||||
 | 
					  } & BaseConfirmationModalProps
 | 
				
			||||||
 | 
					> = ({ accountId, onConfirm, onClose }) => {
 | 
				
			||||||
 | 
					  const intl = useIntl();
 | 
				
			||||||
 | 
					  const account = useAppSelector((state) => state.accounts.get(accountId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ConfirmationModal
 | 
				
			||||||
 | 
					      title={intl.formatMessage(messages.title)}
 | 
				
			||||||
 | 
					      message={
 | 
				
			||||||
 | 
					        <FormattedMessage
 | 
				
			||||||
 | 
					          id='confirmations.follow_to_list.message'
 | 
				
			||||||
 | 
					          defaultMessage='You need to be following {name} to add them to a list.'
 | 
				
			||||||
 | 
					          values={{ name: <strong>@{account?.acct}</strong> }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      confirm={intl.formatMessage(messages.confirm)}
 | 
				
			||||||
 | 
					      onConfirm={onConfirm}
 | 
				
			||||||
 | 
					      onClose={onClose}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -6,3 +6,4 @@ export { ConfirmEditStatusModal } from './edit_status';
 | 
				
			||||||
export { ConfirmUnfollowModal } from './unfollow';
 | 
					export { ConfirmUnfollowModal } from './unfollow';
 | 
				
			||||||
export { ConfirmClearNotificationsModal } from './clear_notifications';
 | 
					export { ConfirmClearNotificationsModal } from './clear_notifications';
 | 
				
			||||||
export { ConfirmLogOutModal } from './log_out';
 | 
					export { ConfirmLogOutModal } from './log_out';
 | 
				
			||||||
 | 
					export { ConfirmFollowToListModal } from './follow_to_list';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@ import {
 | 
				
			||||||
  ConfirmUnfollowModal,
 | 
					  ConfirmUnfollowModal,
 | 
				
			||||||
  ConfirmClearNotificationsModal,
 | 
					  ConfirmClearNotificationsModal,
 | 
				
			||||||
  ConfirmLogOutModal,
 | 
					  ConfirmLogOutModal,
 | 
				
			||||||
 | 
					  ConfirmFollowToListModal,
 | 
				
			||||||
} from './confirmation_modals';
 | 
					} from './confirmation_modals';
 | 
				
			||||||
import FocalPointModal from './focal_point_modal';
 | 
					import FocalPointModal from './focal_point_modal';
 | 
				
			||||||
import ImageModal from './image_modal';
 | 
					import ImageModal from './image_modal';
 | 
				
			||||||
| 
						 | 
					@ -56,6 +57,7 @@ export const MODAL_COMPONENTS = {
 | 
				
			||||||
  'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),
 | 
					  'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }),
 | 
				
			||||||
  'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
 | 
					  'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
 | 
				
			||||||
  'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
 | 
					  'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
 | 
				
			||||||
 | 
					  'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
 | 
				
			||||||
  'MUTE': MuteModal,
 | 
					  'MUTE': MuteModal,
 | 
				
			||||||
  'BLOCK': BlockModal,
 | 
					  'BLOCK': BlockModal,
 | 
				
			||||||
  'DOMAIN_BLOCK': DomainBlockModal,
 | 
					  'DOMAIN_BLOCK': DomainBlockModal,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -205,6 +205,9 @@
 | 
				
			||||||
  "confirmations.edit.confirm": "Edit",
 | 
					  "confirmations.edit.confirm": "Edit",
 | 
				
			||||||
  "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
 | 
					  "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
 | 
				
			||||||
  "confirmations.edit.title": "Overwrite post?",
 | 
					  "confirmations.edit.title": "Overwrite post?",
 | 
				
			||||||
 | 
					  "confirmations.follow_to_list.confirm": "Follow and add to list",
 | 
				
			||||||
 | 
					  "confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
 | 
				
			||||||
 | 
					  "confirmations.follow_to_list.title": "Follow user?",
 | 
				
			||||||
  "confirmations.logout.confirm": "Log out",
 | 
					  "confirmations.logout.confirm": "Log out",
 | 
				
			||||||
  "confirmations.logout.message": "Are you sure you want to log out?",
 | 
					  "confirmations.logout.message": "Are you sure you want to log out?",
 | 
				
			||||||
  "confirmations.logout.title": "Log out?",
 | 
					  "confirmations.logout.title": "Log out?",
 | 
				
			||||||
| 
						 | 
					@ -493,7 +496,7 @@
 | 
				
			||||||
  "lists.replies_policy.list": "Members of the list",
 | 
					  "lists.replies_policy.list": "Members of the list",
 | 
				
			||||||
  "lists.replies_policy.none": "No one",
 | 
					  "lists.replies_policy.none": "No one",
 | 
				
			||||||
  "lists.save": "Save",
 | 
					  "lists.save": "Save",
 | 
				
			||||||
  "lists.search_placeholder": "Search people you follow",
 | 
					  "lists.search": "Search",
 | 
				
			||||||
  "lists.show_replies_to": "Include replies from list members to",
 | 
					  "lists.show_replies_to": "Include replies from list members to",
 | 
				
			||||||
  "load_pending": "{count, plural, one {# new item} other {# new items}}",
 | 
					  "load_pending": "{count, plural, one {# new item} other {# new items}}",
 | 
				
			||||||
  "loading_indicator.label": "Loading…",
 | 
					  "loading_indicator.label": "Loading…",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue