~cytrogen/masto-fe

abfdafef1ededdb87f018414edd6b25fa9a70525 — Claire 3 years ago f30c5e7 + 4db8230
Merge branch 'main' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/auth/setup_controller.rb`:
  Upstream removed a method close to a glitch-soc theming-related method.
  Removed the method like upstream did.
118 files changed, 888 insertions(+), 371 deletions(-)

M .github/workflows/test-js.yml
M Gemfile
M Gemfile.lock
A app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb
M app/controllers/api/v1/admin/trends/links_controller.rb
M app/controllers/api/v1/admin/trends/statuses_controller.rb
M app/controllers/api/v1/admin/trends/tags_controller.rb
M app/controllers/auth/setup_controller.rb
M app/helpers/application_helper.rb
M app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap
R app/javascript/mastodon/components/{animated_number.jsx => animated_number.tsx}
D app/javascript/mastodon/components/avatar_overlay.jsx
A app/javascript/mastodon/components/avatar_overlay.tsx
R app/javascript/mastodon/components/{gifv.jsx => gifv.tsx}
M app/javascript/mastodon/components/status.jsx
M app/javascript/mastodon/components/status_content.jsx
M app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
M app/javascript/mastodon/features/status/index.jsx
M app/javascript/mastodon/features/ui/components/filter_modal.jsx
M app/javascript/mastodon/features/ui/components/focal_point_modal.jsx
M app/javascript/mastodon/features/ui/components/media_modal.jsx
M app/javascript/mastodon/locales/defaultMessages.json
M app/javascript/mastodon/locales/en.json
M app/javascript/mastodon/locales/es-MX.json
M app/javascript/packs/public.jsx
M app/javascript/styles/mastodon/forms.scss
M app/mailers/notification_mailer.rb
M app/models/account_filter.rb
M app/models/preview_card_provider.rb
A app/serializers/rest/admin/trends/link_serializer.rb
A app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb
A app/serializers/rest/admin/trends/status_serializer.rb
M app/services/notify_service.rb
M app/views/auth/registrations/new.html.haml
M app/views/auth/registrations/rules.html.haml
M app/views/auth/setup/show.html.haml
M app/views/auth/shared/_links.html.haml
A app/views/auth/shared/_progress.html.haml
M config/locales/en.yml
M config/locales/simple_form.an.yml
M config/locales/simple_form.ar.yml
M config/locales/simple_form.ast.yml
M config/locales/simple_form.be.yml
M config/locales/simple_form.bg.yml
M config/locales/simple_form.ca.yml
M config/locales/simple_form.ckb.yml
M config/locales/simple_form.co.yml
M config/locales/simple_form.cs.yml
M config/locales/simple_form.cy.yml
M config/locales/simple_form.da.yml
M config/locales/simple_form.de.yml
M config/locales/simple_form.el.yml
M config/locales/simple_form.en-GB.yml
M config/locales/simple_form.en.yml
M config/locales/simple_form.eo.yml
M config/locales/simple_form.es-AR.yml
M config/locales/simple_form.es-MX.yml
M config/locales/simple_form.es.yml
M config/locales/simple_form.et.yml
M config/locales/simple_form.eu.yml
M config/locales/simple_form.fa.yml
M config/locales/simple_form.fi.yml
M config/locales/simple_form.fo.yml
M config/locales/simple_form.fr-QC.yml
M config/locales/simple_form.fr.yml
M config/locales/simple_form.fy.yml
M config/locales/simple_form.gd.yml
M config/locales/simple_form.gl.yml
M config/locales/simple_form.he.yml
M config/locales/simple_form.hu.yml
M config/locales/simple_form.hy.yml
M config/locales/simple_form.id.yml
M config/locales/simple_form.io.yml
M config/locales/simple_form.is.yml
M config/locales/simple_form.it.yml
M config/locales/simple_form.ja.yml
M config/locales/simple_form.kab.yml
M config/locales/simple_form.ko.yml
M config/locales/simple_form.ku.yml
M config/locales/simple_form.lv.yml
M config/locales/simple_form.my.yml
M config/locales/simple_form.nl.yml
M config/locales/simple_form.nn.yml
M config/locales/simple_form.no.yml
M config/locales/simple_form.oc.yml
M config/locales/simple_form.pl.yml
M config/locales/simple_form.pt-BR.yml
M config/locales/simple_form.pt-PT.yml
M config/locales/simple_form.ro.yml
M config/locales/simple_form.ru.yml
M config/locales/simple_form.sc.yml
M config/locales/simple_form.sco.yml
M config/locales/simple_form.si.yml
M config/locales/simple_form.sk.yml
M config/locales/simple_form.sl.yml
M config/locales/simple_form.sq.yml
M config/locales/simple_form.sr-Latn.yml
M config/locales/simple_form.sr.yml
M config/locales/simple_form.sv.yml
M config/locales/simple_form.th.yml
M config/locales/simple_form.tr.yml
M config/locales/simple_form.uk.yml
M config/locales/simple_form.vi.yml
M config/locales/simple_form.zh-CN.yml
M config/locales/simple_form.zh-HK.yml
M config/locales/simple_form.zh-TW.yml
M config/routes.rb
M jest.config.js
M lib/mastodon/accounts_cli.rb
M package.json
A spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb
M spec/controllers/api/v1/admin/trends/links_controller_spec.rb
M spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb
M spec/controllers/api/v1/admin/trends/tags_controller_spec.rb
M spec/mailers/notification_mailer_spec.rb
M spec/models/account_filter_spec.rb
M spec/services/reblog_service_spec.rb
M yarn.lock
M .github/workflows/test-js.yml => .github/workflows/test-js.yml +4 -0
@@ 9,6 9,8 @@ on:
      - '.nvmrc'
      - '**/*.js'
      - '**/*.jsx'
      - '**/*.ts'
      - '**/*.tsx'
      - '**/*.snap'
      - '.github/workflows/test-js.yml'



@@ 19,6 21,8 @@ on:
      - '.nvmrc'
      - '**/*.js'
      - '**/*.jsx'
      - '**/*.ts'
      - '**/*.tsx'
      - '**/*.snap'
      - '.github/workflows/test-js.yml'


M Gemfile => Gemfile +1 -1
@@ 120,7 120,7 @@ end
group :test do
  gem 'capybara', '~> 3.39'
  gem 'climate_control'
  gem 'faker', '~> 3.1'
  gem 'faker', '~> 3.2'
  gem 'json-schema', '~> 3.0'
  gem 'rack-test', '~> 2.1'
  gem 'rails-controller-testing', '~> 1.0'

M Gemfile.lock => Gemfile.lock +9 -9
@@ 243,7 243,7 @@ GEM
      tzinfo
    excon (0.95.0)
    fabrication (2.30.0)
    faker (3.1.1)
    faker (3.2.0)
      i18n (>= 1.8.11, < 2)
    faraday (1.10.3)
      faraday-em_http (~> 1.0)


@@ 348,19 348,19 @@ GEM
    ipaddress (0.8.3)
    jmespath (1.6.2)
    json (2.6.3)
    json-canonicalization (0.3.0)
    json-canonicalization (0.3.1)
    json-jwt (1.15.3)
      activesupport (>= 4.2)
      aes_key_wrap
      bindata
      httpclient
    json-ld (3.2.3)
    json-ld (3.2.4)
      htmlentities (~> 4.3)
      json-canonicalization (~> 0.3)
      link_header (~> 0.0, >= 0.0.8)
      multi_json (~> 1.15)
      rack (~> 2.2)
      rdf (~> 3.2, >= 3.2.9)
      rack (>= 2.2, < 4)
      rdf (~> 3.2, >= 3.2.10)
    json-ld-preloaded (3.2.2)
      json-ld (~> 3.2)
      rdf (~> 3.2)


@@ 479,7 479,7 @@ GEM
    openssl-signature_algorithm (1.3.0)
      openssl (> 2.0)
    orm_adapter (0.5.0)
    ox (2.14.14)
    ox (2.14.16)
    parallel (1.22.1)
    parser (3.2.2.0)
      ast (~> 2.4.1)


@@ 487,7 487,7 @@ GEM
    pastel (0.8.0)
      tty-color (~> 0.5)
    pg (1.4.6)
    pghero (3.3.1)
    pghero (3.3.2)
      activerecord (>= 6)
    pkg-config (1.5.1)
    posix-spawn (0.3.15)


@@ 557,7 557,7 @@ GEM
      thor (~> 1.0)
    rainbow (3.1.1)
    rake (13.0.6)
    rdf (3.2.9)
    rdf (3.2.10)
      link_header (~> 0.0, >= 0.0.8)
    rdf-normalize (0.5.1)
      rdf (~> 3.2)


@@ 799,7 799,7 @@ DEPENDENCIES
  dotenv-rails (~> 2.8)
  ed25519 (~> 1.3)
  fabrication (~> 2.30)
  faker (~> 3.1)
  faker (~> 3.2)
  fast_blank (~> 1.0)
  fastimage
  fog-core (<= 2.4.0)

A app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb => app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb +72 -0
@@ 0,0 1,72 @@
# frozen_string_literal: true

class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController
  include Authorization

  LIMIT = 100

  before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
  before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
  before_action :set_providers, only: :index

  after_action :verify_authorized
  after_action :insert_pagination_headers, only: :index

  PAGINATION_PARAMS = %i(limit).freeze

  def index
    authorize :preview_card_provider, :index?

    render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
  end

  def approve
    authorize :preview_card_provider, :review?

    provider = PreviewCardProvider.find(params[:id])
    provider.update(trendable: true, reviewed_at: Time.now.utc)
    render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
  end

  def reject
    authorize :preview_card_provider, :review?

    provider = PreviewCardProvider.find(params[:id])
    provider.update(trendable: false, reviewed_at: Time.now.utc)
    render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
  end

  private

  def set_providers
    @providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
  end

  def insert_pagination_headers
    set_pagination_headers(next_path, prev_path)
  end

  def next_path
    api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue?
  end

  def prev_path
    api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty?
  end

  def pagination_max_id
    @providers.last.id
  end

  def pagination_since_id
    @providers.first.id
  end

  def records_continue?
    @providers.size == limit_param(LIMIT)
  end

  def pagination_params(core_params)
    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
  end
end

M app/controllers/api/v1/admin/trends/links_controller.rb => app/controllers/api/v1/admin/trends/links_controller.rb +30 -1
@@ 1,7 1,36 @@
# frozen_string_literal: true

class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
  before_action -> { authorize_if_got_token! :'admin:read' }
  include Authorization

  before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
  before_action -> { authorize_if_got_token! :'admin:write' }, except: :index

  after_action :verify_authorized, except: :index

  def index
    if current_user&.can?(:manage_taxonomies)
      render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer
    else
      super
    end
  end

  def approve
    authorize :preview_card, :review?

    link = PreviewCard.find(params[:id])
    link.update(trendable: true)
    render json: link, serializer: REST::Admin::Trends::LinkSerializer
  end

  def reject
    authorize :preview_card, :review?

    link = PreviewCard.find(params[:id])
    link.update(trendable: false)
    render json: link, serializer: REST::Admin::Trends::LinkSerializer
  end

  private


M app/controllers/api/v1/admin/trends/statuses_controller.rb => app/controllers/api/v1/admin/trends/statuses_controller.rb +30 -1
@@ 1,7 1,36 @@
# frozen_string_literal: true

class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
  before_action -> { authorize_if_got_token! :'admin:read' }
  include Authorization

  before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
  before_action -> { authorize_if_got_token! :'admin:write' }, except: :index

  after_action :verify_authorized, except: :index

  def index
    if current_user&.can?(:manage_taxonomies)
      render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer
    else
      super
    end
  end

  def approve
    authorize [:admin, :status], :review?

    status = Status.find(params[:id])
    status.update(trendable: true)
    render json: status, serializer: REST::Admin::Trends::StatusSerializer
  end

  def reject
    authorize [:admin, :status], :review?

    status = Status.find(params[:id])
    status.update(trendable: false)
    render json: status, serializer: REST::Admin::Trends::StatusSerializer
  end

  private


M app/controllers/api/v1/admin/trends/tags_controller.rb => app/controllers/api/v1/admin/trends/tags_controller.rb +22 -1
@@ 1,7 1,12 @@
# frozen_string_literal: true

class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
  before_action -> { authorize_if_got_token! :'admin:read' }
  include Authorization

  before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
  before_action -> { authorize_if_got_token! :'admin:write' }, except: :index

  after_action :verify_authorized, except: :index

  def index
    if current_user&.can?(:manage_taxonomies)


@@ 11,6 16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
    end
  end

  def approve
    authorize :tag, :review?

    tag = Tag.find(params[:id])
    tag.update(trendable: true, reviewed_at: Time.now.utc)
    render json: tag, serializer: REST::Admin::TagSerializer
  end

  def reject
    authorize :tag, :review?

    tag = Tag.find(params[:id])
    tag.update(trendable: false, reviewed_at: Time.now.utc)
    render json: tag, serializer: REST::Admin::TagSerializer
  end

  private

  def enabled?

M app/controllers/auth/setup_controller.rb => app/controllers/auth/setup_controller.rb +3 -16
@@ 11,15 11,7 @@ class Auth::SetupController < ApplicationController

  skip_before_action :require_functional!

  def show
    flash.now[:notice] = begin
      if @user.pending?
        I18n.t('devise.registrations.signed_up_but_pending')
      else
        I18n.t('devise.registrations.signed_up_but_unconfirmed')
      end
    end
  end
  def show; end

  def update
    # This allows updating the e-mail without entering a password as is required


@@ 27,14 19,13 @@ class Auth::SetupController < ApplicationController
    # that were not confirmed yet

    if @user.update(user_params)
      redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
      @user.resend_confirmation_instructions unless @user.confirmed?
      redirect_to auth_setup_path, notice: I18n.t('auth.setup.new_confirmation_instructions_sent')
    else
      render :show
    end
  end

  helper_method :missing_email?

  private

  def require_unconfirmed_or_pending!


@@ 53,10 44,6 @@ class Auth::SetupController < ApplicationController
    params.require(:user).permit(:email)
  end

  def missing_email?
    truthy_param?(:missing_email)
  end

  def set_pack
    use_pack 'auth'
  end

M app/helpers/application_helper.rb => app/helpers/application_helper.rb +4 -0
@@ 117,6 117,10 @@ module ApplicationHelper
    content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
  end

  def check_icon
    content_tag(:svg, tag(:path, 'fill-rule': 'evenodd', 'clip-rule': 'evenodd', d: 'M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'), xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 20 20', fill: 'currentColor')
  end

  def visibility_icon(status)
    if status.public_visibility?
      fa_icon('globe', title: I18n.t('statuses.visibilities.public'))

M app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap => app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap +2 -4
@@ 3,6 3,8 @@
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
<div
  className="account__avatar-overlay"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
  style={
    {
      "height": 46,


@@ 15,8 17,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
  >
    <div
      className="account__avatar"
      onMouseEnter={[Function]}
      onMouseLeave={[Function]}
      style={
        {
          "height": "36px",


@@ 35,8 35,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
  >
    <div
      className="account__avatar"
      onMouseEnter={[Function]}
      onMouseLeave={[Function]}
      style={
        {
          "height": "24px",

R app/javascript/mastodon/components/animated_number.jsx => app/javascript/mastodon/components/animated_number.tsx +43 -61
@@ 1,11 1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import ShortNumber from 'mastodon/components/short_number';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
import { reduceMotion } from 'mastodon/initial_state';
import React, { useCallback, useState } from 'react';
import ShortNumber from './short_number';
import { TransitionMotion, spring } from 'react-motion';
import { reduceMotion } from '../initial_state';

const obfuscatedCount = count => {
const obfuscatedCount = (count: number) => {
  if (count < 0) {
    return 0;
  } else if (count <= 1) {


@@ 15,62 13,46 @@ const obfuscatedCount = count => {
  }
};

export default class AnimatedNumber extends React.PureComponent {

  static propTypes = {
    value: PropTypes.number.isRequired,
    obfuscate: PropTypes.bool,
  };

  state = {
    direction: 1,
  };

  componentWillReceiveProps (nextProps) {
    if (nextProps.value > this.props.value) {
      this.setState({ direction: 1 });
    } else if (nextProps.value < this.props.value) {
      this.setState({ direction: -1 });
    }
type Props = {
  value: number;
  obfuscate?: boolean;
}
export const AnimatedNumber: React.FC<Props> = ({
  value,
  obfuscate,
})=> {
  const [previousValue, setPreviousValue] = useState(value);
  const [direction, setDirection] = useState<1|-1>(1);

  if (previousValue !== value) {
    setPreviousValue(value);
    setDirection(value > previousValue ? 1 : -1);
  }

  willEnter = () => {
    const { direction } = this.state;

    return { y: -1 * direction };
  };

  willLeave = () => {
    const { direction } = this.state;

    return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) };
  };
  const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
  const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]);

  render () {
    const { value, obfuscate } = this.props;
    const { direction } = this.state;

    if (reduceMotion) {
      return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />;
    }

    const styles = [{
      key: `${value}`,
      data: value,
      style: { y: spring(0, { damping: 35, stiffness: 400 }) },
    }];

    return (
      <TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
        {items => (
          <span className='animated-number'>
            {items.map(({ key, data, style }) => (
              <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
            ))}
          </span>
        )}
      </TransitionMotion>
    );
  if (reduceMotion) {
    return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />;
  }

}
  const styles = [{
    key: `${value}`,
    data: value,
    style: { y: spring(0, { damping: 35, stiffness: 400 }) },
  }];

  return (
    <TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
      {items => (
        <span className='animated-number'>
          {items.map(({ key, data, style }) => (
            <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
          ))}
        </span>
      )}
    </TransitionMotion>
  );
};

export default AnimatedNumber;

D app/javascript/mastodon/components/avatar_overlay.jsx => app/javascript/mastodon/components/avatar_overlay.jsx +0 -51
@@ 1,51 0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state';
import Avatar from './avatar';

export default class AvatarOverlay extends React.PureComponent {

  static propTypes = {
    account: ImmutablePropTypes.map.isRequired,
    friend: ImmutablePropTypes.map.isRequired,
    animate: PropTypes.bool,
    size: PropTypes.number,
    baseSize: PropTypes.number,
    overlaySize: PropTypes.number,
  };

  static defaultProps = {
    animate: autoPlayGif,
    size: 46,
    baseSize: 36,
    overlaySize: 24,
  };

  state = {
    hovering: false,
  };

  handleMouseEnter = () => {
    if (this.props.animate) return;
    this.setState({ hovering: true });
  };

  handleMouseLeave = () => {
    if (this.props.animate) return;
    this.setState({ hovering: false });
  };

  render() {
    const { account, friend, animate, size, baseSize, overlaySize } = this.props;
    const { hovering } = this.state;

    return (
      <div className='account__avatar-overlay' style={{ width: size, height: size }}>
        <div className='account__avatar-overlay-base'><Avatar animate={hovering || animate} account={account} size={baseSize} /></div>
        <div className='account__avatar-overlay-overlay'><Avatar animate={hovering || animate} account={friend} size={overlaySize} /></div>
      </div>
    );
  }

}

A app/javascript/mastodon/components/avatar_overlay.tsx => app/javascript/mastodon/components/avatar_overlay.tsx +51 -0
@@ 0,0 1,51 @@
import React from 'react';
import type { Account } from '../../types/resources';
import { useHovering } from '../../hooks/useHovering';
import { autoPlayGif } from '../initial_state';

type Props = {
  account: Account;
  friend: Account;
  size?: number;
  baseSize?: number;
  overlaySize?: number;
};

export const AvatarOverlay: React.FC<Props> = ({
  account,
  friend,
  size = 46,
  baseSize = 36,
  overlaySize = 24,
}) => {
  const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif);
  const accountSrc = hovering ? account?.get('avatar') : account?.get('avatar_static');
  const friendSrc = hovering ? friend?.get('avatar') : friend?.get('avatar_static');

  return (
    <div
      className='account__avatar-overlay' style={{ width: size, height: size }}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <div className='account__avatar-overlay-base'>
        <div
          className='account__avatar'
          style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
        >
          {accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
        </div>
      </div>
      <div className='account__avatar-overlay-overlay'>
        <div
          className='account__avatar'
          style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
        >
          {friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
        </div>
      </div>
    </div>
  );
};

export default AvatarOverlay;

R app/javascript/mastodon/components/gifv.jsx => app/javascript/mastodon/components/gifv.tsx +52 -60
@@ 1,76 1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';

export default class GIFV extends React.PureComponent {

  static propTypes = {
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
    lang: PropTypes.string,
    width: PropTypes.number,
    height: PropTypes.number,
    onClick: PropTypes.func,
  };

  state = {
    loading: true,
  };

  handleLoadedData = () => {
    this.setState({ loading: false });
  };
type Props = {
  src: string;
  key: string;
  alt?: string;
  lang?: string;
  width: number;
  height: number;
  onClick?: () => void;
}

  componentWillReceiveProps (nextProps) {
    if (nextProps.src !== this.props.src) {
      this.setState({ loading: true });
    }
  }
export const GIFV: React.FC<Props> = ({
  src,
  alt,
  lang,
  width,
  height,
  onClick,
})=> {
  const [loading, setLoading] = useState(true);

  handleClick = e => {
    const { onClick } = this.props;
  const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> = useCallback(() => {
    setLoading(false);
  }, [setLoading]);

  const handleClick: React.MouseEventHandler = useCallback((e) => {
    if (onClick) {
      e.stopPropagation();
      onClick();
    }
  };
  }, [onClick]);

  render () {
    const { src, width, height, alt, lang } = this.props;
    const { loading } = this.state;

    return (
      <div className='gifv' style={{ position: 'relative' }}>
        {loading && (
          <canvas
            width={width}
            height={height}
            role='button'
            tabIndex={0}
            aria-label={alt}
            title={alt}
            lang={lang}
            onClick={this.handleClick}
          />
        )}

        <video
          src={src}
  return (
    <div className='gifv' style={{ position: 'relative' }}>
      {loading && (
        <canvas
          width={width}
          height={height}
          role='button'
          tabIndex={0}
          aria-label={alt}
          title={alt}
          lang={lang}
          muted
          loop
          autoPlay
          playsInline
          onClick={this.handleClick}
          onLoadedData={this.handleLoadedData}
          style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
          onClick={handleClick}
        />
      </div>
    );
  }
      )}

}
      <video
        src={src}
        role='button'
        tabIndex={0}
        aria-label={alt}
        title={alt}
        lang={lang}
        muted
        loop
        autoPlay
        playsInline
        onClick={handleClick}
        onLoadedData={handleLoadedData}
        style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
      />
    </div>
  );
};

export default GIFV;

M app/javascript/mastodon/components/status.jsx => app/javascript/mastodon/components/status.jsx +1 -1
@@ 541,7 541,7 @@ class Status extends ImmutablePureComponent {
              expanded={!status.get('hidden')}
              onExpandedToggle={this.handleExpandedToggle}
              onTranslate={this.handleTranslate}
              collapsable
              collapsible
              onCollapsedToggle={this.handleCollapsedToggle}
            />


M app/javascript/mastodon/components/status_content.jsx => app/javascript/mastodon/components/status_content.jsx +3 -3
@@ 65,7 65,7 @@ class StatusContent extends React.PureComponent {
    onExpandedToggle: PropTypes.func,
    onTranslate: PropTypes.func,
    onClick: PropTypes.func,
    collapsable: PropTypes.bool,
    collapsible: PropTypes.bool,
    onCollapsedToggle: PropTypes.func,
    languages: ImmutablePropTypes.map,
    intl: PropTypes.object,


@@ 112,10 112,10 @@ class StatusContent extends React.PureComponent {
    }

    if (status.get('collapsed', null) === null && onCollapsedToggle) {
      const { collapsable, onClick } = this.props;
      const { collapsible, onClick } = this.props;

      const collapsed =
          collapsable
          collapsible
          && onClick
          && node.clientHeight > MAX_HEIGHT
          && status.get('spoiler_text').length === 0;

M app/javascript/mastodon/features/direct_timeline/components/conversation.jsx => app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +1 -1
@@ 165,7 165,7 @@ class Conversation extends ImmutablePureComponent {
              onClick={this.handleClick}
              expanded={!lastStatus.get('hidden')}
              onExpandedToggle={this.handleShowMore}
              collapsable
              collapsible
            />

            {lastStatus.get('media_attachments').size > 0 && (

M app/javascript/mastodon/features/status/index.jsx => app/javascript/mastodon/features/status/index.jsx +6 -4
@@ 69,6 69,7 @@ const messages = defineMessages({
  redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
  revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
  hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
  statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}' },
  detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },


@@ 166,13 167,14 @@ const truncate = (str, num) => {
  }
};

const titleFromStatus = status => {
const titleFromStatus = (intl, status) => {
  const displayName = status.getIn(['account', 'display_name']);
  const username = status.getIn(['account', 'username']);
  const prefix = displayName.trim().length === 0 ? username : displayName;
  const user = displayName.trim().length === 0 ? username : displayName;
  const text = status.get('search_index');
  const attachmentCount = status.get('media_attachments').size;

  return `${prefix}: "${truncate(text, 30)}"`;
  return text ? `${user}: "${truncate(text, 30)}"` : intl.formatMessage(messages.statusTitleWithAttachments, { user, attachmentCount });
};

class Status extends ImmutablePureComponent {


@@ 670,7 672,7 @@ class Status extends ImmutablePureComponent {
        </ScrollContainer>

        <Helmet>
          <title>{titleFromStatus(status)}</title>
          <title>{titleFromStatus(intl, status)}</title>
          <meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
        </Helmet>
      </Column>

M app/javascript/mastodon/features/ui/components/filter_modal.jsx => app/javascript/mastodon/features/ui/components/filter_modal.jsx +1 -1
@@ 131,4 131,4 @@ class FilterModal extends ImmutablePureComponent {

}

export default connect(injectIntl(FilterModal));
export default connect()(injectIntl(FilterModal));

M app/javascript/mastodon/features/ui/components/focal_point_modal.jsx => app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +1 -1
@@ 383,7 383,7 @@ class FocalPointModal extends ImmutablePureComponent {
            {focals && (
              <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
                {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
                {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
                {media.get('type') === 'gifv' && <GIFV src={media.get('url')} key={media.get('url')} width={width} height={height} />}

                <div className='focal-point__preview'>
                  <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>

M app/javascript/mastodon/features/ui/components/media_modal.jsx => app/javascript/mastodon/features/ui/components/media_modal.jsx +1 -1
@@ 186,7 186,7 @@ class MediaModal extends ImmutablePureComponent {
            src={image.get('url')}
            width={width}
            height={height}
            key={image.get('preview_url')}
            key={image.get('url')}
            alt={image.get('description')}
            lang={language}
            onClick={this.toggleNavigation}

M app/javascript/mastodon/locales/defaultMessages.json => app/javascript/mastodon/locales/defaultMessages.json +21 -0
@@ 3733,6 3733,10 @@
        "id": "status.show_less_all"
      },
      {
        "defaultMessage": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}",
        "id": "status.title.with_attachments"
      },
      {
        "defaultMessage": "Detailed conversation view",
        "id": "status.detailed_status"
      },


@@ 4354,5 4358,22 @@
      }
    ],
    "path": "app/javascript/mastodon/features/video/index.json"
  },
  {
    "descriptors": [
      {
        "defaultMessage": "That username is taken. Try another",
        "id": "username.taken"
      },
      {
        "defaultMessage": "Password confirmation exceeds the maximum password length",
        "id": "password_confirmation.exceeds_maxlength"
      },
      {
        "defaultMessage": "Password confirmation does not match",
        "id": "password_confirmation.mismatching"
      }
    ],
    "path": "app/javascript/packs/public.json"
  }
]
\ No newline at end of file

M app/javascript/mastodon/locales/en.json => app/javascript/mastodon/locales/en.json +4 -0
@@ 443,6 443,8 @@
  "notifications_permission_banner.enable": "Enable desktop notifications",
  "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
  "notifications_permission_banner.title": "Never miss a thing",
  "password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length",
  "password_confirmation.mismatching": "Password confirmation does not match",
  "picture_in_picture.restore": "Put it back",
  "poll.closed": "Closed",
  "poll.refresh": "Refresh",


@@ 598,6 600,7 @@
  "status.show_more": "Show more",
  "status.show_more_all": "Show more for all",
  "status.show_original": "Show original",
  "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}",
  "status.translate": "Translate",
  "status.translated_from_with": "Translated from {lang} using {provider}",
  "status.uncached_media_warning": "Not available",


@@ 650,6 653,7 @@
  "upload_modal.preview_label": "Preview ({ratio})",
  "upload_progress.label": "Uploading...",
  "upload_progress.processing": "Processing…",
  "username.taken": "That username is taken. Try another",
  "video.close": "Close video",
  "video.download": "Download file",
  "video.exit_fullscreen": "Exit full screen",

M app/javascript/mastodon/locales/es-MX.json => app/javascript/mastodon/locales/es-MX.json +1 -0
@@ 597,6 597,7 @@
  "status.show_more": "Mostrar más",
  "status.show_more_all": "Mostrar más para todo",
  "status.show_original": "Mostrar original",
  "status.title.with_attachments": "{user} publicó {attachmentCount, plural, one {un archivo adjunto} other {{attachmentCount} archivos adjuntos}}",
  "status.translate": "Traducir",
  "status.translated_from_with": "Traducido del {lang} usando {provider}",
  "status.uncached_media_warning": "No disponible",

M app/javascript/packs/public.jsx => app/javascript/packs/public.jsx +33 -17
@@ 4,6 4,15 @@ import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
import 'cocoon-js-vanilla';
import axios from 'axios';
import { throttle } from 'lodash';
import { defineMessages } from 'react-intl';

const messages = defineMessages({
  usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' },
  passwordExceedsLength: { id: 'password_confirmation.exceeds_maxlength', defaultMessage: 'Password confirmation exceeds the maximum password length' },
  passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' },
});

start();



@@ 13,7 22,7 @@ function main() {
  const { delegate } = require('@rails/ujs');
  const emojify = require('../mastodon/features/emoji/emoji').default;
  const { getLocale } = require('../mastodon/locales');
  const { messages } = getLocale();
  const { localeData } = getLocale();
  const React = require('react');
  const ReactDOM = require('react-dom');
  const { createBrowserHistory } = require('history');


@@ 58,6 67,11 @@ function main() {
      hour12: false,
    });

    const formatMessage = ({ id, defaultMessage }, values) => {
      const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale);
      return messageFormat.format(values);
    };

    [].forEach.call(document.querySelectorAll('.emojify'), (content) => {
      content.innerHTML = emojify(content.innerHTML);
    });


@@ 77,7 91,7 @@ function main() {
        date.getMonth() === today.getMonth() &&
        date.getFullYear() === today.getFullYear();
    };
    const todayFormat = new IntlMessageFormat(messages['relative_format.today'] || 'Today at {time}', locale);
    const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale);

    [].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => {
      const datetime = new Date(content.getAttribute('datetime'));


@@ 103,7 117,7 @@ function main() {
      const timeGiven = content.getAttribute('datetime').includes('T');
      content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime);
      content.textContent = timeAgoString({
        formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values),
        formatMessage,
        formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date),
      }, datetime, now, now.getFullYear(), timeGiven);
    });


@@ 133,17 147,19 @@ function main() {
      scrollToDetailedStatus();
    }

    delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => {
      const password = document.getElementById('registration_user_password');
      const confirmation = document.getElementById('registration_user_password_confirmation');
      if (confirmation.value && confirmation.value.length > password.maxLength) {
        confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format());
      } else if (password.value && password.value !== confirmation.value) {
        confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format());
    delegate(document, '#user_account_attributes_username', 'input', throttle(() => {
      const username = document.getElementById('user_account_attributes_username');

      if (username.value && username.value.length > 0) {
        axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => {
          username.setCustomValidity(formatMessage(messages.usernameTaken));
        }).catch(() => {
          username.setCustomValidity('');
        });
      } else {
        confirmation.setCustomValidity('');
        username.setCustomValidity('');
      }
    });
    }, 500, { leading: false, trailing: true }));

    delegate(document, '#user_password,#user_password_confirmation', 'input', () => {
      const password = document.getElementById('user_password');


@@ 151,9 167,9 @@ function main() {
      if (!confirmation) return;

      if (confirmation.value && confirmation.value.length > password.maxLength) {
        confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.exceeds_maxlength'] || 'Password confirmation exceeds the maximum password length', locale)).format());
        confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength));
      } else if (password.value && password.value !== confirmation.value) {
        confirmation.setCustomValidity((new IntlMessageFormat(messages['password_confirmation.mismatching'] || 'Password confirmation does not match', locale)).format());
        confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch));
      } else {
        confirmation.setCustomValidity('');
      }


@@ 167,10 183,10 @@ function main() {

      if (statusEl.dataset.spoiler === 'expanded') {
        statusEl.dataset.spoiler = 'folded';
        this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
        this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format();
      } else {
        statusEl.dataset.spoiler = 'expanded';
        this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
        this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format();
      }

      return false;


@@ 178,7 194,7 @@ function main() {

    [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
      const statusEl = spoilerLink.parentNode.parentNode;
      const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
      const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more');
      spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
    });
  });

M app/javascript/styles/mastodon/forms.scss => app/javascript/styles/mastodon/forms.scss +86 -0
@@ 1112,3 1112,89 @@ code {
    white-space: nowrap;
  }
}

.progress-tracker {
  display: flex;
  align-items: center;
  padding-bottom: 30px;
  margin-bottom: 30px;

  li {
    flex: 0 0 auto;
    position: relative;
  }

  .separator {
    height: 2px;
    background: $ui-base-lighter-color;
    flex: 1 1 auto;

    &.completed {
      background: $highlight-text-color;
    }
  }

  .circle {
    box-sizing: border-box;
    position: relative;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 2px solid $ui-base-lighter-color;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;

    svg {
      width: 16px;
    }
  }

  .label {
    position: absolute;
    font-size: 14px;
    font-weight: 500;
    color: $secondary-text-color;
    padding-top: 10px;
    text-align: center;
    width: 100px;
    left: 50%;
    transform: translateX(-50%);
  }

  li:first-child .label {
    left: auto;
    inset-inline-start: 0;
    text-align: start;
    transform: none;
  }

  li:last-child .label {
    left: auto;
    inset-inline-end: 0;
    text-align: end;
    transform: none;
  }

  .active .circle {
    border-color: $highlight-text-color;

    &::before {
      content: '';
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: $highlight-text-color;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }

  .completed .circle {
    border-color: $highlight-text-color;
    background: $highlight-text-color;
  }
}

M app/mailers/notification_mailer.rb => app/mailers/notification_mailer.rb +5 -5
@@ 14,7 14,7 @@ class NotificationMailer < ApplicationMailer

    locale_for_account(@me) do
      thread_by_conversation(@status.conversation)
      mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
      mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
    end
  end



@@ 25,7 25,7 @@ class NotificationMailer < ApplicationMailer
    return unless @me.user.functional?

    locale_for_account(@me) do
      mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
      mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
    end
  end



@@ 38,7 38,7 @@ class NotificationMailer < ApplicationMailer

    locale_for_account(@me) do
      thread_by_conversation(@status.conversation)
      mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
      mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
    end
  end



@@ 51,7 51,7 @@ class NotificationMailer < ApplicationMailer

    locale_for_account(@me) do
      thread_by_conversation(@status.conversation)
      mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
      mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
    end
  end



@@ 62,7 62,7 @@ class NotificationMailer < ApplicationMailer
    return unless @me.user.functional?

    locale_for_account(@me) do
      mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
      mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
    end
  end


M app/models/account_filter.rb => app/models/account_filter.rb +1 -1
@@ 55,7 55,7 @@ class AccountFilter
    when 'by_domain'
      Account.where(domain: value.to_s.strip)
    when 'username'
      Account.matches_username(value.to_s.strip)
      Account.matches_username(value.to_s.strip.delete_prefix('@'))
    when 'display_name'
      Account.matches_display_name(value.to_s.strip)
    when 'email'

M app/models/preview_card_provider.rb => app/models/preview_card_provider.rb +1 -0
@@ 18,6 18,7 @@
#

class PreviewCardProvider < ApplicationRecord
  include Paginable
  include DomainNormalizable
  include Attachmentable


A app/serializers/rest/admin/trends/link_serializer.rb => app/serializers/rest/admin/trends/link_serializer.rb +9 -0
@@ 0,0 1,9 @@
# frozen_string_literal: true

class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer
  attributes :id, :requires_review

  def requires_review
    object.requires_review?
  end
end

A app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb => app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb +10 -0
@@ 0,0 1,10 @@
# frozen_string_literal: true

class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer
  attributes :id, :domain, :trendable, :reviewed_at,
             :requested_review_at, :requires_review

  def requires_review
    object.requires_review?
  end
end

A app/serializers/rest/admin/trends/status_serializer.rb => app/serializers/rest/admin/trends/status_serializer.rb +9 -0
@@ 0,0 1,9 @@
# frozen_string_literal: true

class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer
  attributes :requires_review

  def requires_review
    object.requires_review?
  end
end

M app/services/notify_service.rb => app/services/notify_service.rb +1 -0
@@ 7,6 7,7 @@ class NotifyService < BaseService
    admin.report
    admin.sign_up
    update
    poll
  ).freeze

  def call(recipient, type, activity)

M app/views/auth/registrations/new.html.haml => app/views/auth/registrations/new.html.haml +6 -2
@@ 5,6 5,8 @@
  = render partial: 'shared/og', locals: { description: description_for_sign_up }

= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f|
  = render 'auth/shared/progress', stage: 'details'

  %h1.title= t('auth.sign_up.title', domain: site_hostname)
  %p.lead= t('auth.sign_up.preamble')



@@ 18,7 20,7 @@
  .fields-group
    = f.simple_fields_for :account do |ff|
      = ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.display_name'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.display_name') }
      = ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false
      = ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}"
    = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' }, hint: false
    = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: false
    = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' }, hint: false


@@ 26,9 28,11 @@
    = f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: 'Website'), autocomplete: 'off' }

  - if approved_registrations? && !@invite.present?
    %p.lead= t('auth.sign_up.manual_review', domain: site_hostname)

    .fields-group
      = f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
        = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text
        = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: Setting.require_invite_text, label: false, hint: false


  = hidden_field_tag :accept, params[:accept]

M app/views/auth/registrations/rules.html.haml => app/views/auth/registrations/rules.html.haml +2 -0
@@ 5,6 5,8 @@
  = render partial: 'shared/og', locals: { description: description_for_sign_up }

.simple_form
  = render 'auth/shared/progress', stage: 'rules'

  %h1.title= t('auth.rules.title')
  %p.lead= t('auth.rules.preamble', domain: site_hostname)


M app/views/auth/setup/show.html.haml => app/views/auth/setup/show.html.haml +14 -12
@@ 1,20 1,22 @@
- content_for :page_title do
  = t('auth.setup.title')

- if missing_email?
  = simple_form_for(@user, url: auth_setup_path) do |f|
    = render 'shared/error_messages', object: @user
= simple_form_for(@user, url: auth_setup_path) do |f|
  = render 'auth/shared/progress', stage: 'confirm'

    .fields-group
      %p.hint= t('auth.setup.email_below_hint_html')
  %h1.title= t('auth.setup.title')
  %p.lead= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email))

    .fields-group
      = f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' }
  = render 'shared/error_messages', object: @user

    .actions
      = f.submit t('admin.accounts.change_email.label'), class: 'button'
- else
  .simple_form
    %p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email))
  %p.lead
    %strong= t('auth.setup.link_not_received')
  %p.lead= t('auth.setup.email_below_hint_html')

  .fields-group
    = f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' }

  .actions
    = f.submit t('auth.resend_confirmation'), class: 'button'

.form-footer= render 'auth/shared/links'

M app/views/auth/shared/_links.html.haml => app/views/auth/shared/_links.html.haml +1 -1
@@ 14,5 14,5 @@
  - if controller_name != 'confirmations' && (!user_signed_in? || !current_user.confirmed? || current_user.unconfirmed_email.present?)
    %li= link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path

  - if user_signed_in? && controller_name != 'setup'
  - if user_signed_in?
    %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete }

A app/views/auth/shared/_progress.html.haml => app/views/auth/shared/_progress.html.haml +25 -0
@@ 0,0 1,25 @@
- progress_index = { rules: 0, details: 1, confirm: 2 }[stage.to_sym]

%ol.progress-tracker
  %li{ class: progress_index.positive? ? 'completed' : 'active' }
    .circle
      - if progress_index.positive?
        = check_icon
    .label= t('auth.progress.rules')
  %li.separator{ class: progress_index.positive? ? 'completed' : nil }
  %li{ class: [progress_index > 1 && 'completed', progress_index == 1 && 'active'] }
    .circle
      - if progress_index > 1
        = check_icon
    .label= t('auth.progress.details')
  %li.separator{ class: progress_index > 1 ? 'completed' : nil }
  %li{ class: [progress_index > 2 && 'completed', progress_index == 2 && 'active'] }
    .circle
      - if progress_index > 2
        = check_icon
    .label= t('auth.progress.confirm')
  - if approved_registrations?
    %li.separator{ class: progress_index > 2 ? 'completed' : nil }
    %li
      .circle
      .label= t('auth.progress.review')

M config/locales/en.yml => config/locales/en.yml +15 -7
@@ 125,8 125,8 @@ en:
      removed_header_msg: Successfully removed %{username}'s header image
      resend_confirmation:
        already_confirmed: This user is already confirmed
        send: Resend confirmation email
        success: Confirmation email successfully sent!
        send: Resend confirmation link
        success: Confirmation link successfully sent!
      reset: Reset
      reset_password: Reset password
      resubscribe: Resubscribe


@@ 988,7 988,7 @@ en:
      prefix_invited_by_user: "@%{name} invites you to join this server of Mastodon!"
      prefix_sign_up: Sign up on Mastodon today!
      suffix: With an account, you will be able to follow people, post updates and exchange messages with users from any Mastodon server and more!
    didnt_get_confirmation: Didn't receive confirmation instructions?
    didnt_get_confirmation: Didn't receive a confirmation link?
    dont_have_your_security_key: Don't have your security key?
    forgot_password: Forgot your password?
    invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.


@@ 1001,12 1001,17 @@ en:
    migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
    or_log_in_with: Or log in with
    privacy_policy_agreement_html: I have read and agree to the <a href="%{privacy_policy_path}" target="_blank">privacy policy</a>
    progress:
      confirm: Confirm e-mail
      details: Your details
      review: Our review
      rules: Accept rules
    providers:
      cas: CAS
      saml: SAML
    register: Sign up
    registration_closed: "%{instance} is not accepting new members"
    resend_confirmation: Resend confirmation instructions
    resend_confirmation: Resend confirmation link
    reset_password: Reset password
    rules:
      accept: Accept


@@ 1016,13 1021,16 @@ en:
    security: Security
    set_new_password: Set new password
    setup:
      email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail.
      email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings.
      title: Setup
      email_below_hint_html: Check your spam folder, or request another one. You can correct your e-mail address if it's wrong.
      email_settings_hint_html: Click the link we sent you to verify %{email}. We'll wait right here.
      link_not_received: Didn't get a link?
      new_confirmation_instructions_sent: You will receive a new e-mail with the confirmation link in a few minutes!
      title: Check your inbox
    sign_in:
      preamble_html: Sign in with your <strong>%{domain}</strong> credentials. If your account is hosted on a different server, you will not be able to log in here.
      title: Sign in to %{domain}
    sign_up:
      manual_review: Sign-ups on %{domain} go through manual review by our moderators. To help us process your registration, write a bit about yourself and why you want an account on %{domain}.
      preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted.
      title: Let's get you set up on %{domain}.
    status:

M config/locales/simple_form.an.yml => config/locales/simple_form.an.yml +0 -1
@@ 59,7 59,6 @@ an:
        setting_show_application: L'aplicación que utiliza vusté pa publicar publicacions s'amostrará en a vista detallada d'as suyas publicacions
        setting_use_blurhash: Los gradientes se basan en as colors d'as imachens amagadas pero fendo borrosos los detalles
        setting_use_pending_items: Amagar nuevos estaus dezaga d'un clic en cuenta de desplazar automaticament lo feed
        username: Lo tuyo nombre d'usuario será solo en %{domain}
        whole_word: Quan la parola clau u frase ye nomás alfanumerica, nomás será aplicau si concuerda con tota la parola
      domain_allow:
        domain: Este dominio podrá obtener datos d'este servidor y los datos dentrants serán procesaus y archivados

M config/locales/simple_form.ar.yml => config/locales/simple_form.ar.yml +0 -1
@@ 59,7 59,6 @@ ar:
        setting_show_application: سيُعرَض اسم التطبيق الذي تستخدمه عند النشر في العرض المفصّل لمنشوراتك
        setting_use_blurhash: الألوان التدرّجية مبنية على ألوان المرئيات المخفية ولكنها تحجب كافة التفاصيل
        setting_use_pending_items: إخفاء تحديثات الخط وراء نقرة بدلًا مِن التمرير التلقائي للتدفق
        username: اسم المستخدم الخاص بك سوف يكون فريدا مِن نوعه على %{domain}
        whole_word: إذا كانت الكلمة أو العبارة مكونة من أرقام وحروف فقط سوف يتم تطبيقها فقط عند مطابقة الكلمة ككل
      domain_allow:
        domain: سيكون بإمكان هذا النطاق جلب البيانات من هذا الخادم ومعالجة وتخزين البيانات الواردة منه

M config/locales/simple_form.ast.yml => config/locales/simple_form.ast.yml +0 -1
@@ 35,7 35,6 @@ ast:
        setting_noindex: Afeuta al perfil públicu ya a les páxines de los artículos
        setting_show_application: L'aplicación qu'uses pa espublizar apaez na vista detallada de los tos artículos
        setting_use_blurhash: Los dilíos básense nos colores del conteníu multimedia anubríu mas desenfonca los detalles
        username: 'El nome d''usuariu va ser únicu en: %{domain}'
      featured_tag:
        name: 'Equí tán dalgunes de les etiquetes qu''usesti apocayá:'
      form_admin_settings:

M config/locales/simple_form.be.yml => config/locales/simple_form.be.yml +0 -1
@@ 59,7 59,6 @@ be:
        setting_show_application: Праграма, праз якую вы ствараеце допісы, будзе паказвацца ў падрабязнасцях пра допісы
        setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі
        setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі
        username: Ваша імя карыстальніка будзе ўнікальным на %{domain}
        whole_word: Калі ключавое слова ці фраза складаецца толькі з літар і лічбаў, яно будзе ўжытае толькі калі супадае з усім словам
      domain_allow:
        domain: Гэты дамен зможа атрымліваць даныя з гэтага сервера. Даныя з гэтага дамену будуць апрацаваныя ды захаваныя

M config/locales/simple_form.bg.yml => config/locales/simple_form.bg.yml +0 -1
@@ 59,7 59,6 @@ bg:
        setting_show_application: Приложението, което ползвате за публикуване, ще се показва в подробностите на публикацията ви
        setting_use_blurhash: Преливането е въз основа на цветовете на скритите визуализации, но се замъгляват подробностите
        setting_use_pending_items: Да се показват обновявания на часовата ос само след щракване вместо автоматично превъртане на инфоканала
        username: Вашето потребителско име ще е неповторимо в %{domain}
        whole_word: Ако ключовата дума или фраза е само буквеноцифрена, то ще се приложи само, ако съвпадне с цялата дума
      domain_allow:
        domain: Домейнът ще може да извлича данни от този сървър и входящите данни от него ще се обработят и съхранят

M config/locales/simple_form.ca.yml => config/locales/simple_form.ca.yml +0 -1
@@ 59,7 59,6 @@ ca:
        setting_show_application: L'aplicació que fas servir per a publicar es mostrarà a la vista detallada dels teus tuts
        setting_use_blurhash: Els degradats es basen en els colors de les imatges ocultes, però n'enfosqueixen els detalls
        setting_use_pending_items: Amaga les actualitzacions de la línia de temps després de fer un clic, en lloc de desplaçar-les automàticament
        username: El teu nom d'usuari serà únic a %{domain}
        whole_word: Quan la paraula clau o la frase sigui només alfanumèrica, s'aplicarà si coincideix amb la paraula sencera
      domain_allow:
        domain: Aquest domini podrà obtenir dades d’aquest servidor i les dades entrants d’aquests seran processades i emmagatzemades

M config/locales/simple_form.ckb.yml => config/locales/simple_form.ckb.yml +0 -1
@@ 51,7 51,6 @@ ckb:
        setting_show_application: بەرنامەیەک کە بە یارمەتیت توت دەکەیت، لە دیمەنی وردی توتەکان پیشان دەدرێت
        setting_use_blurhash: سێبەرەکان لە سەر بنەمای ڕەنگەکانی بەکارهاتوو لە وێنە داشاراوەکان دروست دەبن بەڵام وردەزانیاری وێنە تێیدا ڕوون نییە
        setting_use_pending_items: لەجیاتی ئەوەی بە خۆکارانە کێشان هەبێت لە نووسراوەکان بە کرتەیەک بەڕۆژبوونی پێرستی نووسراوەکان بشارەوە
        username: ناوی بەکارهێنەری ئێوە لەسەر %{domain} یەکتا دەبێت
        whole_word: کاتێک کلیل‌وشە بریتییە لە ژمارە و پیت، تنەها کاتێک پەیدا دەبێت کە لەگەڵ گشتی وشە لە نێو دەقەکە هاوئاهەنگ بێت، نە تەنها لەگەڵ بەشێک لە وشە
      domain_allow:
        domain: ئەم دۆمەینە دەتوانێت دراوە لە ئەم ڕاژە وەربگرێت و دراوەی ئەم دۆمەینە لێرە ڕێکدەخرین و پاشکەوت دەکرێن

M config/locales/simple_form.co.yml => config/locales/simple_form.co.yml +0 -1
@@ 49,7 49,6 @@ co:
        setting_show_application: L'applicazione chì voi utilizate per mandà statuti sarà affissata indè a vista ditagliata di quelli
        setting_use_blurhash: I digradati blurhash sò basati nant'à i culori di u ritrattu piattatu ma senza i ditagli
        setting_use_pending_items: Clicchi per messe à ghjornu i statuti invece di fà sfilà a linea autumaticamente
        username: U vostru cugnome sarà unicu nant'à %{domain}
        whole_word: Quandu a parolla o a frasa sana hè alfanumerica, sarà applicata solu s'ella currisponde à a parolla sana
      domain_allow:
        domain: Stu duminiu puderà ricuperà i dati di stu servore è i dati ch'affaccanu da quallà saranu trattati è cunservati

M config/locales/simple_form.cs.yml => config/locales/simple_form.cs.yml +0 -1
@@ 59,7 59,6 @@ cs:
        setting_show_application: Aplikace, kterou používáte k odeslání příspěvků, bude zobrazena jejich detailním zobrazení
        setting_use_blurhash: Gradienty jsou založeny na barvách skryté grafiky, ale zakrývají jakékoliv detaily
        setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu
        username: Vaše uživatelské jméno bude na serveru %{domain} unikátní
        whole_word: Je-li klíčové slovo či fráze pouze alfanumerická, bude aplikován pouze, pokud se shoduje s celým slovem
      domain_allow:
        domain: Tato doména bude moci stahovat data z tohoto serveru a příchozí data z ní budou zpracována a uložena

M config/locales/simple_form.cy.yml => config/locales/simple_form.cy.yml +0 -1
@@ 59,7 59,6 @@ cy:
        setting_show_application: Bydd y cymhwysiad a ddefnyddiwch i bostio yn cael ei arddangos yng ngolwg fanwl eich postiadau
        setting_use_blurhash: Mae graddiannau wedi'u seilio ar liwiau'r delweddau cudd ond maen nhw'n cuddio unrhyw fanylion
        setting_use_pending_items: Cuddio diweddariadau llinell amser y tu ôl i glic yn lle sgrolio'n awtomatig
        username: Bydd eich enw defnyddiwr yn unigryw ar %{domain}
        whole_word: Os yw'r allweddair neu'r ymadrodd yn alffaniwmerig yn unig, mi fydd ond yn cael ei osod os yw'n cyfateb a'r gair cyfan
      domain_allow:
        domain: Bydd y parth hwn yn gallu nôl data o'r gweinydd hwn a bydd data sy'n dod i mewn ohono yn cael ei brosesu a'i storio

M config/locales/simple_form.da.yml => config/locales/simple_form.da.yml +0 -1
@@ 59,7 59,6 @@ da:
        setting_show_application: Applikationen, hvormed der postes, vil fremgå af detailvisningen af dine indlæg
        setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer
        setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning
        username: Dit brugernavn vil være unikt på %{domain}
        whole_word: Ved rent alfanumeriske nøgleord/-sætning, forudsætter brugen matchning af hele ordet
      domain_allow:
        domain: Dette domæne vil kunne hente data, som efterfølgende behandles og gemmes, fra denne server

M config/locales/simple_form.de.yml => config/locales/simple_form.de.yml +0 -1
@@ 59,7 59,6 @@ de:
        setting_show_application: Die Anwendung die du nutzt wird in der detaillierten Ansicht deiner Beiträge angezeigt
        setting_use_blurhash: Die Farbverläufe basieren auf den Farben der verborgenen Medien, verschleiern aber jegliche Details
        setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt des automatischen Bildlaufs
        username: Dein Profilname wird auf %{domain} einmalig sein
        whole_word: Wenn das Wort oder die Formulierung nur aus Buchstaben oder Zahlen besteht, tritt der Filter nur dann in Kraft, wenn er exakt dieser Zeichenfolge entspricht
      domain_allow:
        domain: Diese Domain kann Daten von diesem Server abrufen, und eingehende Daten werden verarbeitet und gespeichert

M config/locales/simple_form.el.yml => config/locales/simple_form.el.yml +0 -1
@@ 59,7 59,6 @@ el:
        setting_show_application: Η εφαρμογή που χρησιμοποιείς για να στέλνεις τα τουτ σου θα εμφανίζεται στις αναλυτικές λεπτομέρειες τους
        setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες
        setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους
        username: Το όνομα χρήστη σου θα είναι μοναδικό στο %{domain}
        whole_word: Όταν η λέξη ή η φράση κλειδί είναι μόνο αλφαριθμητική, θα εφαρμοστεί μόνο αν ταιριάζει με ολόκληρη τη λέξη
      domain_allow:
        domain: Ο τομέας αυτός θα επιτρέπεται να ανακτά δεδομένα από αυτό τον διακομιστή και τα εισερχόμενα δεδομένα θα επεξεργάζονται και θα αποθηκεύονται

M config/locales/simple_form.en-GB.yml => config/locales/simple_form.en-GB.yml +0 -1
@@ 59,7 59,6 @@ en-GB:
        setting_show_application: The application you use to post will be displayed in the detailed view of your posts
        setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
        setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
        username: Your username will be unique on %{domain}
        whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
      domain_allow:
        domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored

M config/locales/simple_form.en.yml => config/locales/simple_form.en.yml +1 -1
@@ 59,7 59,7 @@ en:
        setting_show_application: The application you use to post will be displayed in the detailed view of your posts
        setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
        setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
        username: Your username will be unique on %{domain}
        username: You can use letters, numbers, and underscores
        whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
      domain_allow:
        domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored

M config/locales/simple_form.eo.yml => config/locales/simple_form.eo.yml +0 -1
@@ 59,7 59,6 @@ eo:
        setting_show_application: La aplikaĵo, kiun vi uzas por afiŝi, estos montrita en la detala vido de viaj afiŝoj
        setting_use_blurhash: Transirojn estas bazita sur la koloroj de la kaŝitaj aŭdovidaĵoj sed ne montri iun ajn detalon
        setting_use_pending_items: Kaŝi tempoliniajn ĝisdatigojn malantaŭ klako anstataŭ aŭtomate rulumi la fluon
        username: Via uzantnomo estos unika en %{domain}
        whole_word: Kiam la vorto aŭ frazo estas nur litera aŭ cifera, ĝi estos uzata nur se ĝi kongruas kun la tuta vorto
      domain_allow:
        domain: Ĉi tiu domajno povos akiri datumon de ĉi tiu servilo kaj envenanta datumo estos prilaborita kaj konservita

M config/locales/simple_form.es-AR.yml => config/locales/simple_form.es-AR.yml +0 -1
@@ 59,7 59,6 @@ es-AR:
        setting_show_application: La aplicación que usás para enviar mensajes se mostrará en la vista detallada de tus mensajes
        setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
        setting_use_pending_items: Ocultar actualizaciones de la línea temporal detrás de un clic en lugar de desplazar automáticamente el flujo
        username: Tu nombre de usuario será único en %{domain}
        whole_word: Cuando la palabra clave o frase es sólo alfanumérica, sólo será aplicado si coincide con toda la palabra
      domain_allow:
        domain: Este dominio podrá recolectar datos de este servidor, y los datos entrantes serán procesados y archivados

M config/locales/simple_form.es-MX.yml => config/locales/simple_form.es-MX.yml +0 -1
@@ 59,7 59,6 @@ es-MX:
        setting_show_application: La aplicación que utiliza usted para publicar toots se mostrará en la vista detallada de sus toots
        setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
        setting_use_pending_items: Ocultar nuevos estados detrás de un clic en lugar de desplazar automáticamente el feed
        username: Tu nombre de usuario será único en %{domain}
        whole_word: Cuando la palabra clave o frase es solo alfanumérica, solo será aplicado si concuerda con toda la palabra
      domain_allow:
        domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados

M config/locales/simple_form.es.yml => config/locales/simple_form.es.yml +0 -1
@@ 59,7 59,6 @@ es:
        setting_show_application: La aplicación que utiliza usted para publicar publicaciones se mostrará en la vista detallada de sus publicaciones
        setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
        setting_use_pending_items: Ocultar nuevos estados detrás de un clic en lugar de desplazar automáticamente el feed
        username: Tu nombre de usuario será único en %{domain}
        whole_word: Cuando la palabra clave o frase es solo alfanumérica, solo será aplicado si concuerda con toda la palabra
      domain_allow:
        domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados

M config/locales/simple_form.et.yml => config/locales/simple_form.et.yml +0 -1
@@ 59,7 59,6 @@ et:
        setting_show_application: Postitamiseks kasutatud rakenduse infot kuvatakse postituse üksikasjavaates
        setting_use_blurhash: Värvid põhinevad peidetud visuaalidel, kuid hägustavad igasuguseid detaile
        setting_use_pending_items: Voo automaatse kerimise asemel peida ajajoone uuendused kliki taha
        username: Sinu kasutajanimi on %{domain}-il unikaalne
        whole_word: Kui võtmesõna või fraas on ainult tähtnumbriline, rakendub see ainult siis, kui see kattub terve sõnaga
      domain_allow:
        domain: See domeen saab tõmmata andmeid sellelt serverilt ning sissetulevad andmed sellelt domeenilt töödeldakse ning salvestatakse

M config/locales/simple_form.eu.yml => config/locales/simple_form.eu.yml +0 -1
@@ 59,7 59,6 @@ eu:
        setting_show_application: Tootak bidaltzeko erabiltzen duzun aplikazioa zure tooten ikuspegi xehetsuan bistaratuko da
        setting_use_blurhash: Gradienteak ezkutatutakoaren koloreetan oinarritzen dira, baina xehetasunak ezkutatzen dituzte
        setting_use_pending_items: Ezkutatu denbora-lerroko eguneraketak klik baten atzean jarioa automatikoki korritu ordez
        username: Zure erabiltzaile-izena bakana izango da %{domain} domeinuan
        whole_word: Hitz eta esaldi gakoa alfanumerikoa denean, hitz osoarekin bat datorrenean besterik ez da aplikatuko
      domain_allow:
        domain: Domeinu honek zerbitzari honetatik datuak hartu ahal izango ditu eta bertatik jasotako informazioa prozesatu eta gordeko da

M config/locales/simple_form.fa.yml => config/locales/simple_form.fa.yml +0 -1
@@ 59,7 59,6 @@ fa:
        setting_show_application: برنامه‌ای که به کمک آن فرسته می‌زنید، در جزئیات فرسته شما نمایش خواهد یافت
        setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست
        setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن
        username: نام کاربری شما روی %{domain} یکتا خواهد بود
        whole_word: اگر کلیدواژه فقط دارای حروف و اعداد باشد، تنها وقتی پیدا می‌شود که با کل یک واژه در متن منطبق باشد، نه با بخشی از یک واژه
      domain_allow:
        domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد

M config/locales/simple_form.fi.yml => config/locales/simple_form.fi.yml +0 -1
@@ 59,7 59,6 @@ fi:
        setting_show_application: Viestittelyyn käyttämäsi sovellus näkyy viestiesi yksityiskohtaisessa näkymässä
        setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin, mutta sumentavat yksityiskohdat
        setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse sen sijaan, että vierittäisi syötettä automaattisesti
        username: Käyttäjänimesi tulee olemaan yksilöllinen %{domain}
        whole_word: Kun avainsana tai lause on vain aakkosnumeerinen, se otetaan käyttöön, jos se vastaa koko sanaa
      domain_allow:
        domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta ja sieltä saapuvat tiedot käsitellään ja tallennetaan

M config/locales/simple_form.fo.yml => config/locales/simple_form.fo.yml +0 -1
@@ 59,7 59,6 @@ fo:
        setting_show_application: Nýtsluskipanin, sum tú brúkar at posta við, verður víst í nágreinligu vísingini av postum tínum
        setting_use_blurhash: Gradientar eru grundaðir á litirnar av fjaldu myndunum, men grugga allar smálutir
        setting_use_pending_items: Fjal tíðarlinjudagføringar aftan fyri eitt klikk heldur enn at skrulla tilføringina sjálvvirkandi
        username: Brúkaranavnið hjá tær verður eindømi á %{domain}
        whole_word: Tá lyklaorðið ella frasan einans hevur bókstavir og tøl, so verður hon einans nýtt, um tú samsvarar við alt orðið
      domain_allow:
        domain: Økisnavnið kann heinta dátur frá hesum ambætaranum og inngangandi dátur frá honum verða viðgjørdar og goymdar

M config/locales/simple_form.fr-QC.yml => config/locales/simple_form.fr-QC.yml +0 -1
@@ 59,7 59,6 @@ fr-QC:
        setting_show_application: Le nom de l’application que vous utilisez pour publier sera affichée dans la vue détaillée de vos messages
        setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails
        setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement
        username: Votre nom d’utilisateur sera unique sur %{domain}
        whole_word: Si le mot-clé ou la phrase est alphanumérique, alors le filtre ne sera appliqué que s’il correspond au mot entier
      domain_allow:
        domain: Ce domaine pourra récupérer des données de ce serveur et les données entrantes seront traitées et stockées

M config/locales/simple_form.fr.yml => config/locales/simple_form.fr.yml +0 -1
@@ 59,7 59,6 @@ fr:
        setting_show_application: Le nom de l’application que vous utilisez pour publier sera affichée dans la vue détaillée de vos messages
        setting_use_blurhash: Les dégradés sont basés sur les couleurs des images cachées mais n’en montrent pas les détails
        setting_use_pending_items: Cacher les mises à jour des fils d’actualités derrière un clic, au lieu de les afficher automatiquement
        username: Votre identifiant sera unique sur %{domain}
        whole_word: Si le mot-clé ou la phrase est alphanumérique, alors le filtre ne sera appliqué que s’il correspond au mot entier
      domain_allow:
        domain: Ce domaine pourra récupérer des données de ce serveur et les données entrantes seront traitées et stockées

M config/locales/simple_form.fy.yml => config/locales/simple_form.fy.yml +0 -1
@@ 59,7 59,6 @@ fy:
        setting_show_application: De tapassing dy’t jo brûke om berjochten te pleatsen, wurdt yn de detaillearre werjefte fan it berjocht toand
        setting_use_blurhash: Dizige kleuroergongen binne basearre op de kleuren fan de ferstoppe media, wêrmei elk detail ferdwynt
        setting_use_pending_items: De tiidline wurdt bywurke troch op it oantal nije items te klikken, yn stee fan dat dizze automatysk bywurke wurdt
        username: Jo brûkersnamme is unyk op %{domain}
        whole_word: Wannear it trefwurd of part fan de sin alfanumeryk is, wurdt it allinnich filtere wannear’t it hiele wurd oerienkomt
      domain_allow:
        domain: Dit domein is yn steat om gegevens fan dizze server op te heljen, en ynkommende gegevens wurde ferwurke en bewarre

M config/locales/simple_form.gd.yml => config/locales/simple_form.gd.yml +0 -1
@@ 59,7 59,6 @@ gd:
        setting_show_application: Chithear cò an aplacaid a chleachd thu airson post a sgrìobhadh ann an seallaidhean mionaideach nam postaichean agad
        setting_use_blurhash: Tha caiseadan stèidhichte air dathan nan nithean lèirsinneach a chaidh fhalach ach chan fhaicear am mion-fhiosrachadh
        setting_use_pending_items: Falaich ùrachaidhean na loidhne-ama air cùlaibh briogaidh seach a bhith a’ sgroladh nam postaichean gu fèin-obrachail
        username: Bidh ainm-cleachdaiche àraidh agad air %{domain}
        whole_word: Mur eil ach litrichean is àireamhan san fhacal-luirg, cha dèid a chur an sàs ach ma bhios e a’ maidseadh an fhacail shlàin
      domain_allow:
        domain: "’S urrainn dhan àrainn seo dàta fhaighinn on fhrithealaiche seo agus thèid an dàta a thig a-steach uaithe a phròiseasadh ’s a stòradh"

M config/locales/simple_form.gl.yml => config/locales/simple_form.gl.yml +0 -1
@@ 59,7 59,6 @@ gl:
        setting_show_application: A aplicación que estás a utilizar para enviar publicacións mostrarase na vista detallada da publicación
        setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esvaecendo tódolos detalles
        setting_use_pending_items: Agochar actualizacións da cronoloxía tras un click no lugar de desprazar automáticamente os comentarios
        username: O teu nome de usuaria será único en %{domain}
        whole_word: Se a chave ou frase de paso é só alfanumérica, só se aplicará se concorda a palabra completa
      domain_allow:
        domain: Este dominio estará en disposición de obter datos desde este servidor e datos de entrada a el poderán ser procesados e gardados

M config/locales/simple_form.he.yml => config/locales/simple_form.he.yml +0 -1
@@ 59,7 59,6 @@ he:
        setting_show_application: היישום בו נעשה שימוש כדי לחצרץ יופיע בתצוגה המפורטת של החצרוץ
        setting_use_blurhash: הגראדיינטים מבוססים על תוכן התמונה המוסתרת, אבל מסתירים את כל הפרטים
        setting_use_pending_items: הסתר עדכוני פיד מאחורי קליק במקום לגלול את הפיד אוטומטית
        username: שם המשתמש שלך יהיה ייחודי ב- %{domain}
        whole_word: אם מילת מפתח או ביטוי הם אלפאנומריים בלבד, הם יופעלו רק אם נמצאה התאמה למילה שלמה
      domain_allow:
        domain: דומיין זה יוכל לייבא מידע משרת זה והמידע המגיע ממנו יעובד ויאופסן

M config/locales/simple_form.hu.yml => config/locales/simple_form.hu.yml +0 -1
@@ 59,7 59,6 @@ hu:
        setting_show_application: A bejegyzések részletes nézetében látszani fog, milyen alkalmazást használtál a bejegyzés közzétételéhez
        setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt
        setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett
        username: A felhasználói neved egyedi lesz a %{domain} domainen
        whole_word: Ha a kulcsszó alfanumerikus, csak akkor minősül majd találatnak, ha teljes szóra illeszkedik
      domain_allow:
        domain: Ez a domain adatokat kérhet le erről a kiszolgálóról, és a bejövő adatok fel lesznek dolgozva és tárolva lesznek

M config/locales/simple_form.hy.yml => config/locales/simple_form.hy.yml +0 -1
@@ 49,7 49,6 @@ hy:
        setting_show_application: Գրառման մանրամասներում կերեւայ թէ որ ծրագրով ես հրապարակել այն
        setting_use_blurhash: Կտորները հիմնուում են թաքցուած վիզուալի վրայ՝ խամրեցնելով դետալները
        setting_use_pending_items: Թաքցնել հոսքի թարմացումները կտտոի ետեւում՝ աւտօմատ թարմացուող հոսքի փոխարէն
        username: Քո օգտանունը պէտք է եզակի լինի %{domain}-ում։
        whole_word: Եթէ բանալի բառը կամ արտայայտութիւնը պարունակում է միայն այբբենական նիշեր եւ թուեր, ապա այն կիրառուելու է ամբողջ բառի հետ համընկնելու դէպքում միայն
      domain_allow:
        domain: Այս տիրոյթը կարող է ստանալ տուեալներ այս սպասարկչից եւ ստացուող տուեալները կարող են օգտագործուել եւ պահուել

M config/locales/simple_form.id.yml => config/locales/simple_form.id.yml +0 -1
@@ 57,7 57,6 @@ id:
        setting_show_application: Aplikasi yang Anda pakai untuk men-toot akan ditampilkan di tampilan detail toot
        setting_use_blurhash: Gradien didasarkan pada warna visual yang tersembunyi tetapi mengaburkan setiap detail
        setting_use_pending_items: Sembunyikan pembaruan linimasa di balik klik alih-alih bergulir secara otomatis
        username: Nama pengguna Anda unik di %{domain}
        whole_word: Ketika kata kunci/frasa hanya alfanumerik, maka itu hanya akan diterapkan jika cocok dengan semua kata
      domain_allow:
        domain: Domain ini dapat mengambil data dari server ini dan data yang diterima akan diproses dan disimpan

M config/locales/simple_form.io.yml => config/locales/simple_form.io.yml +0 -1
@@ 57,7 57,6 @@ io:
        setting_show_application: Softwaro quon vu uzar por postigar montresos che detala vidajo di vua posti
        setting_use_blurhash: Inklini esas segun kolori di celesis vidaji ma kovras irga detali
        setting_use_pending_items: Celez tempolineonovi dop kliktar e ne automatike movigar niuzeto
        username: Vua uzantonomo esos nura che %{domain}
        whole_word: Kande klefvorto o fraz esas nur litera e nombra, ol nur aplikesos se ol parigesas la tota vorto
      domain_allow:
        domain: Ca domeno povas ganar informi de ca servilo e venanta informo de ol procedagesos e sparesos

M config/locales/simple_form.is.yml => config/locales/simple_form.is.yml +0 -1
@@ 59,7 59,6 @@ is:
        setting_show_application: Nafnið á forritinu sem þú notar til að senda færslur mun birtast í ítarlegri sýn á færslunum þínum
        setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr
        setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt
        username: Notandanafnið þitt verður einstakt á %{domain}
        whole_word: Þegar stikkorð eða setning er einungis tölur og bókstafir, verður það aðeins notað ef það samsvarar heilu orði
      domain_allow:
        domain: Þetta lén mun geta sótt gögn af þessum vefþjóni og tekið verður á móti innsendum gögnum frá léninu til vinnslu og geymslu

M config/locales/simple_form.it.yml => config/locales/simple_form.it.yml +0 -1
@@ 59,7 59,6 @@ it:
        setting_show_application: L'applicazione che usi per pubblicare i toot sarà mostrata nella vista di dettaglio dei tuoi toot
        setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli
        setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente
        username: Il tuo nome utente sarà unico su %{domain}
        whole_word: Quando la parola chiave o la frase è solo alfanumerica, si applica solo se corrisponde alla parola intera
      domain_allow:
        domain: Questo dominio potrà recuperare i dati da questo server e i dati in arrivo da esso verranno elaborati e memorizzati

M config/locales/simple_form.ja.yml => config/locales/simple_form.ja.yml +0 -1
@@ 59,7 59,6 @@ ja:
        setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
        setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
        setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
        username: あなたのユーザー名は%{domain}の中で重複していない必要があります
        whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります
      domain_allow:
        domain: 登録するとこのサーバーからデータを受信したり、このドメインから受信するデータを処理して保存できるようになります

M config/locales/simple_form.kab.yml => config/locales/simple_form.kab.yml +0 -1
@@ 20,7 20,6 @@ kab:
        setting_display_media_hide_all: Ffer yal tikkelt akk taywalt
        setting_display_media_show_all: Ffer yal tikkelt teywalt yettwacreḍ d tanafrit
        setting_hide_network: Wid i teṭṭafaṛeḍ d wid i k-yeṭṭafaṛen ur d-ttwaseknen ara deg umaγnu-inek
        username: Isem-ik n umseqdac ad yili d ayiwen, ulac am netta deg %{domain}
      imports:
        data: Afaylu CSV id yusan seg uqeddac-nniḍen n Maṣṭudun
      ip_block:

M config/locales/simple_form.ko.yml => config/locales/simple_form.ko.yml +0 -1
@@ 59,7 59,6 @@ ko:
        setting_show_application: 당신이 게시물을 작성하는데에 사용한 앱이 게시물의 상세정보에 표시 됩니다
        setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다
        setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다
        username: 당신의 사용자명은 %{domain} 안에서 유일해야 합니다
        whole_word: 키워드가 영문과 숫자로만 이루어 진 경우, 단어 전체에 매칭 되었을 때에만 작동하게 합니다
      domain_allow:
        domain: 이 도메인은 이 서버에서 데이터를 가져갈 수 있고 이 도메인에서 보내진 데이터는 처리되고 저장 됩니다

M config/locales/simple_form.ku.yml => config/locales/simple_form.ku.yml +0 -1
@@ 59,7 59,6 @@ ku:
        setting_show_application: Navê sepana ku tu ji bo şandinê wê bi kar tîne dê di dîtinê berferh ên di şandiyên te de were xuyakirin
        setting_use_blurhash: Gradyen xwe bi rengên dîtbarîyên veşartî ve radigire, lê belê hûrgilîyan diveşêre
        setting_use_pending_items: Li şûna ku herkê wek bixweber bizivirînî nûvekirina demnameyê li paş tikandinekî veşêre
        username: Navê te yê bikarhênerî li ser %{domain} de bêhempa be
        whole_word: Dema peyvkilîd an jî hevok bi tenê alfahejmarî çêbe, bi tenê hemû bêjeyê re li hev bike wê pêk bê
      domain_allow:
        domain: Ev navê navperê, ji vê rajekarê wê daneyan bistîne û daneyên ku jê bê wê were sazkirin û veşartin

M config/locales/simple_form.lv.yml => config/locales/simple_form.lv.yml +0 -1
@@ 59,7 59,6 @@ lv:
        setting_show_application: Lietojumprogramma, ko tu izmanto publicēšanai, tiks parādīta tavu ziņu detalizētajā skatā
        setting_use_blurhash: Gradientu pamatā ir paslēpto vizuālo attēlu krāsas, bet neskaidras visas detaļas
        setting_use_pending_items: Paslēpt laika skalas atjauninājumus aiz klikšķa, nevis automātiski ritini plūsmu
        username: Tavs lietotājvārds %{domain} būs unikāls
        whole_word: Ja atslēgvārds vai frāze ir tikai burtciparu, tas tiks lietots tikai tad, ja tas atbilst visam vārdam
      domain_allow:
        domain: Šis domēns varēs izgūt datus no šī servera, un no tā ienākošie dati tiks apstrādāti un saglabāti

M config/locales/simple_form.my.yml => config/locales/simple_form.my.yml +0 -1
@@ 59,7 59,6 @@ my:
        setting_show_application: ပို့စ်တင်ရန်အတွက် သင်အသုံးပြုသည့် အက်ပလီကေးရှင်းကို သင့်ပို့စ်များ၏ အသေးစိတ်ကြည့်ရှုမှုတွင် ပြသမည်ဖြစ်သည်
        setting_use_blurhash: Gradients မှာ ဖျောက်ထားသောရုပ်ပုံများ၏ အရောင်များကိုအခြေခံသော်လည်း မည်သည့်အသေးစိတ်အချက်အလက်ကိုမဆို ရှုပ်ထွေးစေနိုင်ပါသည်။
        setting_use_pending_items: အပေါ်အောက်လှိမ့်မည့်အစား ကလစ်တစ်ခုနောက်တွင် စာမျက်နှာအပ်ဒိတ်များကို ဖျောက်ထားပါ။
        username: "%{domain} ရှိ သင့်အသုံးပြုသူအမည်မှာ တူညီ၍မရပါ"
        whole_word: အဓိကစကားလုံး သို့မဟုတ် စကားစုသည် အက္ခရာဂဏန်းများသာဖြစ်ပါကစကားလုံးတစ်ခုလုံးနှင့် ကိုက်ညီမှသာ အသုံးပြုနိုင်မည်ဖြစ်သည်
      domain_allow:
        domain: ဤဒိုမိန်းသည် ဤဆာဗာမှ အချက်အလက်များကို ရယူနိုင်မည်ဖြစ်ပြီး ဝင်လာသောအချက်အလက်များကို စီမံဆောင်ရွက်ပေးပြီး သိမ်းဆည်းသွားမည်ဖြစ်သည်

M config/locales/simple_form.nl.yml => config/locales/simple_form.nl.yml +0 -1
@@ 59,7 59,6 @@ nl:
        setting_show_application: De toepassing de je gebruikt om berichten te plaatsen wordt in de gedetailleerde weergave van het bericht getoond
        setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt
        setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt
        username: Jouw gebruikersnaam is uniek op %{domain}
        whole_word: Wanneer het trefwoord of zinsdeel alfanumeriek is, wordt het alleen gefilterd wanneer het hele woord overeenkomt
      domain_allow:
        domain: Dit domein is in staat om gegevens van deze server op te halen, en binnenkomende gegevens worden verwerkt en opgeslagen

M config/locales/simple_form.nn.yml => config/locales/simple_form.nn.yml +0 -1
@@ 57,7 57,6 @@ nn:
        setting_show_application: Programmet du brukar for å tuta blir vist i den detaljerte visninga av tuta dine
        setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege
        setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk
        username: Brukarnamnet ditt vert unikt på %{domain}
        whole_word: Når søkjeordet eller setninga berre er alfanumerisk, nyttast det berre om det samsvarar med heile ordet
      domain_allow:
        domain: Dette domenet er i stand til å henta data frå denne tenaren og innkomande data vert handsama og lagra

M config/locales/simple_form.no.yml => config/locales/simple_form.no.yml +0 -1
@@ 57,7 57,6 @@
        setting_show_application: Appen du bruker til å publisere innlegg vil bli vist i den detaljerte visningen til innleggene dine
        setting_use_blurhash: Gradientene er basert på fargene til de skjulte visualitetene, men gjør alle detaljer uklare
        setting_use_pending_items: Skjul tidslinjeoppdateringer bak et klikk, i stedet for å automatisk la strømmen skrolle
        username: Brukernavnet ditt vil være unikt på %{domain}
        whole_word: Når søkeordet eller setningen bare er alfanumerisk, aktiveres det bare hvis det samsvarer med hele ordet
      domain_allow:
        domain: Dette domenet vil være i stand til å hente data fra denne serveren og dets innkommende data vil bli prosessert og lagret

M config/locales/simple_form.oc.yml => config/locales/simple_form.oc.yml +0 -1
@@ 52,7 52,6 @@ oc:
        setting_show_application: Lo nom de l’aplicacion qu’utilizatz per publicar serà mostrat dins la vista detalhada de vòstres tuts
        setting_use_blurhash: Los degradats venon de las colors de l’imatge rescondut en enfoscar los detalhs
        setting_use_pending_items: Rescondre las actualizacions del flux d’actualitat aprèp un clic allòc de desfilar lo flux automaticament
        username: Vòstre nom d’utilizaire serà unic sus %{domain}
        whole_word: Quand lo mot-clau o frasa es solament alfranumeric, serà pas qu’aplicat se correspond al mot complèt
      domain_allow:
        domain: Aqueste domeni poirà recuperar las donadas d’aqueste servidor estant e las donadas venent d’aqueste domeni seràn tractadas e gardadas

M config/locales/simple_form.pl.yml => config/locales/simple_form.pl.yml +0 -1
@@ 59,7 59,6 @@ pl:
        setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany
        setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły
        setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia
        username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain}
        whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień
      domain_allow:
        domain: Ta domena będzie mogła pobierać dane z serwera, a dane przychodzące z niej będą przetwarzane i przechowywane

M config/locales/simple_form.pt-BR.yml => config/locales/simple_form.pt-BR.yml +0 -1
@@ 59,7 59,6 @@ pt-BR:
        setting_show_application: O aplicativo que você usar para publicar será exibido na visão detalhada das suas publicações
        setting_use_blurhash: O blur é baseado nas cores da imagem oculta, ofusca a maioria dos detalhes
        setting_use_pending_items: Ocultar atualizações da linha do tempo atrás de um clique ao invés de rolar automaticamente
        username: Seu nome de usuário será único em %{domain}
        whole_word: Quando a palavra-chave ou frase é inteiramente alfanumérica, ela será aplicada somente se corresponder a palavra inteira
      domain_allow:
        domain: Este domínio poderá obter dados deste servidor e os dados recebidos dele serão processados e armazenados

M config/locales/simple_form.pt-PT.yml => config/locales/simple_form.pt-PT.yml +0 -1
@@ 59,7 59,6 @@ pt-PT:
        setting_show_application: A aplicação que usa para publicar será mostrada na vista pormenorizada das suas publicações
        setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer pormenores
        setting_use_pending_items: Ocultar atualizações da cronologia por detrás dum clique, em vez de rolar automaticamente o fluxo
        username: O teu nome de utilizador será único em %{domain}
        whole_word: Quando a palavra-chave ou expressão-chave é somente alfanumérica, ela só será aplicada se corresponder à palavra completa
      domain_allow:
        domain: Este domínio será capaz de obter dados desta instância e os dados dele recebidos serão processados e armazenados

M config/locales/simple_form.ro.yml => config/locales/simple_form.ro.yml +0 -1
@@ 49,7 49,6 @@ ro:
        setting_show_application: Aplicația pe care o utilizați pentru a posta va fi afișată în vizualizarea detaliată a postărilor
        setting_use_blurhash: Gradienții sunt bazați pe culorile vizualelor ascunse, dar ofuscă orice detalii
        setting_use_pending_items: Ascunde actualizările cronologice din spatele unui click în loc de a derula automat fluxul
        username: Numele tău de utilizator va fi unic pe %{domain}
        whole_word: Când fraza sau cuvântul este doar alfanumeric, acesta se aplică doar dacă există o potrivire completă
      domain_allow:
        domain: Acest domeniu va putea prelua date de pe acest server și datele primite de la el vor fi procesate și stocate

M config/locales/simple_form.ru.yml => config/locales/simple_form.ru.yml +0 -1
@@ 59,7 59,6 @@ ru:
        setting_show_application: При просмотре поста будет видно из какого приложения он отправлен.
        setting_use_blurhash: Градиенты основаны на цветах скрытых медиа, но скрывают любые детали.
        setting_use_pending_items: Показывать обновления в ленте только после клика вместо автоматической прокрутки.
        username: Ваше имя пользователя будет уникальным на %{domain}
        whole_word: Если слово или фраза состоит только из букв и цифр, сопоставление произойдёт только по полному совпадению
      domain_allow:
        domain: Этот домен сможет получать данные с этого сервера и его входящие данные будут обрабатываться и сохранены

M config/locales/simple_form.sc.yml => config/locales/simple_form.sc.yml +0 -1
@@ 53,7 53,6 @@ sc:
        setting_show_application: S'aplicatzione chi impreas pro publicare tuts at a èssere ammustrada in sa visualizatzione de detàlliu de is tuts
        setting_use_blurhash: Is gradientes sunt basados in subra de is colores de is immàgines cuadas ma imbelant totu is detàllios
        setting_use_pending_items: Cua is atualizatziones in segus de un'incarcu imbetzes de iscùrrere in automàticu su flussu de publicatziones
        username: Su nòmine de utente tuo at a èssere ùnicu in %{domain}
        whole_word: Cando sa crae (faeddu o fràsia) siat isceti alfanumèrica, s'at a aplicare isceti si currispondet a su faeddu intreu
      domain_allow:
        domain: Custu domìniu at a pòdere recuperare datos dae custu serbidore e is datos in intrada dae cue ant a èssere protzessados e archiviados

M config/locales/simple_form.sco.yml => config/locales/simple_form.sco.yml +0 -1
@@ 57,7 57,6 @@ sco:
        setting_show_application: The application thit ye uise fir tae post wull be displayed in the detailt view o yer posts
        setting_use_blurhash: Gradients is based aff o the colors o the image thit's hid, but ye cannae see onie details
        setting_use_pending_items: Plank timeline updates ahin a chap insteid o automatic scrowin o the feed
        username: Yer uisernemm wull be a ane aff on %{domain}
        whole_word: Whan the keywird or phrase is alphanumeric ainly, it wull ainly get applied if it matches the haill wird
      domain_allow:
        domain: This domain wull be able tae get data fae this server an data comin in fae it wull get processed an stowed

M config/locales/simple_form.si.yml => config/locales/simple_form.si.yml +0 -1
@@ 57,7 57,6 @@ si:
        setting_show_application: ඔබ පළ කිරීමට භාවිතා කරන යෙදුම ඔබගේ පළ කිරීම් වල සවිස්තරාත්මක දර්ශනයේ පෙන්වනු ඇත
        setting_use_blurhash: අනුක්‍රමණ සැඟවුණු දෘශ්‍යවල වර්ණ මත පදනම් වන නමුත් ඕනෑම විස්තරයක් අපැහැදිලි කරයි
        setting_use_pending_items: සංග්‍රහය ස්වයංක්‍රීයව අනුචලනය කරනවා වෙනුවට ක්ලික් කිරීමක් පිටුපස කාලරේඛා යාවත්කාලීන සඟවන්න
        username: ඔබගේ පරිශීලක නාමය %{domain}හි අද්විතීය වනු ඇත
        whole_word: මූල පදය හෝ වාක්‍ය ඛණ්ඩය අක්ෂරාංක පමණක් වන විට, එය යෙදෙන්නේ එය සම්පූර්ණ වචනයට ගැලපේ නම් පමණි
      domain_allow:
        domain: මෙම වසමට මෙම සේවාදායකයෙන් දත්ත ලබා ගැනීමට හැකි වන අතර එයින් ලැබෙන දත්ත සකස් කර ගබඩා කරනු ලැබේ

M config/locales/simple_form.sk.yml => config/locales/simple_form.sk.yml +0 -1
@@ 42,7 42,6 @@ sk:
        setting_show_application: Aplikácia, ktorú používaš na písanie príspevkov, bude zobrazená v podrobnom náhľade jednotlivých tvojích príspevkov
        setting_use_blurhash: Prechody sú založené na farbách skrytých vizuálov, ale zahaľujú akékoľvek podrobnosti
        setting_use_pending_items: Skry aktualizovanie časovej osi tak, aby bola načitávaná iba po kliknutí, namiesto samostatného posúvania
        username: Tvoja prezývka bude unikátna pre server %{domain}
        whole_word: Ak je kľúčové slovo, alebo fráza poskladaná iba s písmen a čísel, bude použité iba ak sa zhoduje s celým výrazom
      domain_allow:
        domain: Táto doména bude schopná získavať dáta z tohto servera, a prichádzajúce dáta ním budú spracovávané a uložené

M config/locales/simple_form.sl.yml => config/locales/simple_form.sl.yml +0 -1
@@ 59,7 59,6 @@ sl:
        setting_show_application: Aplikacija, ki jo uporabljate za objavljanje, bo prikazana v podrobnem pogledu vaših objav
        setting_use_blurhash: Prelivi temeljijo na barvah skrite vizualne slike, vendar zakrivajo vse podrobnosti
        setting_use_pending_items: Skrij posodobitev časovnice za klikom namesto samodejnega posodabljanja
        username: Vaše uporabniško ime bo edinstveno na %{domain}
        whole_word: Ko je ključna beseda ali fraza samo alfanumerična, se bo uporabljala le, če se bo ujemala s celotno besedo
      domain_allow:
        domain: Ta domena bo lahko prejela podatke s tega strežnika, dohodni podatki z nje pa bodo obdelani in shranjeni

M config/locales/simple_form.sq.yml => config/locales/simple_form.sq.yml +0 -1
@@ 59,7 59,6 @@ sq:
        setting_show_application: Aplikacioni që përdorni për mesazhe do të shfaqet te pamja e hollësishme për mesazhet tuaj
        setting_use_blurhash: Gradientët bazohen në ngjyrat e elementëve pamorë të fshehur, por errësojnë çfarëdo hollësie
        setting_use_pending_items: Fshihi përditësimet e rrjedhës kohore pas një klikimi, në vend të rrëshqitjes automatike nëpër prurje
        username: Emri juaj i përdoruesit do të jetë unik në %{domain}
        whole_word: Kur fjalëkyçi ose fraza është vetëm numerike, do të aplikohet vetëm nëse përputhet me krejt fjalën
      domain_allow:
        domain: Kjo përkatësi do të jetë në gjendje të sjellë të dhëna prej këtij shërbyesi dhe të dhënat ardhëse prej tij do të përpunohen dhe depozitohen

M config/locales/simple_form.sr-Latn.yml => config/locales/simple_form.sr-Latn.yml +0 -1
@@ 59,7 59,6 @@ sr-Latn:
        setting_show_application: Aplikacija koju koristite za objavljivanje će biti prikazana u detaljnom prikazu vaših objava
        setting_use_blurhash: Gradijenti se formiraju na osnovu bojâ skrivenih slika i zamućuju prikaz, prikrivajući detalje
        setting_use_pending_items: Sakriva ažuriranja vremenske linije iza klika umesto automatskog ažuriranja i pomeranja vremenske linije
        username: Vaš nadimak će biti jedinstven na %{domain}
        whole_word: Kada je ključna reč ili fraza isključivo alfanumerička, biće primenjena samo ako se podudara sa celom rečju
      domain_allow:
        domain: Ovaj domen će moći da preuzima podatke sa ovog servera i dolazni podaci sa njega će se obrađivati i čuvati

M config/locales/simple_form.sr.yml => config/locales/simple_form.sr.yml +0 -1
@@ 59,7 59,6 @@ sr:
        setting_show_application: Апликација коју користите за објављивање ће бити приказана у детаљном приказу ваших објава
        setting_use_blurhash: Градијенти се формирају на основу бојâ скривених слика и замућују приказ, прикривајући детаље
        setting_use_pending_items: Сакрива ажурирања временске линије иза клика уместо аутоматског ажурирања и померања временске линије
        username: Ваш надимак ће бити јединствен на %{domain}
        whole_word: Када је кључна реч или фраза искључиво алфанумеричка, биће примењена само ако се подудара са целом речjу
      domain_allow:
        domain: Овај домен ће моћи да преузима податке са овог сервера и долазни подаци са њега ће се обрађивати и чувати

M config/locales/simple_form.sv.yml => config/locales/simple_form.sv.yml +0 -1
@@ 59,7 59,6 @@ sv:
        setting_show_application: Applikationen du använder för att göra inlägg kommer visas i detaljvyn för dina inlägg
        setting_use_blurhash: Gradienter är baserade på färgerna av de dolda objekten men fördunklar alla detaljer
        setting_use_pending_items: Dölj tidslinjeuppdateringar bakom ett klick istället för att automatiskt bläddra i flödet
        username: Ditt användarnamn måste vara unikt på %{domain}
        whole_word: När sökordet eller frasen endast är alfanumerisk, kommer det endast att tillämpas om det matchar hela ordet
      domain_allow:
        domain: Denna domän kommer att kunna hämta data från denna server och inkommande data från den kommer att behandlas och lagras

M config/locales/simple_form.th.yml => config/locales/simple_form.th.yml +0 -1
@@ 59,7 59,6 @@ th:
        setting_show_application: จะแสดงแอปพลิเคชันที่คุณใช้ในการโพสต์ในมุมมองโดยละเอียดของโพสต์ของคุณ
        setting_use_blurhash: การไล่ระดับสีอิงตามสีของภาพที่ซ่อนอยู่แต่ทำให้รายละเอียดใด ๆ คลุมเครือ
        setting_use_pending_items: ซ่อนการอัปเดตเส้นเวลาไว้หลังการคลิกแทนที่จะเลื่อนฟีดโดยอัตโนมัติ
        username: ชื่อผู้ใช้ของคุณจะไม่ซ้ำกันใน %{domain}
        whole_word: เมื่อคำสำคัญหรือวลีเป็นตัวอักษรและตัวเลขเท่านั้น จะนำไปใช้กับคำสำคัญหรือวลีหากตรงกันทั้งคำเท่านั้น
      domain_allow:
        domain: โดเมนนี้จะสามารถดึงข้อมูลจากเซิร์ฟเวอร์นี้และจะประมวลผลและจัดเก็บข้อมูลขาเข้าจากโดเมน

M config/locales/simple_form.tr.yml => config/locales/simple_form.tr.yml +0 -1
@@ 59,7 59,6 @@ tr:
        setting_show_application: Gönderi gönderimi için kullandığınız uygulama, gönderilerinizin ayrıntılı görünümünde gösterilecektir
        setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler
        setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin
        username: Kullanıcı adınız %{domain} alanında benzersiz olacak
        whole_word: Anahtar kelime veya kelime öbeği yalnızca alfasayısal olduğunda, yalnızca tüm sözcükle eşleşirse uygulanır
      domain_allow:
        domain: Bu alan adı, bu sunucudan veri alabilecek ve ondan gelen veri işlenecek ve saklanacaktır

M config/locales/simple_form.uk.yml => config/locales/simple_form.uk.yml +0 -1
@@ 59,7 59,6 @@ uk:
        setting_show_application: Застосунок, за допомогою якого ви зробили допис, буде показано серед подробиць допису
        setting_use_blurhash: Градієнти, що базуються на кольорах прихованих медіа, але роблять нерозрізненними будь-які деталі
        setting_use_pending_items: Не додавати нові повідомлення до стрічок миттєво, показувати лише після додаткового клацання
        username: Ваше ім'я користувача буде унікальним у %{domain}
        whole_word: Якщо пошукове слово або фраза містить лише літери та цифри, воно має збігатися цілком
      domain_allow:
        domain: Цей домен зможе отримувати дані з цього сервера. Вхідні дані будуть оброблені та збережені

M config/locales/simple_form.vi.yml => config/locales/simple_form.vi.yml +0 -1
@@ 59,7 59,6 @@ vi:
        setting_show_application: Tên ứng dụng bạn dùng để đăng tút sẽ hiện trong chi tiết của tút
        setting_use_blurhash: Lớp phủ mờ dựa trên màu sắc của hình ảnh nhạy cảm
        setting_use_pending_items: Dồn lại toàn bộ tút mới và chỉ hiển thị khi nhấn vào
        username: Tên người dùng của bạn sẽ là duy nhất trên %{domain}
        whole_word: Khi từ khóa hoặc cụm từ là chữ và số, nó sẽ chỉ hiện ra những từ chính xác như vậy
      domain_allow:
        domain: Máy chủ này sẽ tiếp nhận dữ liệu, rồi sau đó xử lý và lưu trữ

M config/locales/simple_form.zh-CN.yml => config/locales/simple_form.zh-CN.yml +0 -1
@@ 59,7 59,6 @@ zh-CN:
        setting_show_application: 你用来发表嘟文的应用程序将会在你嘟文的详细内容中显示
        setting_use_blurhash: 渐变是基于模糊后的隐藏内容生成的
        setting_use_pending_items: 关闭自动滚动更新,时间轴会在点击后更新
        username: 你的用户名在 %{domain} 上是唯一的
        whole_word: 如果关键词只包含字母和数字,将只在词语完全匹配时才会应用
      domain_allow:
        domain: 该站点将能够从该服务器上拉取数据,并处理和存储收到的数据。

M config/locales/simple_form.zh-HK.yml => config/locales/simple_form.zh-HK.yml +0 -1
@@ 59,7 59,6 @@ zh-HK:
        setting_show_application: 你用來發表文章的應用程式,將會顯示在你文章的詳細檢視中
        setting_use_blurhash: 漸變圖樣會基於隱藏媒體內容產生,但所有細節會變得模糊
        setting_use_pending_items: 關閉自動滾動更新,時間軸會在點擊後更新
        username: 你的使用者名稱在 %{domain} 將是獨一無二的
        whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用
      domain_allow:
        domain: 此網域將能從此站獲取資料,而此站發出的數據也會被處理和存儲。

M config/locales/simple_form.zh-TW.yml => config/locales/simple_form.zh-TW.yml +0 -1
@@ 59,7 59,6 @@ zh-TW:
        setting_show_application: 您用來發嘟文的應用程式將會在您嘟文的詳細檢視顯示
        setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節會變得模糊
        setting_use_pending_items: 關閉自動捲動更新,時間軸只會在點擊後更新
        username: 您的使用者名稱將於 %{domain} 是獨一無二的
        whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用
      domain_allow:
        domain: 此網域將能夠攫取本站資料,而自該網域發出的資料也會於本站處理和留存。

M config/routes.rb => config/routes.rb +27 -3
@@ 667,9 667,33 @@ Rails.application.routes.draw do
        resources :ip_blocks, only: [:index, :show, :update, :create, :destroy]

        namespace :trends do
          resources :tags, only: [:index]
          resources :links, only: [:index]
          resources :statuses, only: [:index]
          resources :tags, only: [:index] do
            member do
              post :approve
              post :reject
            end
          end
          resources :links, only: [:index] do
            member do
              post :approve
              post :reject
            end
          end
          resources :statuses, only: [:index] do
            member do
              post :approve
              post :reject
            end
          end

          namespace :links do
            resources :preview_card_providers, only: [:index], path: :publishers do
              member do
                post :approve
                post :reject
              end
            end
          end
        end

        post :measures, to: 'measures#create'

M jest.config.js => jest.config.js +1 -1
@@ 13,7 13,7 @@ const config = {
  setupFiles: ['raf/polyfill'],
  setupFilesAfterEnv: ['<rootDir>/app/javascript/mastodon/test_setup.js'],
  collectCoverageFrom: [
    'app/javascript/mastodon/**/*.js',
    'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
    '!app/javascript/mastodon/features/emoji/emoji_compressed.js',
    '!app/javascript/mastodon/locales/locale-data/*.js',
    '!app/javascript/mastodon/service_worker/entry.js',

M lib/mastodon/accounts_cli.rb => lib/mastodon/accounts_cli.rb +8 -2
@@ 57,6 57,7 @@ module Mastodon
    option :role
    option :reattach, type: :boolean
    option :force, type: :boolean
    option :approve, type: :boolean
    desc 'create USERNAME', 'Create a new user account'
    long_desc <<-LONG_DESC
      Create a new user account with a given USERNAME and an


@@ 72,6 73,8 @@ module Mastodon
      account is still in use by someone else, you can supply
      the --force option to delete the old record and reattach the
      username to the new account anyway.

      With the --approve option, the account will be approved.
    LONG_DESC
    def create(username)
      role_id  = nil


@@ 89,7 92,7 @@ module Mastodon

      account  = Account.new(username: username)
      password = SecureRandom.hex
      user     = User.new(email: options[:email], password: password, agreement: true, approved: true, role_id: role_id, confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)
      user     = User.new(email: options[:email], password: password, agreement: true, role_id: role_id, confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)

      if options[:reattach]
        account = Account.find_local(username) || Account.new(username: username)


@@ 112,6 115,8 @@ module Mastodon
          user.confirm!
        end

        user.approve! if options[:approve]

        say('OK', :green)
        say("New password: #{password}")
      else


@@ 184,9 189,10 @@ module Mastodon
      user.disabled = true if options[:disable]
      user.approved = true if options[:approve]
      user.otp_required_for_login = false if options[:disable_2fa]
      user.confirm if options[:confirm]

      if user.save
        user.confirm if options[:confirm]

        say('OK', :green)
        say("New password: #{password}") if options[:reset_password]
      else

M package.json => package.json +3 -3
@@ 68,7 68,7 @@
    "fuzzysort": "^2.0.4",
    "glob": "^10.0.0",
    "history": "^4.10.1",
    "http-link-header": "^1.1.0",
    "http-link-header": "^1.1.1",
    "immutable": "^4.3.0",
    "imports-loader": "^1.2.0",
    "intl": "^1.2.5",


@@ 86,7 86,7 @@
    "path-complete-extname": "^1.0.0",
    "pg": "^8.5.0",
    "pg-connection-string": "^2.5.0",
    "postcss": "^8.4.21",
    "postcss": "^8.4.22",
    "postcss-loader": "^4.3.0",
    "promise.prototype.finally": "^3.1.4",
    "prop-types": "^15.8.1",


@@ 116,7 116,7 @@
    "redux-thunk": "^2.4.2",
    "regenerator-runtime": "^0.13.11",
    "requestidlecallback": "^0.3.0",
    "reselect": "^4.1.7",
    "reselect": "^4.1.8",
    "rimraf": "^5.0.0",
    "sass": "^1.61.0",
    "sass-loader": "^10.2.0",

A spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb => spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb +68 -0
@@ 0,0 1,68 @@
# frozen_string_literal: true

require 'rails_helper'

describe Api::V1::Admin::Trends::Links::PreviewCardProvidersController do
  render_views

  let(:role)   { UserRole.find_by(name: 'Admin') }
  let(:user)   { Fabricate(:user, role: role) }
  let(:scopes) { 'admin:read admin:write' }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
  let(:account) { Fabricate(:account) }
  let(:preview_card_provider) { Fabricate(:preview_card_provider) }

  before do
    allow(controller).to receive(:doorkeeper_token) { token }
  end

  shared_examples 'forbidden for wrong scope' do |wrong_scope|
    let(:scopes) { wrong_scope }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  shared_examples 'forbidden for wrong role' do |wrong_role|
    let(:role) { UserRole.find_by(name: wrong_role) }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  describe 'GET #index' do
    it 'returns http success' do
      get :index, params: { account_id: account.id, limit: 2 }

      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #approve' do
    before do
      post :approve, params: { id: preview_card_provider.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #reject' do
    before do
      post :reject, params: { id: preview_card_provider.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end
end

M spec/controllers/api/v1/admin/trends/links_controller_spec.rb => spec/controllers/api/v1/admin/trends/links_controller_spec.rb +47 -2
@@ 5,14 5,33 @@ require 'rails_helper'
describe Api::V1::Admin::Trends::LinksController do
  render_views

  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
  let(:role)   { UserRole.find_by(name: 'Admin') }
  let(:user)   { Fabricate(:user, role: role) }
  let(:scopes) { 'admin:read admin:write' }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
  let(:account) { Fabricate(:account) }
  let(:preview_card) { Fabricate(:preview_card) }

  before do
    allow(controller).to receive(:doorkeeper_token) { token }
  end

  shared_examples 'forbidden for wrong scope' do |wrong_scope|
    let(:scopes) { wrong_scope }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  shared_examples 'forbidden for wrong role' do |wrong_role|
    let(:role) { UserRole.find_by(name: wrong_role) }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  describe 'GET #index' do
    it 'returns http success' do
      get :index, params: { account_id: account.id, limit: 2 }


@@ 20,4 39,30 @@ describe Api::V1::Admin::Trends::LinksController do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #approve' do
    before do
      post :approve, params: { id: preview_card.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #reject' do
    before do
      post :reject, params: { id: preview_card.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end
end

M spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb => spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb +47 -2
@@ 5,14 5,33 @@ require 'rails_helper'
describe Api::V1::Admin::Trends::StatusesController do
  render_views

  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
  let(:role)   { UserRole.find_by(name: 'Admin') }
  let(:user)   { Fabricate(:user, role: role) }
  let(:scopes) { 'admin:read admin:write' }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
  let(:account) { Fabricate(:account) }
  let(:status)  { Fabricate(:status) }

  before do
    allow(controller).to receive(:doorkeeper_token) { token }
  end

  shared_examples 'forbidden for wrong scope' do |wrong_scope|
    let(:scopes) { wrong_scope }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  shared_examples 'forbidden for wrong role' do |wrong_role|
    let(:role) { UserRole.find_by(name: wrong_role) }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  describe 'GET #index' do
    it 'returns http success' do
      get :index, params: { account_id: account.id, limit: 2 }


@@ 20,4 39,30 @@ describe Api::V1::Admin::Trends::StatusesController do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #approve' do
    before do
      post :approve, params: { id: status.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #reject' do
    before do
      post :reject, params: { id: status.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end
end

M spec/controllers/api/v1/admin/trends/tags_controller_spec.rb => spec/controllers/api/v1/admin/trends/tags_controller_spec.rb +47 -2
@@ 5,14 5,33 @@ require 'rails_helper'
describe Api::V1::Admin::Trends::TagsController do
  render_views

  let(:user)    { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
  let(:role)   { UserRole.find_by(name: 'Admin') }
  let(:user)   { Fabricate(:user, role: role) }
  let(:scopes) { 'admin:read admin:write' }
  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
  let(:account) { Fabricate(:account) }
  let(:tag)     { Fabricate(:tag) }

  before do
    allow(controller).to receive(:doorkeeper_token) { token }
  end

  shared_examples 'forbidden for wrong scope' do |wrong_scope|
    let(:scopes) { wrong_scope }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  shared_examples 'forbidden for wrong role' do |wrong_role|
    let(:role) { UserRole.find_by(name: wrong_role) }

    it 'returns http forbidden' do
      expect(response).to have_http_status(403)
    end
  end

  describe 'GET #index' do
    it 'returns http success' do
      get :index, params: { account_id: account.id, limit: 2 }


@@ 20,4 39,30 @@ describe Api::V1::Admin::Trends::TagsController do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #approve' do
    before do
      post :approve, params: { id: tag.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end

  describe 'POST #reject' do
    before do
      post :reject, params: { id: tag.id }
    end

    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
    it_behaves_like 'forbidden for wrong role', ''

    it 'returns http success' do
      expect(response).to have_http_status(200)
    end
  end
end

M spec/mailers/notification_mailer_spec.rb => spec/mailers/notification_mailer_spec.rb +5 -5
@@ 29,7 29,7 @@ RSpec.describe NotificationMailer, type: :mailer do

    it 'renders the headers' do
      expect(mail.subject).to eq('You were mentioned by bob')
      expect(mail.to).to eq([receiver.email])
      expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
    end

    it 'renders the body' do


@@ 46,7 46,7 @@ RSpec.describe NotificationMailer, type: :mailer do

    it 'renders the headers' do
      expect(mail.subject).to eq('bob is now following you')
      expect(mail.to).to eq([receiver.email])
      expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
    end

    it 'renders the body' do


@@ 62,7 62,7 @@ RSpec.describe NotificationMailer, type: :mailer do

    it 'renders the headers' do
      expect(mail.subject).to eq('bob favourited your post')
      expect(mail.to).to eq([receiver.email])
      expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
    end

    it 'renders the body' do


@@ 79,7 79,7 @@ RSpec.describe NotificationMailer, type: :mailer do

    it 'renders the headers' do
      expect(mail.subject).to eq('bob boosted your post')
      expect(mail.to).to eq([receiver.email])
      expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
    end

    it 'renders the body' do


@@ 96,7 96,7 @@ RSpec.describe NotificationMailer, type: :mailer do

    it 'renders the headers' do
      expect(mail.subject).to eq('Pending follower: bob')
      expect(mail.to).to eq([receiver.email])
      expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
    end

    it 'renders the body' do

M spec/models/account_filter_spec.rb => spec/models/account_filter_spec.rb +19 -0
@@ 44,4 44,23 @@ describe AccountFilter do
      expect(filter.results).to match_array [remote_account_one]
    end
  end

  describe 'with username' do
    let!(:local_account) { Fabricate(:account, domain: nil, username: 'validUserName') }

    it 'works with @ at the beginning of the username' do
      filter = described_class.new(username: '@validUserName')
      expect(filter.results).to match_array [local_account]
    end

    it 'does not work with more than one @ at the beginning of the username' do
      filter = described_class.new(username: '@@validUserName')
      expect(filter.results).to_not match_array [local_account]
    end

    it 'does not work with @ outside the beginning of the username' do
      filter = described_class.new(username: 'validUserName@')
      expect(filter.results).to_not match_array [local_account]
    end
  end
end

M spec/services/reblog_service_spec.rb => spec/services/reblog_service_spec.rb +17 -5
@@ 35,13 35,25 @@ RSpec.describe ReblogService, type: :service do
  end

  context 'when the reblogged status is discarded in the meantime' do
    let(:status) { Fabricate(:status, account: alice, visibility: :public) }
    let(:status) { Fabricate(:status, account: alice, visibility: :public, text: 'discard-status-text') }

    # Add a callback to discard the status being reblogged after the
    # validations pass but before the database commit is executed.
    before do
      # Update the in-database attribute without reflecting the change in
      # the object. This cannot simulate all race conditions, but it is
      # pretty close.
      Status.where(id: status.id).update_all(deleted_at: Time.now.utc) # rubocop:disable Rails/SkipsModelValidations
      Status.class_eval do
        before_save :discard_status
        def discard_status
          Status
            .where(id: reblog_of_id)
            .where(text: 'discard-status-text')
            .update_all(deleted_at: Time.now.utc) # rubocop:disable Rails/SkipsModelValidations
        end
      end
    end

    # Remove race condition simulating `discard_status` callback.
    after do
      Status._save_callbacks.delete(:discard_status)
    end

    it 'raises an exception' do

M yarn.lock => yarn.lock +17 -17
@@ 6047,10 6047,10 @@ http-errors@~1.6.2:
    setprototypeof "1.1.0"
    statuses ">= 1.4.0 < 2"

http-link-header@^1.1.0:
  version "1.1.0"
  resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.0.tgz#a1ca87efdbcb7778d8d0d4525de1e6964ec1f129"
  integrity sha512-pj6N1yxOz/ANO8HHsWGg/OoIL1kmRYvQnXQ7PIRpgp+15AnEsRH8fmIJE6D1OdWG2Bov+BJHVla1fFXxg1JbbA==
http-link-header@^1.1.1:
  version "1.1.1"
  resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.1.tgz#f0e6971b0ed86e858d2077066ecb7ba4f2e50de9"
  integrity sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw==

http-parser-js@>=0.5.1:
  version "0.5.3"


@@ 8020,10 8020,10 @@ nan@^2.12.1:
  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
  integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==

nanoid@^3.3.4:
  version "3.3.4"
  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
  integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
nanoid@^3.3.6:
  version "3.3.6"
  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
  integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==

nanomatch@^1.2.9:
  version "1.2.13"


@@ 9022,12 9022,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==

postcss@^8.2.15, postcss@^8.4.21:
  version "8.4.21"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
  integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
postcss@^8.2.15, postcss@^8.4.21, postcss@^8.4.22:
  version "8.4.22"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.22.tgz#c29e6776b60ab3af602d4b513d5bd2ff9aa85dc1"
  integrity sha512-XseknLAfRHzVWjCEtdviapiBtfLdgyzExD50Rg2ePaucEesyh8Wv4VPdW0nbyDa1ydbrAxV19jvMT4+LFmcNUA==
  dependencies:
    nanoid "^3.3.4"
    nanoid "^3.3.6"
    picocolors "^1.0.0"
    source-map-js "^1.0.2"



@@ 9804,10 9804,10 @@ requires-port@^1.0.0:
  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=

reselect@^4.1.7:
  version "4.1.7"
  resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42"
  integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==
reselect@^4.1.8:
  version "4.1.8"
  resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524"
  integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==

resolve-cwd@^2.0.0:
  version "2.0.0"