~cytrogen/masto-fe

bd349cb8504318bb37383aceeb7f32210599470a — Claire 2 years ago 09ebf7e + ce1f35d
Merge commit 'ce1f35d7e213327549b960bb64f63c67a141ea40' into glitch-soc/merge-upstream

Conflicts:
- `db/schema.rb`:
  Upstream regenerated the schema file using Rails 7, the conflicts are
  caused by our extra columns.
  Applied upstream's changes, but keeping our extra columns.
172 files changed, 750 insertions(+), 653 deletions(-)

M .github/workflows/test-ruby.yml
M Gemfile.lock
M app/controllers/auth/omniauth_callbacks_controller.rb
M app/controllers/auth/sessions_controller.rb
M app/javascript/mastodon/components/media_gallery.jsx
M app/javascript/mastodon/components/picture_in_picture_placeholder.jsx
M app/javascript/mastodon/components/status.jsx
M app/javascript/mastodon/features/audio/index.jsx
M app/javascript/mastodon/features/bookmarked_statuses/index.jsx
M app/javascript/mastodon/features/explore/components/story.jsx
M app/javascript/mastodon/features/explore/links.jsx
M app/javascript/mastodon/features/favourited_statuses/index.jsx
M app/javascript/mastodon/features/lists/index.jsx
M app/javascript/mastodon/features/status/components/card.jsx
M app/javascript/mastodon/features/status/components/detailed_status.jsx
M app/javascript/mastodon/features/ui/components/media_modal.jsx
M app/javascript/mastodon/features/ui/components/video_modal.jsx
M app/javascript/mastodon/features/video/index.jsx
M app/javascript/mastodon/initial_state.js
M app/javascript/mastodon/locales/en.json
M app/javascript/styles/mastodon-light/diff.scss
M app/javascript/styles/mastodon-light/variables.scss
M app/javascript/styles/mastodon/basics.scss
M app/javascript/styles/mastodon/components.scss
M app/javascript/styles/mastodon/polls.scss
M app/lib/link_details_extractor.rb
M app/models/concerns/has_user_settings.rb
M app/models/preview_card.rb
M app/models/report.rb
M app/models/user_settings.rb
M app/models/webhook.rb
M app/serializers/initial_state_serializer.rb
M app/serializers/rest/preview_card_serializer.rb
M app/views/settings/preferences/appearance/show.html.haml
M config/locales/an.yml
M config/locales/ar.yml
M config/locales/ast.yml
M config/locales/be.yml
M config/locales/bg.yml
M config/locales/ca.yml
M config/locales/ckb.yml
M config/locales/co.yml
M config/locales/cs.yml
M config/locales/cy.yml
M config/locales/da.yml
M config/locales/de.yml
M config/locales/el.yml
M config/locales/en-GB.yml
M config/locales/en.yml
M config/locales/eo.yml
M config/locales/es-AR.yml
M config/locales/es-MX.yml
M config/locales/es.yml
M config/locales/et.yml
M config/locales/eu.yml
M config/locales/fa.yml
M config/locales/fi.yml
M config/locales/fo.yml
M config/locales/fr-QC.yml
M config/locales/fr.yml
M config/locales/fy.yml
M config/locales/gd.yml
M config/locales/gl.yml
M config/locales/he.yml
M config/locales/hu.yml
M config/locales/id.yml
M config/locales/io.yml
M config/locales/is.yml
M config/locales/it.yml
M config/locales/ja.yml
M config/locales/kk.yml
M config/locales/ko.yml
M config/locales/ku.yml
M config/locales/lv.yml
M config/locales/my.yml
M config/locales/nl.yml
M config/locales/nn.yml
M config/locales/no.yml
M config/locales/oc.yml
M config/locales/pl.yml
M config/locales/pt-BR.yml
M config/locales/pt-PT.yml
M config/locales/ro.yml
M config/locales/ru.yml
M config/locales/sc.yml
M config/locales/sco.yml
M config/locales/si.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.kk.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/locales/sk.yml
M config/locales/sl.yml
M config/locales/sq.yml
M config/locales/sr-Latn.yml
M config/locales/sr.yml
M config/locales/sv.yml
M config/locales/th.yml
M config/locales/tr.yml
M config/locales/uk.yml
M config/locales/vi.yml
M config/locales/zh-CN.yml
M config/locales/zh-HK.yml
M config/locales/zh-TW.yml
A db/migrate/20230724160715_add_published_at_to_preview_cards.rb
M db/schema.rb
A spec/requests/omniauth_callbacks_spec.rb
A spec/support/omniauth_mocks.rb
M yarn.lock
M .github/workflows/test-ruby.yml => .github/workflows/test-ruby.yml +4 -0
@@ 107,6 107,10 @@ jobs:
      PAM_ENABLED: true
      PAM_DEFAULT_SERVICE: pam_test
      PAM_CONTROLLED_SERVICE: pam_test_controlled
      OIDC_ENABLED: true
      OIDC_SCOPE: read
      SAML_ENABLED: true
      CAS_ENABLED: true
      BUNDLE_WITH: 'pam_authentication test'
      CI_JOBS: ${{ matrix.ci_job }}/4


M Gemfile.lock => Gemfile.lock +4 -4
@@ 103,7 103,7 @@ GEM
    attr_required (1.0.1)
    awrence (1.2.1)
    aws-eventstream (1.2.0)
    aws-partitions (1.786.0)
    aws-partitions (1.791.0)
    aws-sdk-core (3.178.0)
      aws-eventstream (~> 1, >= 1.0.2)
      aws-partitions (~> 1, >= 1.651.0)


@@ 112,7 112,7 @@ GEM
    aws-sdk-kms (1.71.0)
      aws-sdk-core (~> 3, >= 3.177.0)
      aws-sigv4 (~> 1.1)
    aws-sdk-s3 (1.130.0)
    aws-sdk-s3 (1.131.0)
      aws-sdk-core (~> 3, >= 3.177.0)
      aws-sdk-kms (~> 1)
      aws-sigv4 (~> 1.6)


@@ 144,7 144,7 @@ GEM
    blurhash (0.1.7)
    bootsnap (1.16.0)
      msgpack (~> 1.2)
    brakeman (6.0.0)
    brakeman (6.0.1)
    browser (5.3.1)
    brpoplpush-redis_script (0.1.3)
      concurrent-ruby (~> 1.0, >= 1.0.5)


@@ 569,7 569,7 @@ GEM
    rake (13.0.6)
    rdf (3.2.11)
      link_header (~> 0.0, >= 0.0.8)
    rdf-normalize (0.6.0)
    rdf-normalize (0.6.1)
      rdf (~> 3.2)
    redcarpet (3.6.0)
    redis (4.8.1)

M app/controllers/auth/omniauth_callbacks_controller.rb => app/controllers/auth/omniauth_callbacks_controller.rb +28 -11
@@ 5,21 5,13 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def self.provides_callback_for(provider)
    define_method provider do
      @provider = provider
      @user = User.find_for_oauth(request.env['omniauth.auth'], current_user)

      if @user.persisted?
        LoginActivity.create(
          user: @user,
          success: true,
          authentication_method: :omniauth,
          provider: provider,
          ip: request.remote_ip,
          user_agent: request.user_agent
        )

        record_login_activity
        sign_in_and_redirect @user, event: :authentication
        label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
        set_flash_message(:notice, :success, kind: label) if is_navigational_format?
        set_flash_message(:notice, :success, kind: label_for_provider) if is_navigational_format?
      else
        session["devise.#{provider}_data"] = request.env['omniauth.auth']
        redirect_to new_user_registration_url


@@ 38,4 30,29 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
      auth_setup_path(missing_email: '1')
    end
  end

  private

  def record_login_activity
    LoginActivity.create(
      user: @user,
      success: true,
      authentication_method: :omniauth,
      provider: @provider,
      ip: request.remote_ip,
      user_agent: request.user_agent
    )
  end

  def label_for_provider
    provider_display_name || configured_provider_name
  end

  def provider_display_name
    Devise.omniauth_configs[@provider]&.strategy&.display_name.presence
  end

  def configured_provider_name
    I18n.t("auth.providers.#{@provider}", default: @provider.to_s.chomp('_oauth2').capitalize)
  end
end

M app/controllers/auth/sessions_controller.rb => app/controllers/auth/sessions_controller.rb +1 -1
@@ 113,7 113,7 @@ class Auth::SessionsController < Devise::SessionsController
  end

  def home_paths(resource)
    paths = [about_path]
    paths = [about_path, '/explore']

    paths << short_account_path(username: resource.account) if single_user_mode? && resource.is_a?(User)


M app/javascript/mastodon/components/media_gallery.jsx => app/javascript/mastodon/components/media_gallery.jsx +5 -10
@@ 12,7 12,7 @@ import { debounce } from 'lodash';

import { Blurhash } from 'mastodon/components/blurhash';

import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';

import { IconButton } from './icon_button';



@@ 209,7 209,6 @@ class MediaGallery extends PureComponent {

  static propTypes = {
    sensitive: PropTypes.bool,
    standalone: PropTypes.bool,
    media: ImmutablePropTypes.list.isRequired,
    lang: PropTypes.string,
    size: PropTypes.object,


@@ 223,10 222,6 @@ class MediaGallery extends PureComponent {
    onToggleVisibility: PropTypes.func,
  };

  static defaultProps = {
    standalone: false,
  };

  state = {
    visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
    width: this.props.defaultWidth,


@@ 295,7 290,7 @@ class MediaGallery extends PureComponent {
  }

  render () {
    const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay } = this.props;
    const { media, lang, intl, sensitive, defaultWidth, autoplay } = this.props;
    const { visible } = this.state;
    const width = this.state.width || defaultWidth;



@@ 303,16 298,16 @@ class MediaGallery extends PureComponent {

    const style = {};

    if (this.isFullSizeEligible() && (standalone || !cropImages)) {
    if (this.isFullSizeEligible()) {
      style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
    } else {
      style.aspectRatio = '16 / 9';
      style.aspectRatio = '3 / 2';
    }

    const size     = media.take(4).size;
    const uncached = media.every(attachment => attachment.get('type') === 'unknown');

    if (standalone && this.isFullSizeEligible()) {
    if (this.isFullSizeEligible()) {
      children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
    } else {
      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);

M app/javascript/mastodon/components/picture_in_picture_placeholder.jsx => app/javascript/mastodon/components/picture_in_picture_placeholder.jsx +4 -1
@@ 12,6 12,7 @@ class PictureInPicturePlaceholder extends PureComponent {

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    aspectRatio: PropTypes.string,
  };

  handleClick = () => {


@@ 20,8 21,10 @@ class PictureInPicturePlaceholder extends PureComponent {
  };

  render () {
    const { aspectRatio } = this.props;

    return (
      <div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}>
      <div className='picture-in-picture-placeholder' style={{ aspectRatio }} role='button' tabIndex={0} onClick={this.handleClick}>
        <Icon id='window-restore' />
        <FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
      </div>

M app/javascript/mastodon/components/status.jsx => app/javascript/mastodon/components/status.jsx +30 -20
@@ 19,7 19,6 @@ import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { displayMedia } from '../initial_state';

import AttachmentList from './attachment_list';
import { Avatar } from './avatar';
import { AvatarOverlay } from './avatar_overlay';
import { DisplayName } from './display_name';


@@ 191,17 190,35 @@ class Status extends ImmutablePureComponent {
    this.props.onTranslate(this._properStatus());
  };

  renderLoadingMediaGallery () {
    return <div className='media-gallery' style={{ height: '110px' }} />;
  }
  getAttachmentAspectRatio () {
    const attachments = this._properStatus().get('media_attachments');

  renderLoadingVideoPlayer () {
    return <div className='video-player' style={{ height: '110px' }} />;
    if (attachments.getIn([0, 'type']) === 'video') {
      return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
    } else if (attachments.getIn([0, 'type']) === 'audio') {
      return '16 / 9';
    } else {
      return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
    }
  }

  renderLoadingAudioPlayer () {
    return <div className='audio-player' style={{ height: '110px' }} />;
  }
  renderLoadingMediaGallery = () => {
    return (
      <div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
    );
  };

  renderLoadingVideoPlayer = () => {
    return (
      <div className='video-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
    );
  };

  renderLoadingAudioPlayer = () => {
    return (
      <div className='audio-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
    );
  };

  handleOpenVideo = (options) => {
    const status = this._properStatus();


@@ 426,18 443,11 @@ class Status extends ImmutablePureComponent {
    }

    if (pictureInPicture.get('inUse')) {
      media = <PictureInPicturePlaceholder />;
      media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
    } else if (status.get('media_attachments').size > 0) {
      const language = status.getIn(['translation', 'language']) || status.get('language');

      if (this.props.muted) {
        media = (
          <AttachmentList
            compact
            media={status.get('media_attachments')}
          />
        );
      } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
      if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
        const attachment = status.getIn(['media_attachments', 0]);
        const description = attachment.getIn(['translation', 'description']) || attachment.get('description');



@@ 475,11 485,11 @@ class Status extends ImmutablePureComponent {
              <Component
                preview={attachment.get('preview_url')}
                frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
                aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
                blurhash={attachment.get('blurhash')}
                src={attachment.get('url')}
                alt={description}
                lang={language}
                inline
                sensitive={status.get('sensitive')}
                onOpenVideo={this.handleOpenVideo}
                deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}


@@ 508,7 518,7 @@ class Status extends ImmutablePureComponent {
          </Bundle>
        );
      }
    } else if (status.get('spoiler_text').length === 0 && status.get('card') && !this.props.muted) {
    } else if (status.get('spoiler_text').length === 0 && status.get('card')) {
      media = (
        <Card
          onOpenMedia={this.handleOpenMedia}

M app/javascript/mastodon/features/audio/index.jsx => app/javascript/mastodon/features/audio/index.jsx +5 -1
@@ 470,6 470,7 @@ class Audio extends PureComponent {
    const progress = Math.min((currentTime / duration) * 100, 100);

    let warning;

    if (sensitive) {
      warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
    } else {


@@ 515,7 516,10 @@ class Audio extends PureComponent {

        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
          <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
            <span className='spoiler-button__overlay__label'>{warning}</span>
            <span className='spoiler-button__overlay__label'>
              {warning}
              <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
            </span>
          </button>
        </div>


M app/javascript/mastodon/features/bookmarked_statuses/index.jsx => app/javascript/mastodon/features/bookmarked_statuses/index.jsx +0 -1
@@ 86,7 86,6 @@ class Bookmarks extends ImmutablePureComponent {
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
          showBackButton
        />

        <StatusList

M app/javascript/mastodon/features/explore/components/story.jsx => app/javascript/mastodon/features/explore/components/story.jsx +12 -5
@@ 1,10 1,13 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { FormattedMessage } from 'react-intl';

import classNames from 'classnames';

import { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';



@@ 13,10 16,14 @@ export default class Story extends PureComponent {
  static propTypes = {
    url: PropTypes.string,
    title: PropTypes.string,
    lang: PropTypes.string,
    publisher: PropTypes.string,
    publishedAt: PropTypes.string,
    author: PropTypes.string,
    sharedTimes: PropTypes.number,
    thumbnail: PropTypes.string,
    blurhash: PropTypes.string,
    expanded: PropTypes.bool,
  };

  state = {


@@ 26,16 33,16 @@ export default class Story extends PureComponent {
  handleImageLoad = () => this.setState({ thumbnailLoaded: true });

  render () {
    const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props;
    const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, blurhash } = this.props;

    const { thumbnailLoaded } = this.state;

    return (
      <a className='story' href={url} target='blank' rel='noopener'>
      <a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
        <div className='story__details'>
          <div className='story__details__publisher'>{publisher ? publisher : <Skeleton width={50} />}</div>
          <div className='story__details__title'>{title ? title : <Skeleton />}</div>
          <div className='story__details__shared'>{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
          <div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
          <div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
          <div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
        </div>

        <div className='story__thumbnail'>

M app/javascript/mastodon/features/explore/links.jsx => app/javascript/mastodon/features/explore/links.jsx +5 -1
@@ 55,12 55,16 @@ class Links extends PureComponent {
      <div className='explore__links'>
        {banner}

        {isLoading ? (<LoadingIndicator />) : links.map(link => (
        {isLoading ? (<LoadingIndicator />) : links.map((link, i) => (
          <Story
            key={link.get('id')}
            expanded={i === 0}
            lang={link.get('language')}
            url={link.get('url')}
            title={link.get('title')}
            publisher={link.get('provider_name')}
            publishedAt={link.get('published_at')}
            author={link.get('author_name')}
            sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
            thumbnail={link.get('image')}
            blurhash={link.get('blurhash')}

M app/javascript/mastodon/features/favourited_statuses/index.jsx => app/javascript/mastodon/features/favourited_statuses/index.jsx +0 -1
@@ 86,7 86,6 @@ class Favourites extends ImmutablePureComponent {
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
          showBackButton
        />

        <StatusList

M app/javascript/mastodon/features/lists/index.jsx => app/javascript/mastodon/features/lists/index.jsx +1 -1
@@ 65,7 65,7 @@ class Lists extends ImmutablePureComponent {

    return (
      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} showBackButton />
        <ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} />

        <NewListForm />


M app/javascript/mastodon/features/status/components/card.jsx => app/javascript/mastodon/features/status/components/card.jsx +25 -24
@@ 12,6 12,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';

import { Blurhash } from 'mastodon/components/blurhash';
import { Icon }  from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { useBlurhash } from 'mastodon/initial_state';

const IDNA_PREFIX = 'xn--';


@@ 57,14 58,9 @@ export default class Card extends PureComponent {
  static propTypes = {
    card: ImmutablePropTypes.map,
    onOpenMedia: PropTypes.func.isRequired,
    compact: PropTypes.bool,
    sensitive: PropTypes.bool,
  };

  static defaultProps = {
    compact: false,
  };

  state = {
    previewLoaded: false,
    embedded: false,


@@ 148,7 144,7 @@ export default class Card extends PureComponent {
  }

  render () {
    const { card, compact } = this.props;
    const { card } = this.props;
    const { embedded, revealed } = this.state;

    if (card === null) {


@@ 156,29 152,27 @@ export default class Card extends PureComponent {
    }

    const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
    const horizontal  = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded;
    const interactive = card.get('type') !== 'link';
    const className   = classnames('status-card', { horizontal, compact, interactive });
    const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
    const language    = card.get('language') || '';

    const description = (
      <div className='status-card__content' lang={language}>
        {title}
        {!(horizontal || compact) && <p className='status-card__description' title={card.get('description')}>{card.get('description')}</p>}
        <span className='status-card__host'>{provider}</span>
      <div className='status-card__content'>
        <span className='status-card__host'>
          <span lang={language}>{provider}</span>
          {card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>}
         </span>
        <strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
        {card.get('author_name').length > 0 && <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span>}
      </div>
    );

    const thumbnailStyle = {
      visibility: revealed? null : 'hidden',
      visibility: revealed ? null : 'hidden',
      aspectRatio: `${card.get('width')} / ${card.get('height')}`
    };

    if (horizontal) {
      thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`;
    }
    let embed;

    let embed     = '';
    let canvas = (
      <Blurhash
        className={classnames('status-card__image-preview', {


@@ 188,12 182,18 @@ export default class Card extends PureComponent {
        dummy={!useBlurhash}
      />
    );

    let thumbnail = <img src={card.get('image')} alt='' style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />;

    let spoilerButton = (
      <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
        <span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
        <span className='spoiler-button__overlay__label'>
          <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />
          <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
        </span>
      </button>
    );

    spoilerButton = (
      <div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
        {spoilerButton}


@@ 219,19 219,20 @@ export default class Card extends PureComponent {
              <div className='status-card__actions'>
                <div>
                  <button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
                  {horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
                  <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>
                </div>
              </div>
            )}

            {!revealed && spoilerButton}
          </div>
        );
      }

      return (
        <div className={className} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
        <div className='status-card' ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
          {embed}
          {!compact && description}
          <a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a>
        </div>
      );
    } else if (card.get('image')) {


@@ 243,14 244,14 @@ export default class Card extends PureComponent {
      );
    } else {
      embed = (
        <div className='status-card__image'>
        <div className='status-card__image' style={{ aspectRatio: '1.9 / 1' }}>
          <Icon id='file-text' />
        </div>
      );
    }

    return (
      <a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
      <a href={card.get('url')} className='status-card' target='_blank' rel='noopener noreferrer' ref={this.setRef}>
        {embed}
        {description}
      </a>

M app/javascript/mastodon/features/status/components/detailed_status.jsx => app/javascript/mastodon/features/status/components/detailed_status.jsx +25 -3
@@ 113,8 113,30 @@ class DetailedStatus extends ImmutablePureComponent {
    onTranslate(status);
  };

  _properStatus () {
    const { status } = this.props;

    if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
      return status.get('reblog');
    } else {
      return status;
    }
  }

  getAttachmentAspectRatio () {
    const attachments = this._properStatus().get('media_attachments');

    if (attachments.getIn([0, 'type']) === 'video') {
      return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
    } else if (attachments.getIn([0, 'type']) === 'audio') {
      return '16 / 9';
    } else {
      return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
    }
  }

  render () {
    const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
    const status = this._properStatus();
    const outerStyle = { boxSizing: 'border-box' };
    const { intl, compact, pictureInPicture } = this.props;



@@ 136,7 158,7 @@ class DetailedStatus extends ImmutablePureComponent {
    const language = status.getIn(['translation', 'language']) || status.get('language');

    if (pictureInPicture.get('inUse')) {
      media = <PictureInPicturePlaceholder />;
      media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
    } else if (status.get('media_attachments').size > 0) {
      if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
        const attachment = status.getIn(['media_attachments', 0]);


@@ 167,13 189,13 @@ class DetailedStatus extends ImmutablePureComponent {
          <Video
            preview={attachment.get('preview_url')}
            frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
            aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
            blurhash={attachment.get('blurhash')}
            src={attachment.get('url')}
            alt={description}
            lang={language}
            width={300}
            height={150}
            inline
            onOpenVideo={this.handleOpenVideo}
            sensitive={status.get('sensitive')}
            visible={this.props.showMedia}

M app/javascript/mastodon/features/ui/components/media_modal.jsx => app/javascript/mastodon/features/ui/components/media_modal.jsx +1 -0
@@ 172,6 172,7 @@ class MediaModal extends ImmutablePureComponent {
            width={image.get('width')}
            height={image.get('height')}
            frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
            aspectRatio={`${image.getIn(['meta', 'original', 'width'])} / ${image.getIn(['meta', 'original', 'height'])}`}
            currentTime={currentTime || 0}
            autoPlay={autoPlay || false}
            volume={volume || 1}

M app/javascript/mastodon/features/ui/components/video_modal.jsx => app/javascript/mastodon/features/ui/components/video_modal.jsx +1 -0
@@ 49,6 49,7 @@ class VideoModal extends ImmutablePureComponent {
          <Video
            preview={media.get('preview_url')}
            frameRate={media.getIn(['meta', 'original', 'frame_rate'])}
            aspectRatio={`${media.getIn(['meta', 'original', 'width'])} / ${media.getIn(['meta', 'original', 'height'])}`}
            blurhash={media.get('blurhash')}
            src={media.get('url')}
            currentTime={options.startTime}

M app/javascript/mastodon/features/video/index.jsx => app/javascript/mastodon/features/video/index.jsx +90 -89
@@ 105,6 105,7 @@ class Video extends PureComponent {
  static propTypes = {
    preview: PropTypes.string,
    frameRate: PropTypes.string,
    aspectRatio: PropTypes.string,
    src: PropTypes.string.isRequired,
    alt: PropTypes.string,
    lang: PropTypes.string,


@@ 113,7 114,6 @@ class Video extends PureComponent {
    onOpenVideo: PropTypes.func,
    onCloseVideo: PropTypes.func,
    detailed: PropTypes.bool,
    inline: PropTypes.bool,
    editable: PropTypes.bool,
    alwaysVisible: PropTypes.bool,
    visible: PropTypes.bool,


@@ 500,14 500,9 @@ class Video extends PureComponent {
  }

  render () {
    const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
    const { preview, src, aspectRatio, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
    const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
    const progress = Math.min((currentTime / duration) * 100, 100);
    const playerStyle = {};

    if (inline) {
      playerStyle.aspectRatio = '16 / 9';
    }

    let preload;



@@ 527,95 522,101 @@ class Video extends PureComponent {
      warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
    }

    // The outer wrapper is necessary to avoid reflowing the layout when going into full screen
    return (
      <div
        role='menuitem'
        className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable })}
        style={playerStyle}
        ref={this.setPlayerRef}
        onMouseEnter={this.handleMouseEnter}
        onMouseLeave={this.handleMouseLeave}
        onClick={this.handleClickRoot}
        onKeyDown={this.handleKeyDown}
        tabIndex={0}
      >
        <Blurhash
          hash={blurhash}
          className={classNames('media-gallery__preview', {
            'media-gallery__preview--hidden': revealed,
          })}
          dummy={!useBlurhash}
        />

        {(revealed || editable) && <video
          ref={this.setVideoRef}
          src={src}
          poster={preview}
          preload={preload}
          role='button'
      <div style={{ aspectRatio }}>
        <div
          role='menuitem'
          className={classNames('video-player', { inactive: !revealed, detailed, fullscreen, editable })}
          style={{ aspectRatio }}
          ref={this.setPlayerRef}
          onMouseEnter={this.handleMouseEnter}
          onMouseLeave={this.handleMouseLeave}
          onClick={this.handleClickRoot}
          onKeyDown={this.handleKeyDown}
          tabIndex={0}
          aria-label={alt}
          title={alt}
          lang={lang}
          volume={volume}
          onClick={this.togglePlay}
          onKeyDown={this.handleVideoKeyDown}
          onPlay={this.handlePlay}
          onPause={this.handlePause}
          onLoadedData={this.handleLoadedData}
          onProgress={this.handleProgress}
          onVolumeChange={this.handleVolumeChange}
          style={{ ...playerStyle, width: '100%' }}
        />}

        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
          <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
            <span className='spoiler-button__overlay__label'>{warning}</span>
          </button>
        </div>

        <div className={classNames('video-player__controls', { active: paused || hovered })}>
          <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
            <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
            <div className='video-player__seek__progress' style={{ width: `${progress}%` }} />

            <span
              className={classNames('video-player__seek__handle', { active: dragging })}
              tabIndex={0}
              style={{ left: `${progress}%` }}
              onKeyDown={this.handleVideoKeyDown}
            />
        >
          <Blurhash
            hash={blurhash}
            className={classNames('media-gallery__preview', {
              'media-gallery__preview--hidden': revealed,
            })}
            dummy={!useBlurhash}
          />

          {(revealed || editable) && <video
            ref={this.setVideoRef}
            src={src}
            poster={preview}
            preload={preload}
            role='button'
            tabIndex={0}
            aria-label={alt}
            title={alt}
            lang={lang}
            volume={volume}
            onClick={this.togglePlay}
            onKeyDown={this.handleVideoKeyDown}
            onPlay={this.handlePlay}
            onPause={this.handlePause}
            onLoadedData={this.handleLoadedData}
            onProgress={this.handleProgress}
            onVolumeChange={this.handleVolumeChange}
            style={{ width: '100%' }}
          />}

          <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
            <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
              <span className='spoiler-button__overlay__label'>
                {warning}
                <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
              </span>
            </button>
          </div>

          <div className='video-player__buttons-bar'>
            <div className='video-player__buttons left'>
              <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
              <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>

              <div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
                <div className='video-player__volume__current' style={{ width: `${volume * 100}%` }} />
          <div className={classNames('video-player__controls', { active: paused || hovered })}>
            <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
              <div className='video-player__seek__buffer' style={{ width: `${buffer}%` }} />
              <div className='video-player__seek__progress' style={{ width: `${progress}%` }} />

              <span
                className={classNames('video-player__seek__handle', { active: dragging })}
                tabIndex={0}
                style={{ left: `${progress}%` }}
                onKeyDown={this.handleVideoKeyDown}
              />
            </div>

                <span
                  className={classNames('video-player__volume__handle')}
                  tabIndex={0}
                  style={{ left: `${volume * 100}%` }}
                />
            <div className='video-player__buttons-bar'>
              <div className='video-player__buttons left'>
                <button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
                <button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>

                <div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
                  <div className='video-player__volume__current' style={{ width: `${volume * 100}%` }} />

                  <span
                    className={classNames('video-player__volume__handle')}
                    tabIndex={0}
                    style={{ left: `${volume * 100}%` }}
                  />
                </div>

                {(detailed || fullscreen) && (
                  <span className='video-player__time'>
                    <span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
                    <span className='video-player__time-sep'>/</span>
                    <span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
                  </span>
                )}
              </div>

              {(detailed || fullscreen) && (
                <span className='video-player__time'>
                  <span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
                  <span className='video-player__time-sep'>/</span>
                  <span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
                </span>
              )}
            </div>

            <div className='video-player__buttons right'>
              {(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
              {(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
              {onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
              <button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} className='player-button' onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
              <div className='video-player__buttons right'>
                {(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
                {(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
                {onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
                <button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} className='player-button' onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
              </div>
            </div>
          </div>
        </div>

M app/javascript/mastodon/initial_state.js => app/javascript/mastodon/initial_state.js +0 -2
@@ 51,7 51,6 @@
 * @property {boolean} activity_api_enabled
 * @property {string} admin
 * @property {boolean=} boost_modal
 * @property {boolean} crop_images
 * @property {boolean=} delete_modal
 * @property {boolean=} disable_swiping
 * @property {string=} disabled_account_id


@@ 112,7 111,6 @@ const getMeta = (prop) => initialState?.meta && initialState.meta[prop];
export const activityApiEnabled = getMeta('activity_api_enabled');
export const autoPlayGif = getMeta('auto_play_gif');
export const boostModal = getMeta('boost_modal');
export const cropImages = getMeta('crop_images');
export const deleteModal = getMeta('delete_modal');
export const disableSwiping = getMeta('disable_swiping');
export const disabledAccountId = getMeta('disabled_account_id');

M app/javascript/mastodon/locales/en.json => app/javascript/mastodon/locales/en.json +1 -0
@@ 363,6 363,7 @@
  "lightbox.previous": "Previous",
  "limited_account_hint.action": "Show profile anyway",
  "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
  "link_preview.author": "By {name}",
  "lists.account.add": "Add to list",
  "lists.account.remove": "Remove from list",
  "lists.delete": "Delete list",

M app/javascript/styles/mastodon-light/diff.scss => app/javascript/styles/mastodon-light/diff.scss +9 -2
@@ 24,13 24,16 @@ html {
.column > .scrollable,
.getting-started,
.column-inline-form,
.error-column,
.regeneration-indicator {
  background: $white;
  border: 1px solid lighten($ui-base-color, 8%);
  border-top: 0;
}

.error-column {
  border: 1px solid lighten($ui-base-color, 8%);
}

.column > .scrollable.about {
  border-top: 1px solid lighten($ui-base-color, 8%);
}


@@ 77,6 80,10 @@ html {
  background: $white;
}

.column-header {
  border-bottom: 0;
}

.column-header__button.active {
  color: $ui-highlight-color;



@@ 414,7 421,7 @@ html {
.column-header__collapsible-inner {
  background: darken($ui-base-color, 4%);
  border: 1px solid lighten($ui-base-color, 8%);
  border-top: 0;
  border-bottom: 0;
}

.dashboard__quick-access,

M app/javascript/styles/mastodon-light/variables.scss => app/javascript/styles/mastodon-light/variables.scss +1 -1
@@ 5,7 5,7 @@ $white: #ffffff;
$classic-base-color: #282c37;
$classic-primary-color: #9baec8;
$classic-secondary-color: #d9e1e8;
$classic-highlight-color: #6364ff;
$classic-highlight-color: #858afa;

$blurple-600: #563acc; // Iris
$blurple-500: #6364ff; // Brand purple

M app/javascript/styles/mastodon/basics.scss => app/javascript/styles/mastodon/basics.scss +4 -0
@@ 175,6 175,10 @@ a {
button {
  font-family: inherit;
  cursor: pointer;

  &:focus:not(:focus-visible) {
    outline: none;
  }
}

.app-holder {

M app/javascript/styles/mastodon/components.scss => app/javascript/styles/mastodon/components.scss +97 -99
@@ 252,13 252,14 @@

  &.overlayed {
    box-sizing: content-box;
    background: rgba($base-overlay-background, 0.6);
    color: rgba($primary-text-color, 0.7);
    background: rgba($black, 0.65);
    backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
    color: rgba($white, 0.7);
    border-radius: 4px;
    padding: 2px;

    &:hover {
      background: rgba($base-overlay-background, 0.9);
      background: rgba($black, 0.9);
    }
  }



@@ 1352,6 1353,10 @@ body > [data-popper-placement] {
  }
}

.scrollable > div:first-child .detailed-status {
  border-top: 0;
}

.detailed-status__meta {
  margin-top: 16px;
  color: $dark-text-color;


@@ 3504,12 3509,10 @@ button.icon-button.active i.fa-retweet {
}

.status-card {
  display: block;
  position: relative;
  display: flex;
  font-size: 14px;
  border: 1px solid lighten($ui-base-color, 8%);
  border-radius: 4px;
  color: $dark-text-color;
  color: $darker-text-color;
  margin-top: 14px;
  text-decoration: none;
  overflow: hidden;


@@ 3563,8 3566,29 @@ button.icon-button.active i.fa-retweet {
a.status-card {
  cursor: pointer;

  &:hover {
    background: lighten($ui-base-color, 8%);
  &:hover,
  &:focus,
  &:active {
    .status-card__title,
    .status-card__host,
    .status-card__author {
      color: $highlight-text-color;
    }
  }
}

.status-card a {
  color: inherit;
  text-decoration: none;

  &:hover,
  &:focus,
  &:active {
    .status-card__title,
    .status-card__host,
    .status-card__author {
      color: $highlight-text-color;
    }
  }
}



@@ 3590,42 3614,42 @@ a.status-card {

.status-card__title {
  display: block;
  font-weight: 500;
  margin-bottom: 5px;
  color: $darker-text-color;
  font-weight: 700;
  font-size: 19px;
  line-height: 24px;
  color: $primary-text-color;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-decoration: none;
}

.status-card__content {
  flex: 1 1 auto;
  overflow: hidden;
  padding: 14px 14px 14px 8px;
  padding: 15px 0;
  padding-bottom: 0;
}

.status-card__description {
  color: $darker-text-color;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
.status-card__host {
  display: block;
  font-size: 14px;
  margin-bottom: 8px;
}

.status-card__host {
.status-card__author {
  display: block;
  margin-top: 5px;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 8px;
  font-size: 14px;
  color: $primary-text-color;

  strong {
    font-weight: 500;
  }
}

.status-card__image {
  flex: 0 0 100px;
  width: 100%;
  background: lighten($ui-base-color, 8%);
  position: relative;
  border-radius: 8px;

  & > .fa {
    font-size: 21px;


@@ 3637,50 3661,8 @@ a.status-card {
  }
}

.status-card.horizontal {
  display: block;

  .status-card__image {
    width: 100%;
  }

  .status-card__image-image,
  .status-card__image-preview {
    border-radius: 4px 4px 0 0;
  }

  .status-card__title {
    white-space: inherit;
  }
}

.status-card.compact {
  border-color: lighten($ui-base-color, 4%);

  &.interactive {
    border: 0;
  }

  .status-card__content {
    padding: 8px;
    padding-top: 10px;
  }

  .status-card__title {
    white-space: nowrap;
  }

  .status-card__image {
    flex: 0 0 60px;
  }
}

a.status-card.compact:hover {
  background-color: lighten($ui-base-color, 4%);
}

.status-card__image-image {
  border-radius: 4px 0 0 4px;
  border-radius: 8px;
  display: block;
  margin: 0;
  width: 100%;


@@ 3691,7 3673,7 @@ a.status-card.compact:hover {
}

.status-card__image-preview {
  border-radius: 4px 0 0 4px;
  border-radius: 8px;
  display: block;
  margin: 0;
  width: 100%;


@@ 4204,6 4186,7 @@ a.status-card.compact:hover {
    margin: 0;
    border: 0;
    border-radius: 4px;
    color: $white;

    &__label {
      display: flex;


@@ 4211,7 4194,6 @@ a.status-card.compact:hover {
      justify-content: center;
      gap: 8px;
      flex-direction: column;
      color: $primary-text-color;
      font-weight: 500;
      font-size: 14px;
    }


@@ 5033,7 5015,6 @@ a.status-card.compact:hover {
    position: absolute;
    top: 16px;
    inset-inline-end: 10px;
    z-index: 2;
    display: inline-block;
    opacity: 0;
    transition: all 100ms linear;


@@ 5172,9 5153,9 @@ a.status-card.compact:hover {
  display: flex;
}

.video-modal__container {
.video-modal .video-player {
  max-height: 80vh;
  max-width: 100vw;
  max-height: 100vh;
}

.audio-modal__container {


@@ 6192,7 6173,7 @@ a.status-card.compact:hover {
  box-sizing: border-box;
  margin-top: 8px;
  overflow: hidden;
  border-radius: 4px;
  border-radius: 8px;
  position: relative;
  width: 100%;
  min-height: 64px;


@@ 6207,7 6188,7 @@ a.status-card.compact:hover {
  box-sizing: border-box;
  display: block;
  position: relative;
  border-radius: 4px;
  border-radius: 8px;
  overflow: hidden;

  &--tall {


@@ 6293,7 6274,7 @@ a.status-card.compact:hover {
  box-sizing: border-box;
  position: relative;
  background: darken($ui-base-color, 8%);
  border-radius: 4px;
  border-radius: 8px;
  padding-bottom: 44px;
  width: 100%;



@@ 6360,7 6341,7 @@ a.status-card.compact:hover {
  position: relative;
  background: $base-shadow-color;
  max-width: 100%;
  border-radius: 4px;
  border-radius: 8px;
  box-sizing: border-box;
  color: $white;
  display: flex;


@@ 6377,8 6358,6 @@ a.status-card.compact:hover {

  video {
    display: block;
    max-width: 100vw;
    max-height: 80vh;
    z-index: 1;
  }



@@ 6386,22 6365,15 @@ a.status-card.compact:hover {
    width: 100% !important;
    height: 100% !important;
    margin: 0;
    aspect-ratio: auto !important;

    video {
      max-width: 100% !important;
      max-height: 100% !important;
      width: 100% !important;
      height: 100% !important;
      outline: 0;
    }
  }

  &.inline {
    video {
      object-fit: contain;
    }
  }

  &__controls {
    position: absolute;
    direction: ltr;


@@ 8163,6 8135,7 @@ noscript {
  .search__input {
    border: 1px solid lighten($ui-base-color, 8%);
    padding: 10px;
    padding-inline-end: 28px;
  }

  .search__popout {


@@ 8191,8 8164,9 @@ noscript {
  align-items: center;
  color: $primary-text-color;
  text-decoration: none;
  padding: 15px 0;
  padding: 15px;
  border-bottom: 1px solid lighten($ui-base-color, 8%);
  gap: 15px;

  &:last-child {
    border-bottom: 0;


@@ 8201,33 8175,40 @@ noscript {
  &:hover,
  &:active,
  &:focus {
    background-color: lighten($ui-base-color, 4%);
    color: $highlight-text-color;

    .story__details__publisher,
    .story__details__shared {
      color: $highlight-text-color;
    }
  }

  &__details {
    padding: 0 15px;
    flex: 1 1 auto;

    &__publisher {
      color: $darker-text-color;
      margin-bottom: 4px;
      margin-bottom: 8px;
    }

    &__title {
      font-size: 19px;
      line-height: 24px;
      font-weight: 500;
      margin-bottom: 4px;
      margin-bottom: 8px;
    }

    &__shared {
      color: $darker-text-color;
    }

    strong {
      font-weight: 500;
    }
  }

  &__thumbnail {
    flex: 0 0 auto;
    margin: 0 15px;
    position: relative;
    width: 120px;
    height: 120px;


@@ 8238,7 8219,7 @@ noscript {
    }

    img {
      border-radius: 4px;
      border-radius: 8px;
      display: block;
      margin: 0;
      width: 100%;


@@ 8247,7 8228,7 @@ noscript {
    }

    &__preview {
      border-radius: 4px;
      border-radius: 8px;
      display: block;
      margin: 0;
      width: 100%;


@@ 8263,6 8244,23 @@ noscript {
      }
    }
  }

  &.expanded {
    flex-direction: column;

    .story__thumbnail {
      order: 1;
      width: 100%;
      height: auto;
      aspect-ratio: 1.91 / 1;
    }

    .story__details {
      order: 2;
      width: 100%;
      flex: 0 0 auto;
    }
  }
}

.server-banner {

M app/javascript/styles/mastodon/polls.scss => app/javascript/styles/mastodon/polls.scss +21 -5
@@ 121,11 121,6 @@
      border-radius: 4px;
    }

    &.active {
      border-color: $valid-value-color;
      background: $valid-value-color;
    }

    &:active,
    &:focus,
    &:hover {


@@ 133,6 128,11 @@
      border-width: 4px;
    }

    &.active {
      background-color: $valid-value-color;
      border-color: $valid-value-color;
    }

    &::-moz-focus-inner {
      outline: 0 !important;
      border: 0;


@@ 216,6 216,14 @@
    padding: 10px;
  }

  .poll__input {
    &:active,
    &:focus,
    &:hover {
      border-color: $ui-button-focus-background-color;
    }
  }

  .poll__footer {
    border-top: 1px solid darken($simple-background-color, 8%);
    padding: 10px;


@@ 241,6 249,14 @@
    color: $action-button-color;
    border-color: $action-button-color;
    margin-inline-end: 5px;

    &:hover,
    &:focus,
    &.active {
      border-color: $action-button-color;
      background-color: $action-button-color;
      color: $ui-button-color;
    }
  }

  li {

M app/lib/link_details_extractor.rb => app/lib/link_details_extractor.rb +5 -0
@@ 124,6 124,7 @@ class LinkDetailsExtractor
      author_url: author_url || '',
      embed_url: embed_url || '',
      language: language,
      published_at: published_at.presence,
    }
  end



@@ 159,6 160,10 @@ class LinkDetailsExtractor
    html_entities.decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description'))
  end

  def published_at
    structured_data&.date_published || opengraph_tag('article:published_time')
  end

  def image
    valid_url_or_nil(opengraph_tag('og:image'))
  end

M app/models/concerns/has_user_settings.rb => app/models/concerns/has_user_settings.rb +0 -4
@@ 103,10 103,6 @@ module HasUserSettings
    settings['web.trends']
  end

  def setting_crop_images
    settings['web.crop_images']
  end

  def setting_disable_swiping
    settings['web.disable_swiping']
  end

M app/models/preview_card.rb => app/models/preview_card.rb +1 -0
@@ 30,6 30,7 @@
#  max_score_at                 :datetime
#  trendable                    :boolean
#  link_type                    :integer
#  published_at                 :datetime
#

class PreviewCard < ApplicationRecord

M app/models/report.rb => app/models/report.rb +7 -2
@@ 58,7 58,8 @@ class Report < ApplicationRecord

  before_validation :set_uri, only: :create

  after_create_commit :trigger_webhooks
  after_create_commit :trigger_create_webhooks
  after_update_commit :trigger_update_webhooks

  def object_type
    :flag


@@ 155,7 156,11 @@ class Report < ApplicationRecord
    errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
  end

  def trigger_webhooks
  def trigger_create_webhooks
    TriggerWebhookWorker.perform_async('report.created', 'Report', id)
  end

  def trigger_update_webhooks
    TriggerWebhookWorker.perform_async('report.updated', 'Report', id)
  end
end

M app/models/user_settings.rb => app/models/user_settings.rb +0 -1
@@ 20,7 20,6 @@ class UserSettings
  setting :hide_followers_count, default: false

  namespace :web do
    setting :crop_images, default: true
    setting :advanced_layout, default: false
    setting :trends, default: true
    setting :use_blurhash, default: true

M app/models/webhook.rb => app/models/webhook.rb +2 -1
@@ 20,6 20,7 @@ class Webhook < ApplicationRecord
    account.created
    account.updated
    report.created
    report.updated
    status.created
    status.updated
  ).freeze


@@ 59,7 60,7 @@ class Webhook < ApplicationRecord
    case event
    when 'account.approved', 'account.created', 'account.updated'
      :manage_users
    when 'report.created'
    when 'report.created', 'report.updated'
      :manage_reports
    when 'status.created', 'status.updated'
      :view_devops

M app/serializers/initial_state_serializer.rb => app/serializers/initial_state_serializer.rb +0 -2
@@ 65,13 65,11 @@ class InitialStateSerializer < ActiveModel::Serializer
      store[:default_content_type] = object.current_account.user.setting_default_content_type
      store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font
      store[:show_trends]       = Setting.trends && object.current_account.user.setting_trends
      store[:crop_images]       = object.current_account.user.setting_crop_images
    else
      store[:auto_play_gif] = Setting.auto_play_gif
      store[:display_media] = Setting.display_media
      store[:reduce_motion] = Setting.reduce_motion
      store[:use_blurhash]  = Setting.use_blurhash
      store[:crop_images]   = Setting.crop_images
    end

    store[:disabled_account_id] = object.disabled_account.id.to_s if object.disabled_account

M app/serializers/rest/preview_card_serializer.rb => app/serializers/rest/preview_card_serializer.rb +1 -1
@@ 6,7 6,7 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
  attributes :url, :title, :description, :language, :type,
             :author_name, :author_url, :provider_name,
             :provider_url, :html, :width, :height,
             :image, :embed_url, :blurhash
             :image, :embed_url, :blurhash, :published_at

  def image
    object.image? ? full_asset_url(object.image.url(:original)) : nil

M app/views/settings/preferences/appearance/show.html.haml => app/views/settings/preferences/appearance/show.html.haml +0 -5
@@ 37,11 37,6 @@
      = ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
      = ff.input :'web.use_system_emoji_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_emoji_font'), glitch_only: true

    %h4= t 'appearance.toot_layout'

    .fields-group
      = ff.input :'web.crop_images', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_crop_images')

    %h4= t 'appearance.discovery'

    .fields-group

M config/locales/an.yml => config/locales/an.yml +0 -1
@@ 923,7 923,6 @@ an:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Totz pueden contribuyir.
    sensitive_content: Conteniu sensible
    toot_layout: Disenyo d'as publicacions
  application_mailer:
    notification_preferences: Cambiar preferencias de correu electronico
    salutation: "%{name}:"

M config/locales/ar.yml => config/locales/ar.yml +0 -1
@@ 982,7 982,6 @@ ar:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: يمكن للجميع المساهمة.
    sensitive_content: المحتوى الحساس
    toot_layout: شكل المنشور
  application_mailer:
    notification_preferences: تعديل تفضيلات البريد الإلكتروني
    salutation: "%{name}،"

M config/locales/ast.yml => config/locales/ast.yml +0 -1
@@ 439,7 439,6 @@ ast:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: tol mundu pue collaborar.
    sensitive_content: Conteníu sensible
    toot_layout: Distribución de los artículos
  application_mailer:
    notification_preferences: Camudar les preferencies de los mensaxes de corréu electrónicu
  applications:

M config/locales/be.yml => config/locales/be.yml +0 -1
@@ 1009,7 1009,6 @@ be:
      guide_link: https://be.crowdin.com/project/mastodon/be
      guide_link_text: Кожны можа зрабіць унёсак.
    sensitive_content: Далікатны змест
    toot_layout: Макет допісу
  application_mailer:
    notification_preferences: Змяніць налады эл. пошты
    salutation: "%{name},"

M config/locales/bg.yml => config/locales/bg.yml +0 -1
@@ 973,7 973,6 @@ bg:
      guide_link: https://ru.crowdin.com/project/mastodon
      guide_link_text: Всеки може да участва.
    sensitive_content: Деликатно съдържание
    toot_layout: Оформление на публикацията
  application_mailer:
    notification_preferences: Промяна на предпочитанията за имейл
    salutation: "%{name},"

M config/locales/ca.yml => config/locales/ca.yml +0 -1
@@ 973,7 973,6 @@ ca:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Tothom hi pot contribuir.
    sensitive_content: Contingut sensible
    toot_layout: Disseny dels tuts
  application_mailer:
    notification_preferences: Canvia les preferències de correu
    salutation: "%{name},"

M config/locales/ckb.yml => config/locales/ckb.yml +0 -1
@@ 571,7 571,6 @@ ckb:
      body: ماستۆدۆن لەلایەن خۆبەخشەوە وەردەگێڕێت.
      guide_link_text: هەموو کەسێک دەتوانێت بەشداری بکات.
    sensitive_content: ناوەڕۆکی هەستیار
    toot_layout: لۆی توت
  application_mailer:
    notification_preferences: گۆڕینی پەسەندکراوەکانی ئیمەیڵ
    salutation: "%{name},"

M config/locales/co.yml => config/locales/co.yml +0 -1
@@ 537,7 537,6 @@ co:
      guide_link: https://fr.crowdin.com/project/mastodon
      guide_link_text: Tuttu u mondu pò participà.
    sensitive_content: Cuntinutu sensibile
    toot_layout: Urganizazione
  application_mailer:
    notification_preferences: Cambià e priferenze e-mail
    salutation: "%{name},"

M config/locales/cs.yml => config/locales/cs.yml +0 -1
@@ 997,7 997,6 @@ cs:
      guide_link: https://cs.crowdin.com/project/mastodon
      guide_link_text: Zapojit se může každý.
    sensitive_content: Citlivý obsah
    toot_layout: Rozložení příspěvků
  application_mailer:
    notification_preferences: Změnit předvolby e-mailů
    salutation: "%{name},"

M config/locales/cy.yml => config/locales/cy.yml +0 -1
@@ 1045,7 1045,6 @@ cy:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Gall pawb gyfrannu.
    sensitive_content: Cynnwys sensitif
    toot_layout: Cynllun postiad
  application_mailer:
    notification_preferences: Newid gosodiadau e-bost
    salutation: "%{name},"

M config/locales/da.yml => config/locales/da.yml +0 -1
@@ 973,7 973,6 @@ da:
      guide_link: https://da.crowdin.com/project/mastodon
      guide_link_text: Alle kan bidrage.
    sensitive_content: Sensitivt indhold
    toot_layout: Indlægslayout
  application_mailer:
    notification_preferences: Skift e-mailpræferencer
    salutation: "%{name}"

M config/locales/de.yml => config/locales/de.yml +0 -1
@@ 973,7 973,6 @@ de:
      guide_link: https://de.crowdin.com/project/mastodon/de
      guide_link_text: Alle können mitmachen und etwas dazu beitragen.
    sensitive_content: Inhaltswarnung
    toot_layout: Timeline-Layout
  application_mailer:
    notification_preferences: E-Mail-Einstellungen ändern
    salutation: "%{name},"

M config/locales/el.yml => config/locales/el.yml +0 -1
@@ 961,7 961,6 @@ el:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Μπορεί να συνεισφέρει ο οποιοσδήποτε.
    sensitive_content: Ευαίσθητο περιεχόμενο
    toot_layout: Διαρρύθμιση αναρτήσεων
  application_mailer:
    notification_preferences: Αλλαγή προτιμήσεων email
    salutation: "%{name},"

M config/locales/en-GB.yml => config/locales/en-GB.yml +0 -1
@@ 973,7 973,6 @@ en-GB:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Everyone can contribute.
    sensitive_content: Sensitive content
    toot_layout: Post layout
  application_mailer:
    notification_preferences: Change e-mail preferences
    salutation: "%{name},"

M config/locales/en.yml => config/locales/en.yml +0 -1
@@ 973,7 973,6 @@ en:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Everyone can contribute.
    sensitive_content: Sensitive content
    toot_layout: Post layout
  application_mailer:
    notification_preferences: Change e-mail preferences
    salutation: "%{name},"

M config/locales/eo.yml => config/locales/eo.yml +0 -1
@@ 973,7 973,6 @@ eo:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Ĉiu povas kontribui.
    sensitive_content: Tikla enhavo
    toot_layout: Mesaĝo aranĝo
  application_mailer:
    notification_preferences: Ŝanĝi retmesaĝajn preferojn
    salutation: "%{name},"

M config/locales/es-AR.yml => config/locales/es-AR.yml +0 -1
@@ 973,7 973,6 @@ es-AR:
      guide_link: https://es.crowdin.com/project/mastodon
      guide_link_text: Todos pueden contribuir.
    sensitive_content: Contenido sensible
    toot_layout: Diseño del mensaje
  application_mailer:
    notification_preferences: Cambiar configuración de correo electrónico
    salutation: "%{name}:"

M config/locales/es-MX.yml => config/locales/es-MX.yml +0 -1
@@ 973,7 973,6 @@ es-MX:
      guide_link: https://es.crowdin.com/project/mastodon
      guide_link_text: Todos pueden contribuir.
    sensitive_content: Contenido sensible
    toot_layout: Diseño de los toots
  application_mailer:
    notification_preferences: Cambiar preferencias de correo electrónico
    salutation: "%{name}:"

M config/locales/es.yml => config/locales/es.yml +0 -1
@@ 973,7 973,6 @@ es:
      guide_link: https://es.crowdin.com/project/mastodon
      guide_link_text: Todos pueden contribuir.
    sensitive_content: Contenido sensible
    toot_layout: Diseño de las publicaciones
  application_mailer:
    notification_preferences: Cambiar preferencias de correo electrónico
    salutation: "%{name}:"

M config/locales/et.yml => config/locales/et.yml +0 -1
@@ 973,7 973,6 @@ et:
      guide_link: https://crowdin.com/project/mastodon/et
      guide_link_text: Panustada võib igaüks!
    sensitive_content: Tundlik sisu
    toot_layout: Postituse väljanägemine
  application_mailer:
    notification_preferences: Muuda e-kirjade eelistusi
    salutation: "%{name}!"

M config/locales/eu.yml => config/locales/eu.yml +0 -1
@@ 972,7 972,6 @@ eu:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Edonork lagundu dezake.
    sensitive_content: Eduki hunkigarria
    toot_layout: Bidalketen diseinua
  application_mailer:
    notification_preferences: Aldatu e-mail hobespenak
    salutation: "%{name},"

M config/locales/fa.yml => config/locales/fa.yml +0 -1
@@ 821,7 821,6 @@ fa:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: همه می‌توانند کمک کنند.
    sensitive_content: محتوای حساس
    toot_layout: آرایش فرسته
  application_mailer:
    notification_preferences: تغییر ترجیحات ایمیل
    salutation: "%{name}،"

M config/locales/fi.yml => config/locales/fi.yml +0 -1
@@ 973,7 973,6 @@ fi:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Kaikki voivat osallistua.
    sensitive_content: Arkaluonteinen sisältö
    toot_layout: Viestin asettelu
  application_mailer:
    notification_preferences: Muuta sähköpostiasetuksia
    salutation: "%{name},"

M config/locales/fo.yml => config/locales/fo.yml +0 -1
@@ 973,7 973,6 @@ fo:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Øll kunnu geva íkast.
    sensitive_content: Viðkvæmt innihald
    toot_layout: Uppseting av postum
  application_mailer:
    notification_preferences: Broyt teldupostastillingar
    salutation: "%{name}"

M config/locales/fr-QC.yml => config/locales/fr-QC.yml +0 -1
@@ 973,7 973,6 @@ fr-QC:
      guide_link: https://fr.crowdin.com/project/mastodon
      guide_link_text: Tout le monde peut y contribuer.
    sensitive_content: Contenu sensible
    toot_layout: Agencement des messages
  application_mailer:
    notification_preferences: Modifier les préférences de courriel
    salutation: "%{name},"

M config/locales/fr.yml => config/locales/fr.yml +0 -1
@@ 973,7 973,6 @@ fr:
      guide_link: https://fr.crowdin.com/project/mastodon
      guide_link_text: Tout le monde peut y contribuer.
    sensitive_content: Contenu sensible
    toot_layout: Agencement des messages
  application_mailer:
    notification_preferences: Modifier les préférences de courriel
    salutation: "%{name},"

M config/locales/fy.yml => config/locales/fy.yml +0 -1
@@ 973,7 973,6 @@ fy:
      guide_link: https://crowdin.com/project/mastodon/fy
      guide_link_text: Elkenien kin bydrage.
    sensitive_content: Gefoelige ynhâld
    toot_layout: Lay-out fan berjochten
  application_mailer:
    notification_preferences: E-mailynstellingen wizigje
    salutation: "%{name},"

M config/locales/gd.yml => config/locales/gd.yml +0 -1
@@ 1009,7 1009,6 @@ gd:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: "’S urrainn do neach sam bith cuideachadh."
    sensitive_content: Susbaint fhrionasach
    toot_layout: Co-dhealbhachd nam postaichean
  application_mailer:
    notification_preferences: Atharraich roghainnean a’ phuist-d
    salutation: "%{name},"

M config/locales/gl.yml => config/locales/gl.yml +0 -1
@@ 973,7 973,6 @@ gl:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Todas podemos contribuír.
    sensitive_content: Contido sensible
    toot_layout: Disposición da publicación
  application_mailer:
    notification_preferences: Cambiar os axustes de email
    salutation: "%{name},"

M config/locales/he.yml => config/locales/he.yml +0 -1
@@ 1009,7 1009,6 @@ he:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: כולם יכולים לתרום.
    sensitive_content: תוכן רגיש
    toot_layout: פריסת הודעה
  application_mailer:
    notification_preferences: שינוי העדפות דוא"ל
    salutation: "%{name},"

M config/locales/hu.yml => config/locales/hu.yml +0 -1
@@ 973,7 973,6 @@ hu:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Bárki közreműködhet.
    sensitive_content: Kényes tartalom
    toot_layout: Bejegyzések elrendezése
  application_mailer:
    notification_preferences: E-mail beállítások módosítása
    salutation: "%{name}!"

M config/locales/id.yml => config/locales/id.yml +0 -1
@@ 901,7 901,6 @@ id:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Siapa saja bisa berkontribusi.
    sensitive_content: Konten sensitif
    toot_layout: Tata letak kiriman
  application_mailer:
    notification_preferences: Ubah pilihan email
    salutation: "%{name},"

M config/locales/io.yml => config/locales/io.yml +0 -1
@@ 880,7 880,6 @@ io:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Omnu povas kontributar.
    sensitive_content: Sentoza kontenajo
    toot_layout: Postostrukturo
  application_mailer:
    notification_preferences: Chanjez retpostopreferaji
    salutation: "%{name},"

M config/locales/is.yml => config/locales/is.yml +0 -1
@@ 975,7 975,6 @@ is:
      guide_link: https://crowdin.com/project/mastodon/is
      guide_link_text: Allir geta tekið þátt.
    sensitive_content: Viðkvæmt efni
    toot_layout: Framsetning færslu
  application_mailer:
    notification_preferences: Breyta kjörstillingum tölvupósts
    salutation: "%{name},"

M config/locales/it.yml => config/locales/it.yml +0 -1
@@ 975,7 975,6 @@ it:
      guide_link: https://it.crowdin.com/project/mastodon
      guide_link_text: Tutti possono contribuire.
    sensitive_content: Contenuto sensibile
    toot_layout: Layout dei toot
  application_mailer:
    notification_preferences: Cambia preferenze email
    salutation: "%{name},"

M config/locales/ja.yml => config/locales/ja.yml +0 -1
@@ 955,7 955,6 @@ ja:
      guide_link: https://ja.crowdin.com/project/mastodon
      guide_link_text: 誰でも参加することができます。
    sensitive_content: 閲覧注意コンテンツ
    toot_layout: 投稿のレイアウト
  application_mailer:
    notification_preferences: メール設定の変更
    salutation: "%{name}さん"

M config/locales/kk.yml => config/locales/kk.yml +0 -1
@@ 320,7 320,6 @@ kk:
    confirmation_dialogs: Пікірталас диалогтары
    discovery: Пікірталас
    sensitive_content: Нәзік контент
    toot_layout: Жазба формасы
  application_mailer:
    notification_preferences: Change e-mail prеferences
    settings: 'Change e-mail preferеnces: %{link}'

M config/locales/ko.yml => config/locales/ko.yml +0 -1
@@ 957,7 957,6 @@ ko:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: 누구나 기여할 수 있습니다.
    sensitive_content: 민감한 내용
    toot_layout: 게시물 레이아웃
  application_mailer:
    notification_preferences: 메일 설정 변경
    salutation: "%{name} 님,"

M config/locales/ku.yml => config/locales/ku.yml +0 -1
@@ 920,7 920,6 @@ ku:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Herkes dikare beşdar bibe.
    sensitive_content: Naveroka hestiyarî
    toot_layout: Xêzkirina şandîya
  application_mailer:
    notification_preferences: Sazkariyên e-nameyê biguherîne
    salutation: "%{name},"

M config/locales/lv.yml => config/locales/lv.yml +0 -1
@@ 979,7 979,6 @@ lv:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Ikviens var piedalīties.
    sensitive_content: Sensitīvs saturs
    toot_layout: Ziņas izskats
  application_mailer:
    notification_preferences: Mainīt e-pasta uztādījumus
    salutation: "%{name},"

M config/locales/my.yml => config/locales/my.yml +0 -1
@@ 955,7 955,6 @@ my:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: လူတိုင်းပါဝင်ကူညီနိုင်ပါတယ်။
    sensitive_content: သတိထားရသော အကြောင်းအရာ
    toot_layout: ပို့စ်အပြင်အဆင်
  application_mailer:
    notification_preferences: အီးမေးလ် သတ်မှတ်ချက်များကို ပြောင်းပါ
    salutation: "%{name}"

M config/locales/nl.yml => config/locales/nl.yml +0 -1
@@ 973,7 973,6 @@ nl:
      guide_link: https://crowdin.com/project/mastodon/nl
      guide_link_text: Iedereen kan bijdragen.
    sensitive_content: Gevoelige inhoud
    toot_layout: Lay-out van berichten
  application_mailer:
    notification_preferences: E-mailvoorkeuren wijzigen
    salutation: "%{name},"

M config/locales/nn.yml => config/locales/nn.yml +0 -1
@@ 965,7 965,6 @@ nn:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Alle kan bidra.
    sensitive_content: Ømtolig innhald
    toot_layout: Tutoppsett
  application_mailer:
    notification_preferences: Endr e-post-innstillingane
    salutation: Hei %{name},

M config/locales/no.yml => config/locales/no.yml +0 -1
@@ 914,7 914,6 @@
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Alle kan bidra.
    sensitive_content: Følsomt innhold
    toot_layout: Innleggsoppsett
  application_mailer:
    notification_preferences: Endre E-postinnstillingene
    salutation: "%{name},"

M config/locales/oc.yml => config/locales/oc.yml +0 -1
@@ 459,7 459,6 @@ oc:
      body: Mastodon es traduch per de benevòls.
      guide_link_text: Tot lo monde pòt contribuïr.
    sensitive_content: Contengut sensible
    toot_layout: Disposicion del tut
  application_mailer:
    notification_preferences: Cambiar las preferéncias de corrièl
    salutation: "%{name},"

M config/locales/pl.yml => config/locales/pl.yml +0 -1
@@ 1009,7 1009,6 @@ pl:
      guide_link: https://pl.crowdin.com/project/mastodon
      guide_link_text: Każdy może wnieść swój wkład.
    sensitive_content: Wrażliwa zawartość
    toot_layout: Wygląd wpisów
  application_mailer:
    notification_preferences: Zmień ustawienia e-maili
    salutation: "%{name},"

M config/locales/pt-BR.yml => config/locales/pt-BR.yml +0 -1
@@ 973,7 973,6 @@ pt-BR:
      guide_link: https://br.crowdin.com/project/mastodon
      guide_link_text: Todos podem contribuir.
    sensitive_content: Conteúdo sensível
    toot_layout: Formato da publicação
  application_mailer:
    notification_preferences: Alterar preferências de e-mail
    salutation: "%{name},"

M config/locales/pt-PT.yml => config/locales/pt-PT.yml +0 -1
@@ 973,7 973,6 @@ pt-PT:
      guide_link: https://pt.crowdin.com/project/mastodon/
      guide_link_text: Todos podem contribuir.
    sensitive_content: Conteúdo problemático
    toot_layout: Disposição da publicação
  application_mailer:
    notification_preferences: Alterar preferências de e-mail
    salutation: "%{name},"

M config/locales/ro.yml => config/locales/ro.yml +0 -1
@@ 408,7 408,6 @@ ro:
    localization:
      guide_link_text: Toată lumea poate contribui.
    sensitive_content: Conținut sensibil
    toot_layout: Aspect postare
  application_mailer:
    notification_preferences: Modifică preferințe e-mail
    settings: 'Modifică preferințe e-mail: %{link}'

M config/locales/ru.yml => config/locales/ru.yml +0 -1
@@ 1009,7 1009,6 @@ ru:
      guide_link: https://ru.crowdin.com/project/mastodon
      guide_link_text: Каждый может внести свой вклад.
    sensitive_content: Содержимое деликатного характера
    toot_layout: Структура постов
  application_mailer:
    notification_preferences: Настроить уведомления можно здесь
    salutation: "%{name},"

M config/locales/sc.yml => config/locales/sc.yml +0 -1
@@ 491,7 491,6 @@ sc:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Chie si siat podet contribuire.
    sensitive_content: Cuntenutu sensìbile
    toot_layout: Dispositzione de is tuts
  application_mailer:
    notification_preferences: Muda is preferèntzias de posta
    salutation: "%{name},"

M config/locales/sco.yml => config/locales/sco.yml +0 -1
@@ 913,7 913,6 @@ sco:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Awbody kin contribute.
    sensitive_content: Sensitive content
    toot_layout: Post leyoot
  application_mailer:
    notification_preferences: Chynge email preferences
    salutation: "%{name},"

M config/locales/si.yml => config/locales/si.yml +0 -1
@@ 757,7 757,6 @@ si:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: සෑම කෙනෙකුටම දායක විය හැකිය.
    sensitive_content: සංවේදී අන්තර්ගතය
    toot_layout: පෝස්ට් පිරිසැලසුම
  application_mailer:
    notification_preferences: ඊමේල් මනාප වෙනස් කරන්න
    salutation: "%{name},"

M config/locales/simple_form.an.yml => config/locales/simple_form.an.yml +0 -1
@@ 192,7 192,6 @@ an:
        setting_always_send_emails: Ninviar siempre notificacions per correu
        setting_auto_play_gif: Reproducir automaticament los GIFs animaus
        setting_boost_modal: Amostrar finestra de confirmación antes de retutar
        setting_crop_images: Retallar a 16x9 las imachens d'as publicacions no expandidas
        setting_default_language: Idioma de publicación
        setting_default_privacy: Privacidat de publicacions
        setting_default_sensitive: Marcar siempre imachens como sensibles

M config/locales/simple_form.ar.yml => config/locales/simple_form.ar.yml +0 -1
@@ 200,7 200,6 @@ ar:
        setting_always_send_emails: ارسل إشعارات البريد الإلكتروني دائماً
        setting_auto_play_gif: تشغيل تلقائي لِوَسائط جيف المتحركة
        setting_boost_modal: إظهار مربع حوار التأكيد قبل إعادة مشاركة أي منشور
        setting_crop_images: قص الصور في المنشورات غير الموسعة إلى 16x9
        setting_default_language: لغة النشر
        setting_default_privacy: خصوصية المنشور
        setting_default_sensitive: اعتبر الوسائط دائما كمحتوى حساس

M config/locales/simple_form.ast.yml => config/locales/simple_form.ast.yml +0 -1
@@ 111,7 111,6 @@ ast:
        setting_always_send_emails: Unviar siempres los avisos per corréu electrónicu
        setting_auto_play_gif: Reproducir automáticamente los GIFs
        setting_boost_modal: Amosar el diálogu de confirmación enantes de compartir un artículu
        setting_crop_images: Recortar les imáxenes de los artículos ensin espander a la proporción 16:9
        setting_default_language: Llingua de los artículos
        setting_default_privacy: Privacidá de los artículos
        setting_default_sensitive: Marcar siempres tol conteníu como sensible

M config/locales/simple_form.be.yml => config/locales/simple_form.be.yml +0 -1
@@ 200,7 200,6 @@ be:
        setting_always_send_emails: Заўжды дасылаць для апавяшчэнні эл. пошты
        setting_auto_play_gif: Аўтапрайграванне анімаваных GIF
        setting_boost_modal: Паказваць акно пацвярджэння перад пашырэннем
        setting_crop_images: У неразгорнутых допісах абразаць відарысы да 16:9
        setting_default_language: Мова допісаў
        setting_default_privacy: Прыватнасць допісаў
        setting_default_sensitive: Заўсёды пазначаць кантэнт як далікатны

M config/locales/simple_form.bg.yml => config/locales/simple_form.bg.yml +0 -1
@@ 200,7 200,6 @@ bg:
        setting_always_send_emails: Все да се пращат известия по имейла
        setting_auto_play_gif: Самопускащи се анимирани гифчета
        setting_boost_modal: Показване на прозорец за потвърждение преди подсилване
        setting_crop_images: Изрязване на образи в неразгънати публикации до 16x9
        setting_default_language: Език на публикуване
        setting_default_privacy: Поверителност на публикуване
        setting_default_sensitive: Все да се бележи мултимедията като деликатна

M config/locales/simple_form.ca.yml => config/locales/simple_form.ca.yml +0 -1
@@ 200,7 200,6 @@ ca:
        setting_always_send_emails: Envia'm sempre notificacions per correu electrònic
        setting_auto_play_gif: Reprodueix automàticament els GIF animats
        setting_boost_modal: Mostra la finestra de confirmació abans d'impulsar
        setting_crop_images: Retalla les imatges en tuts no ampliats a 16x9
        setting_default_language: Llengua dels tuts
        setting_default_privacy: Privacitat dels tuts
        setting_default_sensitive: Marcar sempre el contingut gràfic com a sensible

M config/locales/simple_form.ckb.yml => config/locales/simple_form.ckb.yml +0 -1
@@ 136,7 136,6 @@ ckb:
        setting_aggregate_reblogs: گرووپی توتەکان یەکبخە
        setting_auto_play_gif: خۆکاربەخشکردنی GIFــەکان
        setting_boost_modal: پیشاندانی دیالۆگی دووپاتکردنەوە پێش دوبارە توتاندن
        setting_crop_images: لە تووتی نەکراوە،وینەکان لە ئەندازی ۱٦×۹ ببڕە
        setting_default_language: زمانی نووسراوەکانتان
        setting_default_privacy: چوارچێوەی تایبەتێتی ئێوە
        setting_default_sensitive: هەمیشە نیشانکردنی میدیا وەک هەستیار

M config/locales/simple_form.co.yml => config/locales/simple_form.co.yml +0 -1
@@ 137,7 137,6 @@ co:
        setting_aggregate_reblogs: Gruppà e spartere indè e linee
        setting_auto_play_gif: Lettura autumatica di i GIF animati
        setting_boost_modal: Mustrà una cunfirmazione per sparte un statutu
        setting_crop_images: Riquatrà i ritratti in 16x9 indè i statuti micca selezziunati
        setting_default_language: Lingua di pubblicazione
        setting_default_privacy: Cunfidenzialità di i statuti
        setting_default_sensitive: Sempre cunsiderà media cum’è sensibili

M config/locales/simple_form.cs.yml => config/locales/simple_form.cs.yml +0 -1
@@ 195,7 195,6 @@ cs:
        setting_always_send_emails: Vždy posílat e-mailová oznámení
        setting_auto_play_gif: Automaticky přehrávat animace GIF
        setting_boost_modal: Před boostnutím zobrazovat potvrzovací okno
        setting_crop_images: Ořezávat obrázky v nerozbalených příspěvcích na 16x9
        setting_default_language: Jazyk příspěvků
        setting_default_privacy: Soukromí příspěvků
        setting_default_sensitive: Vždy označovat média jako citlivá

M config/locales/simple_form.cy.yml => config/locales/simple_form.cy.yml +0 -1
@@ 200,7 200,6 @@ cy:
        setting_always_send_emails: Anfonwch hysbysiadau e-bost bob amser
        setting_auto_play_gif: Chwarae GIFs wedi'u hanimeiddio yn awtomatig
        setting_boost_modal: Dangos deialog cadarnhau cyn rhoi hwb
        setting_crop_images: Tocio delweddau o fewn postiadau nad ydynt wedi'u hehangu i 16x9
        setting_default_language: Iaith postio
        setting_default_privacy: Preifatrwydd cyhoeddi
        setting_default_sensitive: Marcio cyfryngau fel eu bod yn sensitif bob tro

M config/locales/simple_form.da.yml => config/locales/simple_form.da.yml +0 -1
@@ 200,7 200,6 @@ da:
        setting_always_send_emails: Send altid en e-mailnotifikationer
        setting_auto_play_gif: Autoafspil animerede GIF'er
        setting_boost_modal: Vis bekræftelsesdialog inden boosting
        setting_crop_images: Beskær billeder i ikke-ekspanderede indlæg til 16x9
        setting_default_language: Sprog for indlæg
        setting_default_privacy: Fortrolighed for indlæg
        setting_default_sensitive: Markér altid medier som sensitive

M config/locales/simple_form.de.yml => config/locales/simple_form.de.yml +0 -1
@@ 200,7 200,6 @@ de:
        setting_always_send_emails: Benachrichtigungen immer senden
        setting_auto_play_gif: Animierte GIFs automatisch abspielen
        setting_boost_modal: Bestätigungsdialog beim Teilen eines Beitrags anzeigen
        setting_crop_images: Bilder in nicht ausgeklappten Beiträgen auf 16:9 zuschneiden
        setting_default_language: Beitragssprache
        setting_default_privacy: Beitragssichtbarkeit
        setting_default_sensitive: Eigene Medien immer mit einer Inhaltswarnung versehen

M config/locales/simple_form.el.yml => config/locales/simple_form.el.yml +0 -1
@@ 195,7 195,6 @@ el:
        setting_always_send_emails: Πάντα να αποστέλλονται ειδοποίησεις μέσω email
        setting_auto_play_gif: Αυτόματη αναπαραγωγή των GIF
        setting_boost_modal: Επιβεβαίωση πριν την προώθηση
        setting_crop_images: Περιορισμός των εικόνων σε μη-ανεπτυγμένα τουτ σε αναλογία 16x9
        setting_default_language: Γλώσσα δημοσιεύσεων
        setting_default_privacy: Ιδιωτικότητα δημοσιεύσεων
        setting_default_sensitive: Σημείωση όλων των πολυμέσων ως ευαίσθητου περιεχομένου

M config/locales/simple_form.en-GB.yml => config/locales/simple_form.en-GB.yml +0 -1
@@ 200,7 200,6 @@ en-GB:
        setting_always_send_emails: Always send e-mail notifications
        setting_auto_play_gif: Auto-play animated GIFs
        setting_boost_modal: Show confirmation dialogue before boosting
        setting_crop_images: Crop images in non-expanded posts to 16x9
        setting_default_language: Posting language
        setting_default_privacy: Posting privacy
        setting_default_sensitive: Always mark media as sensitive

M config/locales/simple_form.en.yml => config/locales/simple_form.en.yml +0 -1
@@ 200,7 200,6 @@ en:
        setting_always_send_emails: Always send e-mail notifications
        setting_auto_play_gif: Auto-play animated GIFs
        setting_boost_modal: Show confirmation dialog before boosting
        setting_crop_images: Crop images in non-expanded posts to 16x9
        setting_default_language: Posting language
        setting_default_privacy: Posting privacy
        setting_default_sensitive: Always mark media as sensitive

M config/locales/simple_form.eo.yml => config/locales/simple_form.eo.yml +0 -1
@@ 200,7 200,6 @@ eo:
        setting_always_send_emails: Ĉiam sendi la sciigojn per retpoŝto
        setting_auto_play_gif: Aŭtomate ekigi GIF-ojn
        setting_boost_modal: Montri konfirman fenestron antaŭ ol diskonigi mesaĝon
        setting_crop_images: Stuci bildojn en negrandigitaj mesaĝoj al 16x9
        setting_default_language: Publikada lingvo
        setting_default_privacy: Privateco de afiŝado
        setting_default_sensitive: Ĉiam marki plurmediojn kiel tiklaj

M config/locales/simple_form.es-AR.yml => config/locales/simple_form.es-AR.yml +0 -1
@@ 200,7 200,6 @@ es-AR:
        setting_always_send_emails: Siempre enviar notificaciones por correo electrónico
        setting_auto_play_gif: Reproducir automáticamente los GIFs animados
        setting_boost_modal: Mostrar diálogo de confirmación antes de adherir
        setting_crop_images: Recortar imágenes en mensajes no expandidos a 16x9
        setting_default_language: Idioma de tus mensajes
        setting_default_privacy: Privacidad de mensajes
        setting_default_sensitive: Siempre marcar medios como sensibles

M config/locales/simple_form.es-MX.yml => config/locales/simple_form.es-MX.yml +0 -1
@@ 200,7 200,6 @@ es-MX:
        setting_always_send_emails: Enviar siempre notificaciones por correo
        setting_auto_play_gif: Reproducir automáticamente los GIFs animados
        setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot
        setting_crop_images: Recortar a 16x9 las imágenes de los toots no expandidos
        setting_default_language: Idioma de publicación
        setting_default_privacy: Privacidad de publicaciones
        setting_default_sensitive: Marcar siempre imágenes como sensibles

M config/locales/simple_form.es.yml => config/locales/simple_form.es.yml +0 -1
@@ 200,7 200,6 @@ es:
        setting_always_send_emails: Enviar siempre notificaciones por correo
        setting_auto_play_gif: Reproducir automáticamente los GIFs animados
        setting_boost_modal: Mostrar ventana de confirmación antes de impulsar
        setting_crop_images: Recortar a 16x9 las imágenes de las publicaciones no expandidas
        setting_default_language: Idioma de publicación
        setting_default_privacy: Privacidad de publicaciones
        setting_default_sensitive: Marcar siempre imágenes como sensibles

M config/locales/simple_form.et.yml => config/locales/simple_form.et.yml +0 -1
@@ 200,7 200,6 @@ et:
        setting_always_send_emails: Edasta kõik teavitused meilile
        setting_auto_play_gif: Esita GIF-e automaatselt
        setting_boost_modal: Näita enne jagamist kinnitusdialoogi
        setting_crop_images: Laiendamata postitustes kärbi pildid 16:9 küljesuhtesse
        setting_default_language: Postituse keel
        setting_default_privacy: Postituse nähtavus
        setting_default_sensitive: Alati märgista meedia tundlikuks

M config/locales/simple_form.eu.yml => config/locales/simple_form.eu.yml +0 -1
@@ 195,7 195,6 @@ eu:
        setting_always_send_emails: Bidali beti eposta jakinarazpenak
        setting_auto_play_gif: Erreproduzitu GIF animatuak automatikoki
        setting_boost_modal: Erakutsi baieztapen elkarrizketa-koadroa bultzada eman aurretik
        setting_crop_images: Moztu irudiak hedatu gabeko tootetan 16x9 proportzioan
        setting_default_language: Argitalpenen hizkuntza
        setting_default_privacy: Mezuen pribatutasuna
        setting_default_sensitive: Beti markatu edukiak hunkigarri gisa

M config/locales/simple_form.fa.yml => config/locales/simple_form.fa.yml +0 -1
@@ 169,7 169,6 @@ fa:
        setting_always_send_emails: فرستادن همیشگی آگاهی‌های رایانامه‌ای
        setting_auto_play_gif: پخش خودکار تصویرهای متحرک
        setting_boost_modal: نمایش پیغام تأیید پیش از تقویت کردن
        setting_crop_images: در فرسته‌های ناگسترده، تصویرها را به ابعاد ‎۱۶×۹ کوچک کن
        setting_default_language: زبان نوشته‌های شما
        setting_default_privacy: حریم خصوصی نوشته‌ها
        setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن

M config/locales/simple_form.fi.yml => config/locales/simple_form.fi.yml +0 -1
@@ 200,7 200,6 @@ fi:
        setting_always_send_emails: Lähetä aina sähköposti-ilmoituksia
        setting_auto_play_gif: Toista GIF-animaatiot automaattisesti
        setting_boost_modal: Kysy vahvistus ennen tehostusta
        setting_crop_images: Rajaa kuvat avaamattomissa tuuttauksissa 16:9 kuvasuhteeseen
        setting_default_language: Julkaisujen kieli
        setting_default_privacy: Viestin näkyvyys
        setting_default_sensitive: Merkitse media aina arkaluontoiseksi

M config/locales/simple_form.fo.yml => config/locales/simple_form.fo.yml +0 -1
@@ 200,7 200,6 @@ fo:
        setting_always_send_emails: Send altíð fráboðanir við telduposti
        setting_auto_play_gif: Spæl teknimyndagjørdar GIFar sjálvvirkandi
        setting_boost_modal: Vís váttanarmynd, áðrenn tú stimbrar postar
        setting_crop_images: Sker myndir til lutfallið 16x9 í postum, sum ikki eru víðkaðir
        setting_default_language: Mál, sum verður brúkt til postar
        setting_default_privacy: Hvussu privatir eru postar?
        setting_default_sensitive: Merk altíð miðlafílur sum viðkvæmar

M config/locales/simple_form.fr-QC.yml => config/locales/simple_form.fr-QC.yml +0 -1
@@ 200,7 200,6 @@ fr-QC:
        setting_always_send_emails: Toujours envoyer les notifications par courriel
        setting_auto_play_gif: Lire automatiquement les GIFs animés
        setting_boost_modal: Demander confirmation avant de partager un message
        setting_crop_images: Recadrer en 16x9 les images des messages qui ne sont pas ouverts en vue détaillée
        setting_default_language: Langue de publication
        setting_default_privacy: Confidentialité des messages
        setting_default_sensitive: Toujours marquer les médias comme sensibles

M config/locales/simple_form.fr.yml => config/locales/simple_form.fr.yml +0 -1
@@ 200,7 200,6 @@ fr:
        setting_always_send_emails: Toujours envoyer les notifications par courriel
        setting_auto_play_gif: Lire automatiquement les GIFs animés
        setting_boost_modal: Demander confirmation avant de partager un message
        setting_crop_images: Recadrer en 16x9 les images des messages qui ne sont pas ouverts en vue détaillée
        setting_default_language: Langue de publication
        setting_default_privacy: Confidentialité des messages
        setting_default_sensitive: Toujours marquer les médias comme sensibles

M config/locales/simple_form.fy.yml => config/locales/simple_form.fy.yml +0 -1
@@ 200,7 200,6 @@ fy:
        setting_always_send_emails: Altyd e-mailmeldingen ferstjoere
        setting_auto_play_gif: Spylje animearre GIF’s automatysk ôf
        setting_boost_modal: Freegje foar it boosten fan in berjocht in befêstiging
        setting_crop_images: Ofbyldingen bysnije oant 16x9 yn berjochten op tiidlinen
        setting_default_language: Taal fan jo berjochten
        setting_default_privacy: Sichtberheid fan nije berjochten
        setting_default_sensitive: Media altyd as gefoelich markearje

M config/locales/simple_form.gd.yml => config/locales/simple_form.gd.yml +0 -1
@@ 200,7 200,6 @@ gd:
        setting_always_send_emails: Cuir brathan puist-d an-còmhnaidh
        setting_auto_play_gif: Cluich GIFs beòthaichte gu fèin-obrachail
        setting_boost_modal: Seall còmhradh dearbhaidh mus dèan thu brosnachadh
        setting_crop_images: Beàrr na dealbhan sna postaichean gun leudachadh air 16x9
        setting_default_language: Cànan postaidh
        setting_default_privacy: Prìobhaideachd postaidh
        setting_default_sensitive: Cuir comharra ri meadhanan an-còmhnaidh gu bheil iad frionasach

M config/locales/simple_form.gl.yml => config/locales/simple_form.gl.yml +0 -1
@@ 200,7 200,6 @@ gl:
        setting_always_send_emails: Enviar sempre notificacións por correo electrónico
        setting_auto_play_gif: Reprodución automática de GIFs animados
        setting_boost_modal: Solicitar confirmación antes de promover
        setting_crop_images: Recortar imaxes a 16x9 en publicacións non despregadas
        setting_default_language: Idioma de publicación
        setting_default_privacy: Privacidade da publicación
        setting_default_sensitive: Marcar sempre multimedia como sensible

M config/locales/simple_form.he.yml => config/locales/simple_form.he.yml +0 -1
@@ 200,7 200,6 @@ he:
        setting_always_send_emails: תמיד שלח התראות לדוא"ל
        setting_auto_play_gif: ניגון אוטומטי של גיפים
        setting_boost_modal: הצגת דיאלוג אישור לפני הדהוד
        setting_crop_images: קטום תמונות בהודעות לא מורחבות ל 16 על 9
        setting_default_language: שפת ברירת מחדל להודעה
        setting_default_privacy: פרטיות ההודעות
        setting_default_sensitive: תמיד לתת סימון "רגיש" למדיה

M config/locales/simple_form.hu.yml => config/locales/simple_form.hu.yml +0 -1
@@ 200,7 200,6 @@ hu:
        setting_always_send_emails: E-mail értesítések küldése mindig
        setting_auto_play_gif: GIF-ek automatikus lejátszása
        setting_boost_modal: Megerősítés kérése megtolás előtt
        setting_crop_images: Képek 16x9-re vágása nem kinyitott bejegyzéseknél
        setting_default_language: Bejegyzések nyelve
        setting_default_privacy: Bejegyzések láthatósága
        setting_default_sensitive: Minden médiafájl megjelölése kényesként

M config/locales/simple_form.hy.yml => config/locales/simple_form.hy.yml +0 -1
@@ 135,7 135,6 @@ hy:
        setting_aggregate_reblogs: Տարծածները խմբաւորել հոսքում
        setting_auto_play_gif: Աւտոմատ մեկնարկել GIFs անիմացիաները
        setting_boost_modal: Ցուցադրել հաստատման պատուհանը տարածելուց առաջ
        setting_crop_images: Ցոյց տալ գրառման նկարը 16x9 համամասնութեամբ
        setting_default_language: Հրապարակման լեզու
        setting_default_privacy: Հրապարակման գաղտնիութիւն
        setting_default_sensitive: Միշտ նշել մեդիան որպէս դիւրազգաց

M config/locales/simple_form.id.yml => config/locales/simple_form.id.yml +0 -1
@@ 188,7 188,6 @@ id:
        setting_always_send_emails: Selalu kirim notifikasi email
        setting_auto_play_gif: Mainkan otomatis animasi GIF
        setting_boost_modal: Tampilkan dialog konfirmasi dialog sebelum boost
        setting_crop_images: Potong gambar ke 16x9 pada toot yang tidak dibentangkan
        setting_default_language: Bahasa posting
        setting_default_privacy: Privasi postingan
        setting_default_sensitive: Selalu tandai media sebagai sensitif

M config/locales/simple_form.io.yml => config/locales/simple_form.io.yml +0 -1
@@ 186,7 186,6 @@ io:
        setting_always_send_emails: Sempre sendez retpostoavizi
        setting_auto_play_gif: Automate pleez animigita GIFi
        setting_boost_modal: Montrez konfirmdialogo ante bustar
        setting_crop_images: Ektranchez imaji en neexpansigita posti a 16x9
        setting_default_language: Postolinguo
        setting_default_privacy: Videbleso di la mesaji
        setting_default_sensitive: Sempre markizez medii quale sentoza

M config/locales/simple_form.is.yml => config/locales/simple_form.is.yml +0 -1
@@ 200,7 200,6 @@ is:
        setting_always_send_emails: Alltaf senda tilkynningar í tölvupósti
        setting_auto_play_gif: Spila sjálfkrafa GIF-hreyfimyndir
        setting_boost_modal: Sýna staðfestingarglugga fyrir endurbirtingu
        setting_crop_images: Utansníða myndir í ekki-útfelldum færslum í 16x9
        setting_default_language: Tungumál sem skrifað er á
        setting_default_privacy: Gagnaleynd færslna
        setting_default_sensitive: Alltaf merkja myndefni sem viðkvæmt

M config/locales/simple_form.it.yml => config/locales/simple_form.it.yml +0 -1
@@ 200,7 200,6 @@ it:
        setting_always_send_emails: Manda sempre notifiche via email
        setting_auto_play_gif: Riproduci automaticamente le GIF animate
        setting_boost_modal: Mostra dialogo di conferma prima del boost
        setting_crop_images: Ritaglia immagini in post non espansi a 16x9
        setting_default_language: Lingua dei post
        setting_default_privacy: Privacy dei post
        setting_default_sensitive: Segna sempre i media come sensibili

M config/locales/simple_form.ja.yml => config/locales/simple_form.ja.yml +0 -1
@@ 200,7 200,6 @@ ja:
        setting_always_send_emails: 常にメール通知を送信する
        setting_auto_play_gif: アニメーションGIFを自動再生する
        setting_boost_modal: ブーストする前に確認ダイアログを表示する
        setting_crop_images: 投稿の詳細以外では画像を16:9に切り抜く
        setting_default_language: 投稿する言語
        setting_default_privacy: 投稿の公開範囲
        setting_default_sensitive: メディアを常に閲覧注意としてマークする

M config/locales/simple_form.kk.yml => config/locales/simple_form.kk.yml +0 -1
@@ 46,7 46,6 @@ kk:
        setting_advanced_layout: Кеңейтілген веб-интерфейс қосу
        setting_auto_play_gif: GIF анимацияларды бірден қосу
        setting_boost_modal: Бөлісу алдында растау диалогын көрсету
        setting_crop_images: Кеңейтілмеген жазбаларда суреттерді 16х9 көлеміне кес
        setting_default_language: Жазба тілі
        setting_default_privacy: Жазба құпиялылығы
        setting_default_sensitive: Медиаларды әрдайым нәзік ретінде белгілеу

M config/locales/simple_form.ko.yml => config/locales/simple_form.ko.yml +0 -1
@@ 200,7 200,6 @@ ko:
        setting_always_send_emails: 항상 이메일 알림 보내기
        setting_auto_play_gif: 애니메이션 GIF를 자동 재생
        setting_boost_modal: 부스트 전 확인 창을 표시
        setting_crop_images: 확장되지 않은 게시물의 이미지를 16x9로 자르기
        setting_default_language: 게시물 언어
        setting_default_privacy: 게시물 프라이버시
        setting_default_sensitive: 미디어를 언제나 민감한 콘텐츠로 설정

M config/locales/simple_form.ku.yml => config/locales/simple_form.ku.yml +0 -1
@@ 190,7 190,6 @@ ku:
        setting_always_send_emails: Her dem agahdariya e-nameyê bişîne
        setting_auto_play_gif: GIF ên livok bi xweber bilîzine
        setting_boost_modal: Gotûbêja pejirandinê nîşan bide berî ku şandî werê bilindkirin
        setting_crop_images: Wêneyên di nav şandiyên ku nehatine berfireh kirin wek 16×9 jê bike
        setting_default_language: Zimanê weşanê
        setting_default_privacy: Ewlehiya weşanê
        setting_default_sensitive: Her dem medya wek hestyar bide nîşan

M config/locales/simple_form.lv.yml => config/locales/simple_form.lv.yml +0 -1
@@ 195,7 195,6 @@ lv:
        setting_always_send_emails: Vienmēr sūtīt e-pasta paziņojumus
        setting_auto_play_gif: Automātiski atskaņot animētos GIF
        setting_boost_modal: Rādīt apstiprinājuma dialogu pirms izcelšanas
        setting_crop_images: Apgrieziet attēlus neizvērstajās ziņās līdz 16x9
        setting_default_language: Publicēšanas valoda
        setting_default_privacy: Publicēšanas privātums
        setting_default_sensitive: Atļaut atzīmēt multividi kā sensitīvu

M config/locales/simple_form.my.yml => config/locales/simple_form.my.yml +0 -1
@@ 200,7 200,6 @@ my:
        setting_always_send_emails: အီးမေးလ်သတိပေးချက်များကို အမြဲပို့ပါ
        setting_auto_play_gif: ကာတွန်း GIF များကို အလိုအလျောက်ဖွင့်ပါ
        setting_boost_modal: Boost မလုပ်မီ အတည်ပြုချက်ပြပါ
        setting_crop_images: အကျယ်မချဲ့ထားသော စာစုများတွင် ပုံများကို ၁၆း၉ အရွယ် ဖြတ်တောက်ပါ။
        setting_default_language: ပို့စ်တင်မည့်ဘာသာစကား
        setting_default_privacy: ပို့စ်ကို ဘယ်သူမြင်နိုင်မလဲ
        setting_default_sensitive: သတိထားရသောမီဒီယာအဖြစ် အမြဲအမှတ်အသားပြုပါ

M config/locales/simple_form.nl.yml => config/locales/simple_form.nl.yml +0 -1
@@ 200,7 200,6 @@ nl:
        setting_always_send_emails: Altijd e-mailmeldingen verzenden
        setting_auto_play_gif: Geanimeerde GIF's automatisch afspelen
        setting_boost_modal: Vraag voor het boosten van een bericht een bevestiging
        setting_crop_images: Afbeeldingen in tijdlijnberichten bijsnijden tot 16x9
        setting_default_language: Taal van jouw berichten
        setting_default_privacy: Zichtbaarheid van nieuwe berichten
        setting_default_sensitive: Media altijd als gevoelig markeren

M config/locales/simple_form.nn.yml => config/locales/simple_form.nn.yml +0 -1
@@ 195,7 195,6 @@ nn:
        setting_always_send_emails: Alltid send epostvarsel
        setting_auto_play_gif: Spel av animerte GIF-ar automatisk
        setting_boost_modal: Vis stadfesting før framheving
        setting_crop_images: Skjer bilete i ikkje-utvida tut til 16x9
        setting_default_language: Språk på innlegg
        setting_default_privacy: Privatliv
        setting_default_sensitive: Merk alltid media som nærtakande

M config/locales/simple_form.no.yml => config/locales/simple_form.no.yml +0 -1
@@ 183,7 183,6 @@
        setting_always_send_emails: Alltid send e-postvarslinger
        setting_auto_play_gif: Autoavspill animert GIF-filer
        setting_boost_modal: Vis bekreftelse før fremheving
        setting_crop_images: Klipp bilder i ikke-utvidede innlegg til 16:9
        setting_default_language: Innleggsspråk
        setting_default_privacy: Postintegritet
        setting_default_sensitive: Merk alltid media som følsomt

M config/locales/simple_form.oc.yml => config/locales/simple_form.oc.yml +0 -1
@@ 139,7 139,6 @@ oc:
        setting_always_send_emails: Totjorn enviar los corrièls de notificacion
        setting_auto_play_gif: Lectura automatica dels GIFS animats
        setting_boost_modal: Mostrar una fenèstra de confirmacion abans de partejar un estatut
        setting_crop_images: Retalhar los imatges dins los tuts pas desplegats a 16x9
        setting_default_language: Lenga de publicacion
        setting_default_privacy: Confidencialitat dels tuts
        setting_default_sensitive: Totjorn marcar los mèdias coma sensibles

M config/locales/simple_form.pl.yml => config/locales/simple_form.pl.yml +0 -1
@@ 200,7 200,6 @@ pl:
        setting_always_send_emails: Zawsze wysyłaj powiadomienia e-mail
        setting_auto_play_gif: Automatycznie odtwarzaj animowane GIFy
        setting_boost_modal: Pytaj o potwierdzenie przed podbiciem
        setting_crop_images: Przycinaj obrazki w nierozwiniętych wpisach do 16x9
        setting_default_language: Język wpisów
        setting_default_privacy: Widoczność wpisów
        setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą

M config/locales/simple_form.pt-BR.yml => config/locales/simple_form.pt-BR.yml +0 -1
@@ 200,7 200,6 @@ pt-BR:
        setting_always_send_emails: Sempre enviar notificações por e-mail
        setting_auto_play_gif: Reproduzir GIFs automaticamente
        setting_boost_modal: Solicitar confirmação antes de dar boost
        setting_crop_images: Cortar imagens no formato 16x9 em publicações não expandidas
        setting_default_language: Idioma dos toots
        setting_default_privacy: Privacidade dos toots
        setting_default_sensitive: Sempre marcar mídia como sensível

M config/locales/simple_form.pt-PT.yml => config/locales/simple_form.pt-PT.yml +0 -1
@@ 200,7 200,6 @@ pt-PT:
        setting_always_send_emails: Enviar sempre notificações de email
        setting_auto_play_gif: Reproduzir GIF automaticamente
        setting_boost_modal: Solicitar confirmação antes de partilhar uma publicação
        setting_crop_images: Recortar imagens para o formato 16x9 nas publicações não expandidas
        setting_default_language: Língua de publicação
        setting_default_privacy: Privacidade da publicação
        setting_default_sensitive: Marcar sempre os media como problemáticos

M config/locales/simple_form.ro.yml => config/locales/simple_form.ro.yml +0 -1
@@ 124,7 124,6 @@ ro:
        setting_aggregate_reblogs: Grupează impulsurile în fluxuri
        setting_auto_play_gif: Redă automat animațiile GIF
        setting_boost_modal: Arată dialogul de confirmare înainte de a impulsiona
        setting_crop_images: Decupează imaginile în postările non-extinse la 16x9
        setting_default_language: În ce limbă postezi
        setting_default_privacy: Cine vede postările tale
        setting_default_sensitive: Întotdeauna marchează conținutul media ca fiind sensibil

M config/locales/simple_form.ru.yml => config/locales/simple_form.ru.yml +0 -1
@@ 195,7 195,6 @@ ru:
        setting_always_send_emails: Всегда отправлять уведомления по электронной почте
        setting_auto_play_gif: Автоматически проигрывать GIF анимации
        setting_boost_modal: Всегда спрашивать перед продвижением
        setting_crop_images: Кадрировать изображения в нераскрытых постах до 16:9
        setting_default_language: Язык публикуемых постов
        setting_default_privacy: Видимость постов
        setting_default_sensitive: Всегда отмечать медиафайлы как «деликатного характера»

M config/locales/simple_form.sc.yml => config/locales/simple_form.sc.yml +0 -1
@@ 141,7 141,6 @@ sc:
        setting_aggregate_reblogs: Agrupa is cumpartziduras in is lìnias de tempus
        setting_auto_play_gif: Riprodui is GIF animadas in automàticu
        setting_boost_modal: Ammustra unu diàlogu de cunfirma in antis de cumpartzire
        setting_crop_images: Retàllia a 16x9 is immàgines de is tuts no ismanniados
        setting_default_language: Idioma de publicatzione
        setting_default_privacy: Riservadesa de is publicatziones
        setting_default_sensitive: Marca semper is elementos multimediales comente sensìbiles

M config/locales/simple_form.sco.yml => config/locales/simple_form.sco.yml +0 -1
@@ 188,7 188,6 @@ sco:
        setting_always_send_emails: Aye sen email notifications
        setting_auto_play_gif: Auto-pley animatit GIFs
        setting_boost_modal: Shaw confirmation dialog afore heezin
        setting_crop_images: Crap images in non-expandit posts tae 16x9
        setting_default_language: Postin leid
        setting_default_privacy: Postin privacy
        setting_default_sensitive: Aye mairk media as sensitive

M config/locales/simple_form.si.yml => config/locales/simple_form.si.yml +0 -1
@@ 157,7 157,6 @@ si:
        setting_always_send_emails: සෑම විටම විද්‍යුත් තැපැල් දැනුම්දීම් යවන්න
        setting_auto_play_gif: සජීවිකරණ GIF ස්වයංක්‍රීයව ධාවනය කරන්න
        setting_boost_modal: වැඩි කිරීමට පෙර තහවුරු කිරීමේ සංවාදය පෙන්වන්න
        setting_crop_images: ප්‍රසාරණය නොකළ පළ කිරීම් වල පින්තූර 16x9 දක්වා කප්පාදු කරන්න
        setting_default_language: පළ කිරීමේ භාෂාව
        setting_default_privacy: පුද්ගලිකත්වය පළ කිරීම
        setting_default_sensitive: සෑම විටම මාධ්‍ය සංවේදී ලෙස සලකුණු කරන්න

M config/locales/simple_form.sk.yml => config/locales/simple_form.sk.yml +0 -1
@@ 117,7 117,6 @@ sk:
        setting_aggregate_reblogs: Zoskupuj vyzdvihnutia v časovej osi
        setting_auto_play_gif: Automaticky prehrávaj animované GIFy
        setting_boost_modal: Zobrazuj potvrdzovacie okno pred povýšením
        setting_crop_images: Orež obrázky v nerozbalených príspevkoch na 16x9
        setting_default_language: Píšeš v jazyku
        setting_default_privacy: Súkromie príspevkov
        setting_default_sensitive: Označ všetky mediálne súbory ako chúlostivé

M config/locales/simple_form.sl.yml => config/locales/simple_form.sl.yml +0 -1
@@ 198,7 198,6 @@ sl:
        setting_always_send_emails: Vedno pošlji e-obvestila
        setting_auto_play_gif: Samodejno predvajanje animiranih GIF-ov
        setting_boost_modal: Pred izpostavljanjem pokaži potrditveno okno
        setting_crop_images: Obreži slike v nerazširjenih objavah v razmerju 16:9
        setting_default_language: Jezik objavljanja
        setting_default_privacy: Zasebnost objave
        setting_default_sensitive: Vedno označi medije kot občutljive

M config/locales/simple_form.sq.yml => config/locales/simple_form.sq.yml +0 -1
@@ 200,7 200,6 @@ sq:
        setting_always_send_emails: Dërgo përherë njoftime me email
        setting_auto_play_gif: Vetëluaji GIF-et e animuar
        setting_boost_modal: Shfaq dialog ripohimi përpara përforcimi
        setting_crop_images: Në mesazhe jo të zgjerueshëm, qethi figurat në 16x9
        setting_default_language: Gjuhë postimi
        setting_default_privacy: Privatësi postimi
        setting_default_sensitive: Mediave vëru përherë shenjë si rezervat

M config/locales/simple_form.sr-Latn.yml => config/locales/simple_form.sr-Latn.yml +0 -1
@@ 200,7 200,6 @@ sr-Latn:
        setting_always_send_emails: Uvek šalji obaveštenja e-poštom
        setting_auto_play_gif: Automatski reprodukuj animirane GIF-ove
        setting_boost_modal: Prikaži dijalog za potvrdu pre davanja podrške
        setting_crop_images: Izreži slike u neproširenim objavama na 16x9
        setting_default_language: Jezik objavljivanja
        setting_default_privacy: Privatnost objava
        setting_default_sensitive: Uvek označi multimediju kao osetljivu

M config/locales/simple_form.sr.yml => config/locales/simple_form.sr.yml +0 -1
@@ 200,7 200,6 @@ sr:
        setting_always_send_emails: Увек шаљи обавештења е-поштом
        setting_auto_play_gif: Аутоматски репродукуј анимиране GIF-ове
        setting_boost_modal: Прикажи дијалог за потврду пре давања подршке
        setting_crop_images: Изрежи слике у непроширеним објавама на 16x9
        setting_default_language: Језик објављивања
        setting_default_privacy: Приватност објава
        setting_default_sensitive: Увек означи мултимедију као осетљиву

M config/locales/simple_form.sv.yml => config/locales/simple_form.sv.yml +0 -1
@@ 195,7 195,6 @@ sv:
        setting_always_send_emails: Skicka alltid e-postnotiser
        setting_auto_play_gif: Spela upp GIF:ar automatiskt
        setting_boost_modal: Visa bekräftelsedialog innan boostning
        setting_crop_images: Beskär bilder i icke-utökade inlägg till 16x9
        setting_default_language: Inläggsspråk
        setting_default_privacy: Inläggsintegritet
        setting_default_sensitive: Markera alltid media som känsligt

M config/locales/simple_form.th.yml => config/locales/simple_form.th.yml +0 -1
@@ 200,7 200,6 @@ th:
        setting_always_send_emails: ส่งการแจ้งเตือนอีเมลเสมอ
        setting_auto_play_gif: เล่น GIF แบบเคลื่อนไหวโดยอัตโนมัติ
        setting_boost_modal: แสดงกล่องโต้ตอบการยืนยันก่อนดัน
        setting_crop_images: ครอบตัดภาพในโพสต์ที่ไม่ได้ขยายเป็น 16x9
        setting_default_language: ภาษาของการโพสต์
        setting_default_privacy: ความเป็นส่วนตัวของการโพสต์
        setting_default_sensitive: ทำเครื่องหมายสื่อว่าละเอียดอ่อนเสมอ

M config/locales/simple_form.tr.yml => config/locales/simple_form.tr.yml +0 -1
@@ 200,7 200,6 @@ tr:
        setting_always_send_emails: Her zaman e-posta bildirimleri gönder
        setting_auto_play_gif: Hareketli GIF'leri otomatik oynat
        setting_boost_modal: Boostlamadan önce onay iletişim kutusu göster
        setting_crop_images: Genişletilmemiş gönderilerdeki resimleri 16x9 olarak kırp
        setting_default_language: Gönderi dili
        setting_default_privacy: Gönderi gizliliği
        setting_default_sensitive: Medyayı her zaman hassas olarak işaretle

M config/locales/simple_form.uk.yml => config/locales/simple_form.uk.yml +0 -1
@@ 203,7 203,6 @@ uk:
        setting_always_send_emails: Завжди надсилати сповіщення електронною поштою
        setting_auto_play_gif: Автоматично відтворювати анімовані GIF
        setting_boost_modal: Показувати діалог підтвердження під час поширення
        setting_crop_images: Обрізати зображення в нерозгорнутих дописах до 16x9
        setting_default_language: Мова дописів
        setting_default_privacy: Видимість дописів
        setting_default_sensitive: Позначати медіа делікатними

M config/locales/simple_form.vi.yml => config/locales/simple_form.vi.yml +0 -1
@@ 200,7 200,6 @@ vi:
        setting_always_send_emails: Luôn gửi email thông báo
        setting_auto_play_gif: Tự động phát ảnh GIF
        setting_boost_modal: Yêu cầu xác nhận trước khi đăng lại tút
        setting_crop_images: Hiển thị ảnh theo tỉ lệ 16x9
        setting_default_language: Ngôn ngữ đăng
        setting_default_privacy: Kiểu đăng
        setting_default_sensitive: Ảnh/video là nội dung nhạy cảm

M config/locales/simple_form.zh-CN.yml => config/locales/simple_form.zh-CN.yml +0 -1
@@ 200,7 200,6 @@ zh-CN:
        setting_always_send_emails: 总是发送电子邮件通知
        setting_auto_play_gif: 自动播放 GIF 动画
        setting_boost_modal: 在转嘟前询问我
        setting_crop_images: 把未展开嘟文中的图片裁剪到 16x9
        setting_default_language: 发布语言
        setting_default_privacy: 嘟文默认可见范围
        setting_default_sensitive: 始终标记媒体为敏感内容

M config/locales/simple_form.zh-HK.yml => config/locales/simple_form.zh-HK.yml +0 -1
@@ 194,7 194,6 @@ zh-HK:
        setting_always_send_emails: 總是傳送電郵通知
        setting_auto_play_gif: 自動播放 GIF
        setting_boost_modal: 在轉推前詢問我
        setting_crop_images: 將未展開文章中的圖片裁剪到 16x9
        setting_default_language: 文章語言
        setting_default_privacy: 文章預設為
        setting_default_sensitive: 預設我的內容為敏感內容

M config/locales/simple_form.zh-TW.yml => config/locales/simple_form.zh-TW.yml +0 -1
@@ 200,7 200,6 @@ zh-TW:
        setting_always_send_emails: 總是發送電子郵件通知
        setting_auto_play_gif: 自動播放 GIF 動畫
        setting_boost_modal: 轉嘟前先詢問我
        setting_crop_images: 將未展開嘟文中的圖片裁剪至 16x9
        setting_default_language: 嘟文語言
        setting_default_privacy: 嘟文可見範圍
        setting_default_sensitive: 總是將媒體標記為敏感內容

M config/locales/sk.yml => config/locales/sk.yml +0 -1
@@ 684,7 684,6 @@ sk:
      body: Mastodon je prekladaný dobrovoľníkmi.
      guide_link_text: Prispievať môže každý.
    sensitive_content: Chúlostivý obsah
    toot_layout: Rozloženie príspevkov
  application_mailer:
    notification_preferences: Zmeň emailové voľby
    settings: 'Zmeň emailové voľby: %{link}'

M config/locales/sl.yml => config/locales/sl.yml +0 -1
@@ 1002,7 1002,6 @@ sl:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Vsakdo lahko prispeva.
    sensitive_content: Občutljiva vsebina
    toot_layout: Postavitev objave
  application_mailer:
    notification_preferences: Spremenite e-poštne nastavitve
    salutation: "%{name},"

M config/locales/sq.yml => config/locales/sq.yml +0 -1
@@ 973,7 973,6 @@ sq:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Çdokush mund të kontribuojë.
    sensitive_content: Lëndë rezervat
    toot_layout: Skemë mesazhesh
  application_mailer:
    notification_preferences: Ndryshoni parapëlqime email-i
    salutation: "%{name},"

M config/locales/sr-Latn.yml => config/locales/sr-Latn.yml +0 -1
@@ 991,7 991,6 @@ sr-Latn:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Svako može doprineti.
    sensitive_content: Osetljiv sadržaj
    toot_layout: Raspored objava
  application_mailer:
    notification_preferences: Promeni preference E-pošte
    salutation: Poštovani %{name},

M config/locales/sr.yml => config/locales/sr.yml +0 -1
@@ 991,7 991,6 @@ sr:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Свако може допринети.
    sensitive_content: Осетљив садржај
    toot_layout: Распоред објава
  application_mailer:
    notification_preferences: Промени преференце Е-поште
    salutation: Поштовани %{name},

M config/locales/sv.yml => config/locales/sv.yml +0 -1
@@ 963,7 963,6 @@ sv:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Alla kan bidra.
    sensitive_content: Känsligt innehåll
    toot_layout: Inläggslayout
  application_mailer:
    notification_preferences: Ändra e-postinställningar
    salutation: "%{name},"

M config/locales/th.yml => config/locales/th.yml +0 -1
@@ 955,7 955,6 @@ th:
      guide_link: https://crowdin.com/project/mastodon/th
      guide_link_text: ทุกคนสามารถมีส่วนร่วม
    sensitive_content: เนื้อหาที่ละเอียดอ่อน
    toot_layout: เค้าโครงโพสต์
  application_mailer:
    notification_preferences: เปลี่ยนการกำหนดลักษณะอีเมล
    salutation: "%{name},"

M config/locales/tr.yml => config/locales/tr.yml +0 -1
@@ 973,7 973,6 @@ tr:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Herkes katkıda bulunabilir.
    sensitive_content: Hassas içerik
    toot_layout: Gönderi düzeni
  application_mailer:
    notification_preferences: E-posta tercihlerini değiştir
    salutation: "%{name},"

M config/locales/uk.yml => config/locales/uk.yml +0 -1
@@ 1009,7 1009,6 @@ uk:
      guide_link: https://uk.crowdin.com/project/mastodon
      guide_link_text: Кожен може взяти участь.
    sensitive_content: Дражливий зміст
    toot_layout: Зовнішній вигляд дописів
  application_mailer:
    notification_preferences: Змінити налаштування електронної пошти
    salutation: "%{name},"

M config/locales/vi.yml => config/locales/vi.yml +0 -1
@@ 955,7 955,6 @@ vi:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: Ai cũng có thể đóng góp.
    sensitive_content: Nội dung nhạy cảm
    toot_layout: Tút
  application_mailer:
    notification_preferences: Thay đổi tùy chọn email
    salutation: "%{name},"

M config/locales/zh-CN.yml => config/locales/zh-CN.yml +0 -1
@@ 955,7 955,6 @@ zh-CN:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: 每个人都可以参与翻译。
    sensitive_content: 敏感内容
    toot_layout: 嘟文布局
  application_mailer:
    notification_preferences: 更改电子邮件首选项
    salutation: "%{name}:"

M config/locales/zh-HK.yml => config/locales/zh-HK.yml +0 -1
@@ 941,7 941,6 @@ zh-HK:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: 每個人都能貢獻。
    sensitive_content: 敏感內容
    toot_layout: 發文介面
  application_mailer:
    notification_preferences: 更改電郵設定
    salutation: "%{name}:"

M config/locales/zh-TW.yml => config/locales/zh-TW.yml +0 -1
@@ 959,7 959,6 @@ zh-TW:
      guide_link: https://crowdin.com/project/mastodon
      guide_link_text: 每個人都能貢獻。
    sensitive_content: 敏感內容
    toot_layout: 嘟文排版
  application_mailer:
    notification_preferences: 變更電子郵件設定
    salutation: "%{name}、"

A db/migrate/20230724160715_add_published_at_to_preview_cards.rb => db/migrate/20230724160715_add_published_at_to_preview_cards.rb +7 -0
@@ 0,0 1,7 @@
# frozen_string_literal: true

class AddPublishedAtToPreviewCards < ActiveRecord::Migration[7.0]
  def change
    add_column :preview_cards, :published_at, :datetime
  end
end

M db/schema.rb => db/schema.rb +216 -216
@@ 10,8 10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

ActiveRecord::Schema[7.0].define(version: 2023_07_24_160715) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"



@@ 19,8 18,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id"
    t.string "acct", default: "", null: false
    t.string "uri", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id"], name: "index_account_aliases_on_account_id"
  end



@@ 38,15 37,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "account_deletion_requests", force: :cascade do |t|
    t.bigint "account_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id"], name: "index_account_deletion_requests_on_account_id"
  end

  create_table "account_domain_blocks", force: :cascade do |t|
    t.string "domain"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id"
    t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
  end


@@ 56,8 55,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "acct", default: "", null: false
    t.bigint "followers_count", default: 0, null: false
    t.bigint "target_account_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id"], name: "index_account_migrations_on_account_id"
    t.index ["target_account_id"], name: "index_account_migrations_on_target_account_id", where: "(target_account_id IS NOT NULL)"
  end


@@ 66,8 65,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.text "content", null: false
    t.bigint "account_id", null: false
    t.bigint "target_account_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id"], name: "index_account_moderation_notes_on_account_id"
    t.index ["target_account_id"], name: "index_account_moderation_notes_on_target_account_id"
  end


@@ 76,8 75,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id"
    t.bigint "target_account_id"
    t.text "comment", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id", "target_account_id"], name: "index_account_notes_on_account_id_and_target_account_id", unique: true
    t.index ["target_account_id"], name: "index_account_notes_on_target_account_id"
  end


@@ 85,8 84,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "account_pins", force: :cascade do |t|
    t.bigint "account_id"
    t.bigint "target_account_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id", "target_account_id"], name: "index_account_pins_on_account_id_and_target_account_id", unique: true
    t.index ["target_account_id"], name: "index_account_pins_on_target_account_id"
  end


@@ 96,9 95,9 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "statuses_count", default: 0, null: false
    t.bigint "following_count", default: 0, null: false
    t.bigint "followers_count", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "last_status_at"
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.datetime "last_status_at", precision: nil
    t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true
  end



@@ 114,15 113,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.boolean "keep_self_bookmark", default: true, null: false
    t.integer "min_favs"
    t.integer "min_reblogs"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["account_id"], name: "index_account_statuses_cleanup_policies_on_account_id"
  end

  create_table "account_warning_presets", force: :cascade do |t|
    t.text "text", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "title", default: "", null: false
  end



@@ 131,11 130,11 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "target_account_id"
    t.integer "action", default: 0, null: false
    t.text "text", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "report_id"
    t.string "status_ids", array: true
    t.datetime "overruled_at"
    t.datetime "overruled_at", precision: nil
    t.index ["account_id"], name: "index_account_warnings_on_account_id"
    t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id"
  end


@@ 145,8 144,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "domain"
    t.text "private_key"
    t.text "public_key", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.text "note", default: "", null: false
    t.string "display_name", default: "", null: false
    t.string "uri", default: "", null: false


@@ 154,15 153,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "avatar_file_name"
    t.string "avatar_content_type"
    t.integer "avatar_file_size"
    t.datetime "avatar_updated_at"
    t.datetime "avatar_updated_at", precision: nil
    t.string "header_file_name"
    t.string "header_content_type"
    t.integer "header_file_size"
    t.datetime "header_updated_at"
    t.datetime "header_updated_at", precision: nil
    t.string "avatar_remote_url"
    t.boolean "locked", default: false, null: false
    t.string "header_remote_url", default: "", null: false
    t.datetime "last_webfingered_at"
    t.datetime "last_webfingered_at", precision: nil
    t.string "inbox_url", default: "", null: false
    t.string "outbox_url", default: "", null: false
    t.string "shared_inbox_url", default: "", null: false


@@ 175,17 174,17 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "actor_type"
    t.boolean "discoverable"
    t.string "also_known_as", array: true
    t.datetime "silenced_at"
    t.datetime "suspended_at"
    t.datetime "silenced_at", precision: nil
    t.datetime "suspended_at", precision: nil
    t.boolean "hide_collections"
    t.integer "avatar_storage_schema_version"
    t.integer "header_storage_schema_version"
    t.string "devices_url"
    t.integer "suspension_origin"
    t.datetime "sensitized_at"
    t.datetime "sensitized_at", precision: nil
    t.boolean "trendable"
    t.datetime "reviewed_at"
    t.datetime "requested_review_at"
    t.datetime "reviewed_at", precision: nil
    t.datetime "requested_review_at", precision: nil
    t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
    t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
    t.index ["domain", "id"], name: "index_accounts_on_domain_and_id"


@@ 205,8 204,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "action", default: "", null: false
    t.string "target_type"
    t.bigint "target_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "human_identifier"
    t.string "route_param"
    t.string "permalink"


@@ 217,8 216,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "announcement_mutes", force: :cascade do |t|
    t.bigint "account_id"
    t.bigint "announcement_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id", "announcement_id"], name: "index_announcement_mutes_on_account_id_and_announcement_id", unique: true
    t.index ["announcement_id"], name: "index_announcement_mutes_on_announcement_id"
  end


@@ 228,8 227,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "announcement_id"
    t.string "name", default: "", null: false
    t.bigint "custom_emoji_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id", "announcement_id", "name"], name: "index_announcement_reactions_on_account_id_and_announcement_id", unique: true
    t.index ["announcement_id"], name: "index_announcement_reactions_on_announcement_id"
    t.index ["custom_emoji_id"], name: "index_announcement_reactions_on_custom_emoji_id", where: "(custom_emoji_id IS NOT NULL)"


@@ 239,12 238,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.text "text", default: "", null: false
    t.boolean "published", default: false, null: false
    t.boolean "all_day", default: false, null: false
    t.datetime "scheduled_at"
    t.datetime "starts_at"
    t.datetime "ends_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "published_at"
    t.datetime "scheduled_at", precision: nil
    t.datetime "starts_at", precision: nil
    t.datetime "ends_at", precision: nil
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.datetime "published_at", precision: nil
    t.bigint "status_ids", array: true
  end



@@ 252,12 251,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id", null: false
    t.bigint "account_warning_id", null: false
    t.text "text", default: "", null: false
    t.datetime "approved_at"
    t.datetime "approved_at", precision: nil
    t.bigint "approved_by_account_id"
    t.datetime "rejected_at"
    t.datetime "rejected_at", precision: nil
    t.bigint "rejected_by_account_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["account_id"], name: "index_appeals_on_account_id"
    t.index ["account_warning_id"], name: "index_appeals_on_account_warning_id", unique: true
    t.index ["approved_by_account_id"], name: "index_appeals_on_approved_by_account_id", where: "(approved_by_account_id IS NOT NULL)"


@@ 268,17 267,17 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "user_id"
    t.string "dump_file_name"
    t.string "dump_content_type"
    t.datetime "dump_updated_at"
    t.datetime "dump_updated_at", precision: nil
    t.boolean "processed", default: false, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "dump_file_size"
    t.index ["user_id"], name: "index_backups_on_user_id"
  end

  create_table "blocks", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "target_account_id", null: false
    t.string "uri"


@@ 289,8 288,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "bookmarks", force: :cascade do |t|
    t.bigint "account_id", null: false
    t.bigint "status_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id", "status_id"], name: "index_bookmarks_on_account_id_and_status_id", unique: true
    t.index ["status_id"], name: "index_bookmarks_on_status_id"
  end


@@ 298,8 297,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "bulk_import_rows", force: :cascade do |t|
    t.bigint "bulk_import_id", null: false
    t.jsonb "data"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["bulk_import_id"], name: "index_bulk_import_rows_on_bulk_import_id"
  end



@@ 309,13 308,13 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.integer "total_items", default: 0, null: false
    t.integer "imported_items", default: 0, null: false
    t.integer "processed_items", default: 0, null: false
    t.datetime "finished_at"
    t.datetime "finished_at", precision: nil
    t.boolean "overwrite", default: false, null: false
    t.boolean "likely_mismatched", default: false, null: false
    t.string "original_filename", default: "", null: false
    t.bigint "account_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["account_id"], name: "index_bulk_imports_on_account_id"
    t.index ["id"], name: "index_bulk_imports_unconfirmed", where: "(state = 0)"
  end


@@ 323,8 322,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "canonical_email_blocks", force: :cascade do |t|
    t.string "canonical_email_hash", default: "", null: false
    t.bigint "reference_account_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["canonical_email_hash"], name: "index_canonical_email_blocks_on_canonical_email_hash", unique: true
    t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id"
  end


@@ 337,15 336,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "conversations", force: :cascade do |t|
    t.string "uri"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["uri"], name: "index_conversations_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)"
  end

  create_table "custom_emoji_categories", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["name"], name: "index_custom_emoji_categories_on_name", unique: true
  end



@@ 355,9 354,9 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "image_file_name"
    t.string "image_content_type"
    t.integer "image_file_size"
    t.datetime "image_updated_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "image_updated_at", precision: nil
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.boolean "disabled", default: false, null: false
    t.string "uri"
    t.string "image_remote_url"


@@ 371,27 370,27 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "custom_filter_id", null: false
    t.text "keyword", default: "", null: false
    t.boolean "whole_word", default: true, null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["custom_filter_id"], name: "index_custom_filter_keywords_on_custom_filter_id"
  end

  create_table "custom_filter_statuses", force: :cascade do |t|
    t.bigint "custom_filter_id", null: false
    t.bigint "status_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["custom_filter_id"], name: "index_custom_filter_statuses_on_custom_filter_id"
    t.index ["status_id"], name: "index_custom_filter_statuses_on_status_id"
  end

  create_table "custom_filters", force: :cascade do |t|
    t.bigint "account_id"
    t.datetime "expires_at"
    t.datetime "expires_at", precision: nil
    t.text "phrase", default: "", null: false
    t.string "context", default: [], null: false, array: true
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.integer "action", default: 0, null: false
    t.index ["account_id"], name: "index_custom_filters_on_account_id"
  end


@@ 403,23 402,23 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "name", default: "", null: false
    t.text "fingerprint_key", default: "", null: false
    t.text "identity_key", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["access_token_id"], name: "index_devices_on_access_token_id"
    t.index ["account_id"], name: "index_devices_on_account_id"
  end

  create_table "domain_allows", force: :cascade do |t|
    t.string "domain", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["domain"], name: "index_domain_allows_on_domain", unique: true
  end

  create_table "domain_blocks", force: :cascade do |t|
    t.string "domain", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.integer "severity", default: 0
    t.boolean "reject_media", default: false, null: false
    t.boolean "reject_reports", default: false, null: false


@@ 431,8 430,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "email_domain_blocks", force: :cascade do |t|
    t.string "domain", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "parent_id"
    t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true
  end


@@ 445,15 444,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.text "body", default: "", null: false
    t.text "digest", default: "", null: false
    t.text "message_franking", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["device_id"], name: "index_encrypted_messages_on_device_id"
    t.index ["from_account_id"], name: "index_encrypted_messages_on_from_account_id"
  end

  create_table "favourites", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "status_id", null: false
    t.index ["account_id", "id"], name: "index_favourites_on_account_id_and_id"


@@ 465,9 464,9 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id", null: false
    t.bigint "tag_id", null: false
    t.bigint "statuses_count", default: 0, null: false
    t.datetime "last_status_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "last_status_at", precision: nil
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "name"
    t.index ["account_id", "tag_id"], name: "index_featured_tags_on_account_id_and_tag_id", unique: true
    t.index ["tag_id"], name: "index_featured_tags_on_tag_id"


@@ 475,14 474,14 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "follow_recommendation_suppressions", force: :cascade do |t|
    t.bigint "account_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["account_id"], name: "index_follow_recommendation_suppressions_on_account_id", unique: true
  end

  create_table "follow_requests", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "target_account_id", null: false
    t.boolean "show_reblogs", default: true, null: false


@@ 493,8 492,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  end

  create_table "follows", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "target_account_id", null: false
    t.boolean "show_reblogs", default: true, null: false


@@ 508,8 507,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "identities", force: :cascade do |t|
    t.string "provider", default: "", null: false
    t.string "uid", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "user_id"
    t.index ["user_id"], name: "index_identities_on_user_id"
  end


@@ 517,12 516,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "imports", force: :cascade do |t|
    t.integer "type", null: false
    t.boolean "approved", default: false, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "data_file_name"
    t.string "data_content_type"
    t.integer "data_file_size"
    t.datetime "data_updated_at"
    t.datetime "data_updated_at", precision: nil
    t.bigint "account_id", null: false
    t.boolean "overwrite", default: false, null: false
  end


@@ 530,11 529,11 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "invites", force: :cascade do |t|
    t.bigint "user_id", null: false
    t.string "code", default: "", null: false
    t.datetime "expires_at"
    t.datetime "expires_at", precision: nil
    t.integer "max_uses"
    t.integer "uses", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.boolean "autofollow", default: false, null: false
    t.text "comment"
    t.index ["code"], name: "index_invites_on_code", unique: true


@@ 542,9 541,9 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  end

  create_table "ip_blocks", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "expires_at"
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.datetime "expires_at", precision: nil
    t.inet "ip", default: "0.0.0.0", null: false
    t.integer "severity", default: 0, null: false
    t.text "comment", default: "", null: false


@@ 565,8 564,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "lists", force: :cascade do |t|
    t.bigint "account_id", null: false
    t.string "title", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.integer "replies_policy", default: 0, null: false
    t.boolean "exclusive", default: false, null: false
    t.index ["account_id"], name: "index_lists_on_account_id"


@@ 580,7 579,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "failure_reason"
    t.inet "ip"
    t.string "user_agent"
    t.datetime "created_at"
    t.datetime "created_at", precision: nil
    t.index ["user_id"], name: "index_login_activities_on_user_id"
  end



@@ 589,8 588,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "timeline", default: "", null: false
    t.bigint "last_read_id", default: 0, null: false
    t.integer "lock_version", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["user_id", "timeline"], name: "index_markers_on_user_id_and_timeline", unique: true
  end



@@ 599,10 598,10 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "file_file_name"
    t.string "file_content_type"
    t.integer "file_file_size"
    t.datetime "file_updated_at"
    t.datetime "file_updated_at", precision: nil
    t.string "remote_url", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "shortcode"
    t.integer "type", default: 0, null: false
    t.json "file_meta"


@@ 615,7 614,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "thumbnail_file_name"
    t.string "thumbnail_content_type"
    t.integer "thumbnail_file_size"
    t.datetime "thumbnail_updated_at"
    t.datetime "thumbnail_updated_at", precision: nil
    t.string "thumbnail_remote_url"
    t.index ["account_id", "status_id"], name: "index_media_attachments_on_account_id_and_status_id", order: { status_id: :desc }
    t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id", where: "(scheduled_status_id IS NOT NULL)"


@@ 625,8 624,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "mentions", force: :cascade do |t|
    t.bigint "status_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id"
    t.boolean "silent", default: false, null: false
    t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true


@@ 634,12 633,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  end

  create_table "mutes", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.boolean "hide_notifications", default: true, null: false
    t.bigint "account_id", null: false
    t.bigint "target_account_id", null: false
    t.datetime "expires_at"
    t.datetime "expires_at", precision: nil
    t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true
    t.index ["target_account_id"], name: "index_mutes_on_target_account_id"
  end


@@ 647,8 646,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "notifications", force: :cascade do |t|
    t.bigint "activity_id", null: false
    t.string "activity_type", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "from_account_id", null: false
    t.string "type"


@@ 661,8 660,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "token", null: false
    t.integer "expires_in", null: false
    t.text "redirect_uri", null: false
    t.datetime "created_at", null: false
    t.datetime "revoked_at"
    t.datetime "created_at", precision: nil, null: false
    t.datetime "revoked_at", precision: nil
    t.string "scopes"
    t.bigint "application_id", null: false
    t.bigint "resource_owner_id", null: false


@@ 674,12 673,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "token", null: false
    t.string "refresh_token"
    t.integer "expires_in"
    t.datetime "revoked_at"
    t.datetime "created_at", null: false
    t.datetime "revoked_at", precision: nil
    t.datetime "created_at", precision: nil, null: false
    t.string "scopes"
    t.bigint "application_id"
    t.bigint "resource_owner_id"
    t.datetime "last_used_at"
    t.datetime "last_used_at", precision: nil
    t.inet "last_used_ip"
    t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, opclass: :text_pattern_ops, where: "(refresh_token IS NOT NULL)"
    t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", where: "(resource_owner_id IS NOT NULL)"


@@ 692,8 691,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "secret", null: false
    t.text "redirect_uri", null: false
    t.string "scopes", default: "", null: false
    t.datetime "created_at"
    t.datetime "updated_at"
    t.datetime "created_at", precision: nil
    t.datetime "updated_at", precision: nil
    t.boolean "superapp", default: false, null: false
    t.string "website"
    t.string "owner_type"


@@ 709,8 708,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "key_id", default: "", null: false
    t.text "key", default: "", null: false
    t.text "signature", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["device_id"], name: "index_one_time_keys_on_device_id"
    t.index ["key_id"], name: "index_one_time_keys_on_key_id"
  end


@@ 720,7 719,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.text "schema"
    t.text "relation"
    t.bigint "size"
    t.datetime "captured_at"
    t.datetime "captured_at", precision: nil
    t.index ["database", "captured_at"], name: "index_pghero_space_stats_on_database_and_captured_at"
  end



@@ 728,8 727,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id"
    t.bigint "poll_id"
    t.integer "choice", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "uri"
    t.index ["account_id"], name: "index_poll_votes_on_account_id"
    t.index ["poll_id"], name: "index_poll_votes_on_poll_id"


@@ 738,15 737,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "polls", force: :cascade do |t|
    t.bigint "account_id"
    t.bigint "status_id"
    t.datetime "expires_at"
    t.datetime "expires_at", precision: nil
    t.string "options", default: [], null: false, array: true
    t.bigint "cached_tallies", default: [], null: false, array: true
    t.boolean "multiple", default: false, null: false
    t.boolean "hide_totals", default: false, null: false
    t.bigint "votes_count", default: 0, null: false
    t.datetime "last_fetched_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "last_fetched_at", precision: nil
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.integer "lock_version", default: 0, null: false
    t.bigint "voters_count"
    t.index ["account_id"], name: "index_polls_on_account_id"


@@ 758,12 757,12 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "icon_file_name"
    t.string "icon_content_type"
    t.bigint "icon_file_size"
    t.datetime "icon_updated_at"
    t.datetime "icon_updated_at", precision: nil
    t.boolean "trendable"
    t.datetime "reviewed_at"
    t.datetime "requested_review_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "reviewed_at", precision: nil
    t.datetime "requested_review_at", precision: nil
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["domain"], name: "index_preview_card_providers_on_domain", unique: true
  end



@@ 783,7 782,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "image_file_name"
    t.string "image_content_type"
    t.integer "image_file_size"
    t.datetime "image_updated_at"
    t.datetime "image_updated_at", precision: nil
    t.integer "type", default: 0, null: false
    t.text "html", default: "", null: false
    t.string "author_name", default: "", null: false


@@ 792,16 791,17 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "provider_url", default: "", null: false
    t.integer "width", default: 0, null: false
    t.integer "height", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "embed_url", default: "", null: false
    t.integer "image_storage_schema_version"
    t.string "blurhash"
    t.string "language"
    t.float "max_score"
    t.datetime "max_score_at"
    t.datetime "max_score_at", precision: nil
    t.boolean "trendable"
    t.integer "link_type"
    t.datetime "published_at"
    t.index ["url"], name: "index_preview_cards_on_url", unique: true
  end



@@ 814,8 814,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "relays", force: :cascade do |t|
    t.string "inbox_url", default: "", null: false
    t.string "follow_activity_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.integer "state", default: 0, null: false
  end



@@ 823,8 823,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.text "content", null: false
    t.bigint "report_id", null: false
    t.bigint "account_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["account_id"], name: "index_report_notes_on_account_id"
    t.index ["report_id"], name: "index_report_notes_on_report_id"
  end


@@ 832,8 832,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "reports", force: :cascade do |t|
    t.bigint "status_ids", default: [], null: false, array: true
    t.text "comment", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "account_id", null: false
    t.bigint "action_taken_by_account_id"
    t.bigint "target_account_id", null: false


@@ 841,7 841,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "uri"
    t.boolean "forwarded"
    t.integer "category", default: 0, null: false
    t.datetime "action_taken_at"
    t.datetime "action_taken_at", precision: nil
    t.bigint "rule_ids", array: true
    t.index ["account_id"], name: "index_reports_on_account_id"
    t.index ["action_taken_by_account_id"], name: "index_reports_on_action_taken_by_account_id", where: "(action_taken_by_account_id IS NOT NULL)"


@@ 851,15 851,15 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "rules", force: :cascade do |t|
    t.integer "priority", default: 0, null: false
    t.datetime "deleted_at"
    t.datetime "deleted_at", precision: nil
    t.text "text", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
  end

  create_table "scheduled_statuses", force: :cascade do |t|
    t.bigint "account_id"
    t.datetime "scheduled_at"
    t.datetime "scheduled_at", precision: nil
    t.jsonb "params"
    t.index ["account_id"], name: "index_scheduled_statuses_on_account_id"
    t.index ["scheduled_at"], name: "index_scheduled_statuses_on_scheduled_at"


@@ 867,8 867,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "session_activations", force: :cascade do |t|
    t.string "session_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "user_agent", default: "", null: false
    t.inet "ip"
    t.bigint "access_token_id"


@@ 883,8 883,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "var", null: false
    t.text "value"
    t.string "thing_type"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.datetime "created_at", precision: nil
    t.datetime "updated_at", precision: nil
    t.bigint "thing_id"
    t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
  end


@@ 894,10 894,10 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "file_file_name"
    t.string "file_content_type"
    t.integer "file_file_size"
    t.datetime "file_updated_at"
    t.datetime "file_updated_at", precision: nil
    t.json "meta"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "blurhash"
    t.index ["var"], name: "index_site_uploads_on_var", unique: true
  end


@@ 907,8 907,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "account_id"
    t.text "text", default: "", null: false
    t.text "spoiler_text", default: "", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "content_type"
    t.bigint "ordered_media_attachment_ids", array: true
    t.text "media_descriptions", array: true


@@ 921,8 921,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "status_pins", force: :cascade do |t|
    t.bigint "account_id", null: false
    t.bigint "status_id", null: false
    t.datetime "created_at", default: -> { "now()" }, null: false
    t.datetime "updated_at", default: -> { "now()" }, null: false
    t.datetime "created_at", precision: nil, default: -> { "now()" }, null: false
    t.datetime "updated_at", precision: nil, default: -> { "now()" }, null: false
    t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true
    t.index ["status_id"], name: "index_status_pins_on_status_id"
  end


@@ 932,8 932,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "replies_count", default: 0, null: false
    t.bigint "reblogs_count", default: 0, null: false
    t.bigint "favourites_count", default: 0, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true
  end



@@ 951,8 951,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "statuses", id: :bigint, default: -> { "timestamp_id('statuses'::text)" }, force: :cascade do |t|
    t.string "uri"
    t.text "text", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "in_reply_to_id"
    t.bigint "reblog_of_id"
    t.string "url"


@@ 969,8 969,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.boolean "local_only"
    t.bigint "poll_id"
    t.string "content_type"
    t.datetime "deleted_at"
    t.datetime "edited_at"
    t.datetime "deleted_at", precision: nil
    t.datetime "edited_at", precision: nil
    t.boolean "trendable"
    t.bigint "ordered_media_attachment_ids", array: true
    t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"


@@ 992,31 992,31 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "system_keys", force: :cascade do |t|
    t.binary "key"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
  end

  create_table "tag_follows", force: :cascade do |t|
    t.bigint "tag_id", null: false
    t.bigint "account_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["account_id", "tag_id"], name: "index_tag_follows_on_account_id_and_tag_id", unique: true
    t.index ["tag_id"], name: "index_tag_follows_on_tag_id"
  end

  create_table "tags", force: :cascade do |t|
    t.string "name", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.boolean "usable"
    t.boolean "trendable"
    t.boolean "listable"
    t.datetime "reviewed_at"
    t.datetime "requested_review_at"
    t.datetime "last_status_at"
    t.datetime "reviewed_at", precision: nil
    t.datetime "requested_review_at", precision: nil
    t.datetime "last_status_at", precision: nil
    t.float "max_score"
    t.datetime "max_score_at"
    t.datetime "max_score_at", precision: nil
    t.string "display_name"
    t.index "lower((name)::text) text_pattern_ops", name: "index_tags_on_name_lower_btree", unique: true
  end


@@ 1024,8 1024,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
  create_table "tombstones", force: :cascade do |t|
    t.bigint "account_id"
    t.string "uri", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.boolean "by_moderator"
    t.index ["account_id"], name: "index_tombstones_on_account_id"
    t.index ["uri"], name: "index_tombstones_on_uri"


@@ 1033,16 1033,16 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "unavailable_domains", force: :cascade do |t|
    t.string "domain", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["domain"], name: "index_unavailable_domains_on_domain", unique: true
  end

  create_table "user_invite_requests", force: :cascade do |t|
    t.bigint "user_id"
    t.text "text"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["user_id"], name: "index_user_invite_requests_on_user_id"
  end



@@ 1052,24 1052,24 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.integer "position", default: 0, null: false
    t.bigint "permissions", default: 0, null: false
    t.boolean "highlighted", default: false, null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "reset_password_sent_at", precision: nil
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.datetime "current_sign_in_at", precision: nil
    t.datetime "last_sign_in_at", precision: nil
    t.boolean "admin", default: false, null: false
    t.string "confirmation_token"
    t.datetime "confirmed_at"
    t.datetime "confirmation_sent_at"
    t.datetime "confirmed_at", precision: nil
    t.datetime "confirmation_sent_at", precision: nil
    t.string "unconfirmed_email"
    t.string "locale"
    t.string "encrypted_otp_secret"


@@ 1077,7 1077,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "encrypted_otp_secret_salt"
    t.integer "consumed_timestep"
    t.boolean "otp_required_for_login", default: false, null: false
    t.datetime "last_emailed_at"
    t.datetime "last_emailed_at", precision: nil
    t.string "otp_backup_codes", array: true
    t.bigint "account_id", null: false
    t.boolean "disabled", default: false, null: false


@@ 1087,7 1087,7 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.bigint "created_by_application_id"
    t.boolean "approved", default: true, null: false
    t.string "sign_in_token"
    t.datetime "sign_in_token_sent_at"
    t.datetime "sign_in_token_sent_at", precision: nil
    t.string "webauthn_id"
    t.inet "sign_up_ip"
    t.boolean "skip_sign_in_token"


@@ 1108,8 1108,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "key_p256dh", null: false
    t.string "key_auth", null: false
    t.json "data"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "access_token_id"
    t.bigint "user_id"
    t.index ["access_token_id"], name: "index_web_push_subscriptions_on_access_token_id", where: "(access_token_id IS NOT NULL)"


@@ 1118,8 1118,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do

  create_table "web_settings", force: :cascade do |t|
    t.json "data"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.bigint "user_id", null: false
    t.index ["user_id"], name: "index_web_settings_on_user_id", unique: true
  end


@@ 1130,8 1130,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "nickname", null: false
    t.bigint "sign_count", default: 0, null: false
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "created_at", precision: nil, null: false
    t.datetime "updated_at", precision: nil, null: false
    t.index ["external_id"], name: "index_webauthn_credentials_on_external_id", unique: true
    t.index ["user_id"], name: "index_webauthn_credentials_on_user_id"
  end


@@ 1141,8 1141,8 @@ ActiveRecord::Schema[6.1].define(version: 2023_07_02_151753) do
    t.string "events", default: [], null: false, array: true
    t.string "secret", default: "", null: false
    t.boolean "enabled", default: true, null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.text "template"
    t.index ["url"], name: "index_webhooks_on_url", unique: true
  end

A spec/requests/omniauth_callbacks_spec.rb => spec/requests/omniauth_callbacks_spec.rb +124 -0
@@ 0,0 1,124 @@
# frozen_string_literal: true

require 'rails_helper'

describe 'OmniAuth callbacks' do
  shared_examples 'omniauth provider callbacks' do |provider|
    subject { post send "user_#{provider}_omniauth_callback_path" }

    context 'with full information in response' do
      before do
        mock_omniauth(provider, {
          provider: provider.to_s,
          uid: '123',
          info: {
            verified: 'true',
            email: 'user@host.example',
          },
        })
      end

      context 'without a matching user' do
        it 'creates a user and an identity and redirects to root path' do
          expect { subject }
            .to change(User, :count)
            .by(1)
            .and change(Identity, :count)
            .by(1)
            .and change(LoginActivity, :count)
            .by(1)

          expect(User.last.email).to eq('user@host.example')
          expect(Identity.find_by(user: User.last).uid).to eq('123')
          expect(response).to redirect_to(root_path)
        end
      end

      context 'with a matching user and no matching identity' do
        before do
          Fabricate(:user, email: 'user@host.example')
        end

        it 'matches the existing user, creates an identity, and redirects to root path' do
          expect { subject }
            .to not_change(User, :count)
            .and change(Identity, :count)
            .by(1)
            .and change(LoginActivity, :count)
            .by(1)

          expect(Identity.find_by(user: User.last).uid).to eq('123')
          expect(response).to redirect_to(root_path)
        end
      end

      context 'with a matching user and a matching identity' do
        before do
          user = Fabricate(:user, email: 'user@host.example')
          Fabricate(:identity, user: user, uid: '123', provider: provider)
        end

        it 'matches the existing records and redirects to root path' do
          expect { subject }
            .to not_change(User, :count)
            .and not_change(Identity, :count)
            .and change(LoginActivity, :count)
            .by(1)

          expect(response).to redirect_to(root_path)
        end
      end
    end

    context 'with a response missing email address' do
      before do
        mock_omniauth(provider, {
          provider: provider.to_s,
          uid: '123',
          info: {
            verified: 'true',
          },
        })
      end

      it 'redirects to the auth setup page' do
        expect { subject }
          .to change(User, :count)
          .by(1)
          .and change(Identity, :count)
          .by(1)
          .and change(LoginActivity, :count)
          .by(1)

        expect(response).to redirect_to(auth_setup_path(missing_email: '1'))
      end
    end

    context 'when a user cannot be built' do
      before do
        allow(User).to receive(:find_for_oauth).and_return(User.new)
      end

      it 'redirects to the new user signup page' do
        expect { subject }
          .to not_change(User, :count)
          .and not_change(Identity, :count)
          .and not_change(LoginActivity, :count)

        expect(response).to redirect_to(new_user_registration_url)
      end
    end
  end

  describe '#openid_connect', if: ENV['OIDC_ENABLED'] == 'true' && ENV['OIDC_SCOPE'].present? do
    include_examples 'omniauth provider callbacks', :openid_connect
  end

  describe '#cas', if: ENV['CAS_ENABLED'] == 'true' do
    include_examples 'omniauth provider callbacks', :cas
  end

  describe '#saml', if: ENV['SAML_ENABLED'] == 'true' do
    include_examples 'omniauth provider callbacks', :saml
  end
end

A spec/support/omniauth_mocks.rb => spec/support/omniauth_mocks.rb +7 -0
@@ 0,0 1,7 @@
# frozen_string_literal: true

OmniAuth.config.test_mode = true

def mock_omniauth(provider, data)
  OmniAuth.config.mock_auth[provider] = OmniAuth::AuthHash.new(data)
end

M yarn.lock => yarn.lock +6 -6
@@ 9263,9 9263,9 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==

postcss@^8.2.15, postcss@^8.4.24:
  version "8.4.26"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
  integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
  version "8.4.27"
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
  integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
  dependencies:
    nanoid "^3.3.6"
    picocolors "^1.0.0"


@@ 10253,9 10253,9 @@ sass-loader@^10.2.0:
    semver "^7.3.2"

sass@^1.62.1:
  version "1.63.6"
  resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea"
  integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==
  version "1.64.1"
  resolved "https://registry.yarnpkg.com/sass/-/sass-1.64.1.tgz#6a46f6d68e0fa5ad90aa59ce025673ddaa8441cf"
  integrity sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==
  dependencies:
    chokidar ">=3.0.0 <4.0.0"
    immutable "^4.0.0"