Add preview of followers removed in domain block modal in web UI (#32032)
This commit is contained in:
		
							
								
								
									
										27
									
								
								app/controllers/api/v1/domain_blocks/previews_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/controllers/api/v1/domain_blocks/previews_controller.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Api::V1::DomainBlocks::PreviewsController < Api::BaseController
 | 
				
			||||||
 | 
					  before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }
 | 
				
			||||||
 | 
					  before_action :require_user!
 | 
				
			||||||
 | 
					  before_action :set_domain
 | 
				
			||||||
 | 
					  before_action :set_domain_block_preview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def show
 | 
				
			||||||
 | 
					    render json: @domain_block_preview, serializer: REST::DomainBlockPreviewSerializer
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_domain
 | 
				
			||||||
 | 
					    @domain = TagManager.instance.normalize_domain(params[:domain])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_domain_block_preview
 | 
				
			||||||
 | 
					    @domain_block_preview = with_read_replica do
 | 
				
			||||||
 | 
					      DomainBlockPreviewPresenter.new(
 | 
				
			||||||
 | 
					        following_count: current_account.following.where(domain: @domain).count,
 | 
				
			||||||
 | 
					        followers_count: current_account.followers.where(domain: @domain).count
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -70,6 +70,7 @@ export async function apiRequest<ApiResponse = unknown>(
 | 
				
			|||||||
  args: {
 | 
					  args: {
 | 
				
			||||||
    params?: RequestParamsOrData;
 | 
					    params?: RequestParamsOrData;
 | 
				
			||||||
    data?: RequestParamsOrData;
 | 
					    data?: RequestParamsOrData;
 | 
				
			||||||
 | 
					    timeout?: number;
 | 
				
			||||||
  } = {},
 | 
					  } = {},
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  const { data } = await api().request<ApiResponse>({
 | 
					  const { data } = await api().request<ApiResponse>({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ interface BaseProps
 | 
				
			|||||||
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
 | 
					  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
 | 
				
			||||||
  block?: boolean;
 | 
					  block?: boolean;
 | 
				
			||||||
  secondary?: boolean;
 | 
					  secondary?: boolean;
 | 
				
			||||||
 | 
					  dangerous?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
 | 
					interface PropsChildren extends PropsWithChildren<BaseProps> {
 | 
				
			||||||
@@ -26,6 +27,7 @@ export const Button: React.FC<Props> = ({
 | 
				
			|||||||
  disabled,
 | 
					  disabled,
 | 
				
			||||||
  block,
 | 
					  block,
 | 
				
			||||||
  secondary,
 | 
					  secondary,
 | 
				
			||||||
 | 
					  dangerous,
 | 
				
			||||||
  className,
 | 
					  className,
 | 
				
			||||||
  title,
 | 
					  title,
 | 
				
			||||||
  text,
 | 
					  text,
 | 
				
			||||||
@@ -46,6 +48,7 @@ export const Button: React.FC<Props> = ({
 | 
				
			|||||||
      className={classNames('button', className, {
 | 
					      className={classNames('button', className, {
 | 
				
			||||||
        'button-secondary': secondary,
 | 
					        'button-secondary': secondary,
 | 
				
			||||||
        'button--block': block,
 | 
					        'button--block': block,
 | 
				
			||||||
 | 
					        'button--dangerous': dangerous,
 | 
				
			||||||
      })}
 | 
					      })}
 | 
				
			||||||
      disabled={disabled}
 | 
					      disabled={disabled}
 | 
				
			||||||
      onClick={handleClick}
 | 
					      onClick={handleClick}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,7 +99,7 @@ export const BlockModal = ({ accountId, acct }) => {
 | 
				
			|||||||
            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
 | 
					            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <Button onClick={handleClick} autoFocus>
 | 
					          <Button onClick={handleClick} dangerous autoFocus>
 | 
				
			||||||
            <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
 | 
					            <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
 | 
				
			||||||
          </Button>
 | 
					          </Button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,106 +0,0 @@
 | 
				
			|||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
import { useCallback } from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { useDispatch } from 'react-redux';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
 | 
					 | 
				
			||||||
import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react';
 | 
					 | 
				
			||||||
import HistoryIcon from '@/material-icons/400-24px/history.svg?react';
 | 
					 | 
				
			||||||
import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react';
 | 
					 | 
				
			||||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
 | 
					 | 
				
			||||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
 | 
					 | 
				
			||||||
import { blockAccount } from 'mastodon/actions/accounts';
 | 
					 | 
				
			||||||
import { blockDomain } from 'mastodon/actions/domain_blocks';
 | 
					 | 
				
			||||||
import { closeModal } from 'mastodon/actions/modal';
 | 
					 | 
				
			||||||
import { Button } from 'mastodon/components/button';
 | 
					 | 
				
			||||||
import { Icon } from 'mastodon/components/icon';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const DomainBlockModal = ({ domain, accountId, acct }) => {
 | 
					 | 
				
			||||||
  const dispatch = useDispatch();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleClick = useCallback(() => {
 | 
					 | 
				
			||||||
    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
					 | 
				
			||||||
    dispatch(blockDomain(domain));
 | 
					 | 
				
			||||||
  }, [dispatch, domain]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSecondaryClick = useCallback(() => {
 | 
					 | 
				
			||||||
    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
					 | 
				
			||||||
    dispatch(blockAccount(accountId));
 | 
					 | 
				
			||||||
  }, [dispatch, accountId]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleCancel = useCallback(() => {
 | 
					 | 
				
			||||||
    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
					 | 
				
			||||||
  }, [dispatch]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className='modal-root__modal safety-action-modal'>
 | 
					 | 
				
			||||||
      <div className='safety-action-modal__top'>
 | 
					 | 
				
			||||||
        <div className='safety-action-modal__header'>
 | 
					 | 
				
			||||||
          <div className='safety-action-modal__header__icon'>
 | 
					 | 
				
			||||||
            <Icon icon={DomainDisabledIcon} />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <h1><FormattedMessage id='domain_block_modal.title' defaultMessage='Block domain?' /></h1>
 | 
					 | 
				
			||||||
            <div>{domain}</div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div className='safety-action-modal__bullet-points'>
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='safety-action-modal__bullet-points__icon'><Icon icon={CampaignIcon} /></div>
 | 
					 | 
				
			||||||
            <div><FormattedMessage id='domain_block_modal.they_wont_know' defaultMessage="They won't know they've been blocked." /></div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='safety-action-modal__bullet-points__icon'><Icon icon={VisibilityOffIcon} /></div>
 | 
					 | 
				
			||||||
            <div><FormattedMessage id='domain_block_modal.you_wont_see_posts' defaultMessage="You won't see posts or notifications from users on this server." /></div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='safety-action-modal__bullet-points__icon'><Icon icon={PersonRemoveIcon} /></div>
 | 
					 | 
				
			||||||
            <div><FormattedMessage id='domain_block_modal.you_will_lose_followers' defaultMessage='All your followers from this server will be removed.' /></div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='safety-action-modal__bullet-points__icon'><Icon icon={ReplyIcon} /></div>
 | 
					 | 
				
			||||||
            <div><FormattedMessage id='domain_block_modal.they_cant_follow' defaultMessage='Nobody from this server can follow you.' /></div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='safety-action-modal__bullet-points__icon'><Icon icon={HistoryIcon} /></div>
 | 
					 | 
				
			||||||
            <div><FormattedMessage id='domain_block_modal.they_can_interact_with_old_posts' defaultMessage='People from this server can interact with your old posts.' /></div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className='safety-action-modal__bottom'>
 | 
					 | 
				
			||||||
        <div className='safety-action-modal__actions'>
 | 
					 | 
				
			||||||
          <Button onClick={handleSecondaryClick} secondary>
 | 
					 | 
				
			||||||
            <FormattedMessage id='domain_block_modal.block_account_instead' defaultMessage='Block @{name} instead' values={{ name: acct.split('@')[0] }} />
 | 
					 | 
				
			||||||
          </Button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div className='spacer' />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <button onClick={handleCancel} className='link-button'>
 | 
					 | 
				
			||||||
            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <Button onClick={handleClick} autoFocus>
 | 
					 | 
				
			||||||
            <FormattedMessage id='domain_block_modal.block' defaultMessage='Block server' />
 | 
					 | 
				
			||||||
          </Button>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
DomainBlockModal.propTypes = {
 | 
					 | 
				
			||||||
  domain: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  accountId: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
  acct: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default DomainBlockModal;
 | 
					 | 
				
			||||||
@@ -0,0 +1,204 @@
 | 
				
			|||||||
 | 
					import { useCallback, useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
 | 
				
			||||||
 | 
					import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react';
 | 
				
			||||||
 | 
					import HistoryIcon from '@/material-icons/400-24px/history.svg?react';
 | 
				
			||||||
 | 
					import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react';
 | 
				
			||||||
 | 
					import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
 | 
				
			||||||
 | 
					import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
 | 
				
			||||||
 | 
					import { blockAccount } from 'mastodon/actions/accounts';
 | 
				
			||||||
 | 
					import { blockDomain } from 'mastodon/actions/domain_blocks';
 | 
				
			||||||
 | 
					import { closeModal } from 'mastodon/actions/modal';
 | 
				
			||||||
 | 
					import { apiRequest } from 'mastodon/api';
 | 
				
			||||||
 | 
					import { Button } from 'mastodon/components/button';
 | 
				
			||||||
 | 
					import { Icon } from 'mastodon/components/icon';
 | 
				
			||||||
 | 
					import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | 
				
			||||||
 | 
					import { ShortNumber } from 'mastodon/components/short_number';
 | 
				
			||||||
 | 
					import { useAppDispatch } from 'mastodon/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface DomainBlockPreviewResponse {
 | 
				
			||||||
 | 
					  following_count: number;
 | 
				
			||||||
 | 
					  followers_count: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DomainBlockModal: React.FC<{
 | 
				
			||||||
 | 
					  domain: string;
 | 
				
			||||||
 | 
					  accountId: string;
 | 
				
			||||||
 | 
					  acct: string;
 | 
				
			||||||
 | 
					}> = ({ domain, accountId, acct }) => {
 | 
				
			||||||
 | 
					  const dispatch = useAppDispatch();
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(true);
 | 
				
			||||||
 | 
					  const [preview, setPreview] = useState<DomainBlockPreviewResponse | null>(
 | 
				
			||||||
 | 
					    null,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClick = useCallback(() => {
 | 
				
			||||||
 | 
					    if (loading) {
 | 
				
			||||||
 | 
					      return; // Prevent destructive action before the preview finishes loading or times out
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
				
			||||||
 | 
					    dispatch(blockDomain(domain));
 | 
				
			||||||
 | 
					  }, [dispatch, loading, domain]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSecondaryClick = useCallback(() => {
 | 
				
			||||||
 | 
					    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
				
			||||||
 | 
					    dispatch(blockAccount(accountId));
 | 
				
			||||||
 | 
					  }, [dispatch, accountId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCancel = useCallback(() => {
 | 
				
			||||||
 | 
					    dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | 
				
			||||||
 | 
					  }, [dispatch]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    apiRequest<DomainBlockPreviewResponse>('GET', 'v1/domain_blocks/preview', {
 | 
				
			||||||
 | 
					      params: { domain },
 | 
				
			||||||
 | 
					      timeout: 5000,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					      .then((data) => {
 | 
				
			||||||
 | 
					        setPreview(data);
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(() => {
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }, [setPreview, setLoading, domain]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='modal-root__modal safety-action-modal' aria-live='polite'>
 | 
				
			||||||
 | 
					      <div className='safety-action-modal__top'>
 | 
				
			||||||
 | 
					        <div className='safety-action-modal__header'>
 | 
				
			||||||
 | 
					          <div className='safety-action-modal__header__icon'>
 | 
				
			||||||
 | 
					            <Icon id='' icon={DomainDisabledIcon} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div>
 | 
				
			||||||
 | 
					            <h1>
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.title'
 | 
				
			||||||
 | 
					                defaultMessage='Block domain?'
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </h1>
 | 
				
			||||||
 | 
					            <div>{domain}</div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className='safety-action-modal__bullet-points'>
 | 
				
			||||||
 | 
					          {preview && preview.followers_count + preview.following_count > 0 && (
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <div className='safety-action-modal__bullet-points__icon'>
 | 
				
			||||||
 | 
					                <Icon id='' icon={PersonRemoveIcon} />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              <div>
 | 
				
			||||||
 | 
					                <strong>
 | 
				
			||||||
 | 
					                  <FormattedMessage
 | 
				
			||||||
 | 
					                    id='domain_block_modal.you_will_lose_num_followers'
 | 
				
			||||||
 | 
					                    defaultMessage='You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.'
 | 
				
			||||||
 | 
					                    values={{
 | 
				
			||||||
 | 
					                      followersCount: preview.followers_count,
 | 
				
			||||||
 | 
					                      followersCountDisplay: (
 | 
				
			||||||
 | 
					                        <ShortNumber value={preview.followers_count} />
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      followingCount: preview.following_count,
 | 
				
			||||||
 | 
					                      followingCountDisplay: (
 | 
				
			||||||
 | 
					                        <ShortNumber value={preview.following_count} />
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </strong>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='safety-action-modal__bullet-points--deemphasized'>
 | 
				
			||||||
 | 
					            <div className='safety-action-modal__bullet-points__icon'>
 | 
				
			||||||
 | 
					              <Icon id='' icon={CampaignIcon} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.they_wont_know'
 | 
				
			||||||
 | 
					                defaultMessage="They won't know they've been blocked."
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='safety-action-modal__bullet-points--deemphasized'>
 | 
				
			||||||
 | 
					            <div className='safety-action-modal__bullet-points__icon'>
 | 
				
			||||||
 | 
					              <Icon id='' icon={VisibilityOffIcon} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.you_wont_see_posts'
 | 
				
			||||||
 | 
					                defaultMessage="You won't see posts or notifications from users on this server."
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='safety-action-modal__bullet-points--deemphasized'>
 | 
				
			||||||
 | 
					            <div className='safety-action-modal__bullet-points__icon'>
 | 
				
			||||||
 | 
					              <Icon id='' icon={ReplyIcon} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.they_cant_follow'
 | 
				
			||||||
 | 
					                defaultMessage='Nobody from this server can follow you.'
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='safety-action-modal__bullet-points--deemphasized'>
 | 
				
			||||||
 | 
					            <div className='safety-action-modal__bullet-points__icon'>
 | 
				
			||||||
 | 
					              <Icon id='' icon={HistoryIcon} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.they_can_interact_with_old_posts'
 | 
				
			||||||
 | 
					                defaultMessage='People from this server can interact with your old posts.'
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className='safety-action-modal__bottom'>
 | 
				
			||||||
 | 
					        <div className='safety-action-modal__actions'>
 | 
				
			||||||
 | 
					          <Button onClick={handleSecondaryClick} secondary>
 | 
				
			||||||
 | 
					            <FormattedMessage
 | 
				
			||||||
 | 
					              id='domain_block_modal.block_account_instead'
 | 
				
			||||||
 | 
					              defaultMessage='Block @{name} instead'
 | 
				
			||||||
 | 
					              values={{ name: acct.split('@')[0] }}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='spacer' />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <button onClick={handleCancel} className='link-button'>
 | 
				
			||||||
 | 
					            <FormattedMessage
 | 
				
			||||||
 | 
					              id='confirmation_modal.cancel'
 | 
				
			||||||
 | 
					              defaultMessage='Cancel'
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Button onClick={handleClick} dangerous aria-busy={loading}>
 | 
				
			||||||
 | 
					            {loading ? (
 | 
				
			||||||
 | 
					              <LoadingIndicator />
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <FormattedMessage
 | 
				
			||||||
 | 
					                id='domain_block_modal.block'
 | 
				
			||||||
 | 
					                defaultMessage='Block server'
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line import/no-default-export
 | 
				
			||||||
 | 
					export default DomainBlockModal;
 | 
				
			||||||
@@ -221,7 +221,7 @@
 | 
				
			|||||||
  "domain_block_modal.they_cant_follow": "Nobody from this server can follow you.",
 | 
					  "domain_block_modal.they_cant_follow": "Nobody from this server can follow you.",
 | 
				
			||||||
  "domain_block_modal.they_wont_know": "They won't know they've been blocked.",
 | 
					  "domain_block_modal.they_wont_know": "They won't know they've been blocked.",
 | 
				
			||||||
  "domain_block_modal.title": "Block domain?",
 | 
					  "domain_block_modal.title": "Block domain?",
 | 
				
			||||||
  "domain_block_modal.you_will_lose_followers": "All your followers from this server will be removed.",
 | 
					  "domain_block_modal.you_will_lose_num_followers": "You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.",
 | 
				
			||||||
  "domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.",
 | 
					  "domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.",
 | 
				
			||||||
  "domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.",
 | 
					  "domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.",
 | 
				
			||||||
  "domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.",
 | 
					  "domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,6 +81,18 @@
 | 
				
			|||||||
    outline: $ui-button-icon-focus-outline;
 | 
					    outline: $ui-button-icon-focus-outline;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &--dangerous {
 | 
				
			||||||
 | 
					    background-color: var(--error-background-color);
 | 
				
			||||||
 | 
					    color: var(--on-error-color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &:active,
 | 
				
			||||||
 | 
					    &:focus,
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      background-color: var(--error-active-background-color);
 | 
				
			||||||
 | 
					      transition: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &--destructive {
 | 
					  &--destructive {
 | 
				
			||||||
    &:active,
 | 
					    &:active,
 | 
				
			||||||
    &:focus,
 | 
					    &:focus,
 | 
				
			||||||
@@ -6237,6 +6249,14 @@ a.status-card {
 | 
				
			|||||||
      display: flex;
 | 
					      display: flex;
 | 
				
			||||||
      gap: 16px;
 | 
					      gap: 16px;
 | 
				
			||||||
      align-items: center;
 | 
					      align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      strong {
 | 
				
			||||||
 | 
					        font-weight: 700;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &--deemphasized {
 | 
				
			||||||
 | 
					      color: $secondary-text-color;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &__icon {
 | 
					    &__icon {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,4 +111,7 @@ $font-monospace: 'mastodon-font-monospace' !default;
 | 
				
			|||||||
  --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
 | 
					  --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
 | 
				
			||||||
  --on-surface-color: #{transparentize($ui-base-color, 0.5)};
 | 
					  --on-surface-color: #{transparentize($ui-base-color, 0.5)};
 | 
				
			||||||
  --avatar-border-radius: 8px;
 | 
					  --avatar-border-radius: 8px;
 | 
				
			||||||
 | 
					  --error-background-color: #{darken($error-red, 16%)};
 | 
				
			||||||
 | 
					  --error-active-background-color: #{darken($error-red, 12%)};
 | 
				
			||||||
 | 
					  --on-error-color: #fff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								app/presenters/domain_block_preview_presenter.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/presenters/domain_block_preview_presenter.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DomainBlockPreviewPresenter < ActiveModelSerializers::Model
 | 
				
			||||||
 | 
					  attributes :followers_count, :following_count
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										5
									
								
								app/serializers/rest/domain_block_preview_serializer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/serializers/rest/domain_block_preview_serializer.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class REST::DomainBlockPreviewSerializer < ActiveModel::Serializer
 | 
				
			||||||
 | 
					  attributes :following_count, :followers_count
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -125,6 +125,10 @@ namespace :api, format: false do
 | 
				
			|||||||
      get :search, to: 'search#index'
 | 
					      get :search, to: 'search#index'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    namespace :domain_blocks do
 | 
				
			||||||
 | 
					      resource :preview, only: [:show]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    resource :domain_blocks, only: [:show, :create, :destroy]
 | 
					    resource :domain_blocks, only: [:show, :create, :destroy]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    resource :directory, only: [:show]
 | 
					    resource :directory, only: [:show]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user