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: {
 | 
			
		||||
    params?: RequestParamsOrData;
 | 
			
		||||
    data?: RequestParamsOrData;
 | 
			
		||||
    timeout?: number;
 | 
			
		||||
  } = {},
 | 
			
		||||
) {
 | 
			
		||||
  const { data } = await api().request<ApiResponse>({
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ interface BaseProps
 | 
			
		||||
  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
 | 
			
		||||
  block?: boolean;
 | 
			
		||||
  secondary?: boolean;
 | 
			
		||||
  dangerous?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
 | 
			
		||||
@@ -26,6 +27,7 @@ export const Button: React.FC<Props> = ({
 | 
			
		||||
  disabled,
 | 
			
		||||
  block,
 | 
			
		||||
  secondary,
 | 
			
		||||
  dangerous,
 | 
			
		||||
  className,
 | 
			
		||||
  title,
 | 
			
		||||
  text,
 | 
			
		||||
@@ -46,6 +48,7 @@ export const Button: React.FC<Props> = ({
 | 
			
		||||
      className={classNames('button', className, {
 | 
			
		||||
        'button-secondary': secondary,
 | 
			
		||||
        'button--block': block,
 | 
			
		||||
        'button--dangerous': dangerous,
 | 
			
		||||
      })}
 | 
			
		||||
      disabled={disabled}
 | 
			
		||||
      onClick={handleClick}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ export const BlockModal = ({ accountId, acct }) => {
 | 
			
		||||
            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
          <Button onClick={handleClick} autoFocus>
 | 
			
		||||
          <Button onClick={handleClick} dangerous autoFocus>
 | 
			
		||||
            <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
 | 
			
		||||
          </Button>
 | 
			
		||||
        </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_wont_know": "They won't know they've been blocked.",
 | 
			
		||||
  "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_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.",
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,18 @@
 | 
			
		||||
    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 {
 | 
			
		||||
    &:active,
 | 
			
		||||
    &:focus,
 | 
			
		||||
@@ -6237,6 +6249,14 @@ a.status-card {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      gap: 16px;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
 | 
			
		||||
      strong {
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &--deemphasized {
 | 
			
		||||
      color: $secondary-text-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__icon {
 | 
			
		||||
 
 | 
			
		||||
@@ -111,4 +111,7 @@ $font-monospace: 'mastodon-font-monospace' !default;
 | 
			
		||||
  --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
 | 
			
		||||
  --on-surface-color: #{transparentize($ui-base-color, 0.5)};
 | 
			
		||||
  --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'
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    namespace :domain_blocks do
 | 
			
		||||
      resource :preview, only: [:show]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    resource :domain_blocks, only: [:show, :create, :destroy]
 | 
			
		||||
 | 
			
		||||
    resource :directory, only: [:show]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user