~cytrogen/masto-fe

bb98d970e3f3333f85d454bc1fd26a1b582b14d2 — Claire 2 years ago 67055b0 + c2bfbf5
Merge pull request #2291 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes
571 files changed, 2651 insertions(+), 2270 deletions(-)

A .github/workflows/bundler-audit.yml
M .github/workflows/lint-ruby.yml
M .github/workflows/rebase-needed.yml
M .haml-lint_todo.yml
M .rubocop.yml
M .rubocop_todo.yml
M CHANGELOG.md
M Gemfile
M Gemfile.lock
M Rakefile
M app/controllers/api/v1/bookmarks_controller.rb
M app/controllers/api/v1/favourites_controller.rb
M app/controllers/api/v1/reports_controller.rb
M app/controllers/api/v1/timelines/home_controller.rb
M app/controllers/auth/sessions_controller.rb
M app/controllers/concerns/rate_limit_headers.rb
M app/controllers/concerns/two_factor_authentication_concern.rb
M app/helpers/accounts_helper.rb
M app/javascript/flavours/glitch/components/animated_number.tsx
M app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx
R app/javascript/flavours/glitch/components/{common_counter.jsx => counters.tsx}
D app/javascript/flavours/glitch/components/dismissable_banner.jsx
A app/javascript/flavours/glitch/components/dismissable_banner.tsx
M app/javascript/flavours/glitch/components/hashtag.jsx
M app/javascript/flavours/glitch/components/media_gallery.jsx
M app/javascript/flavours/glitch/components/server_banner.jsx
R app/javascript/flavours/glitch/components/{short_number.jsx => short_number.tsx}
M app/javascript/flavours/glitch/components/status_content.jsx
M app/javascript/flavours/glitch/features/account/components/header.jsx
M app/javascript/flavours/glitch/features/community_timeline/index.jsx
M app/javascript/flavours/glitch/features/directory/components/account_card.jsx
M app/javascript/flavours/glitch/features/explore/components/story.jsx
M app/javascript/flavours/glitch/features/explore/index.jsx
M app/javascript/flavours/glitch/features/explore/links.jsx
M app/javascript/flavours/glitch/features/explore/statuses.jsx
M app/javascript/flavours/glitch/features/explore/tags.jsx
M app/javascript/flavours/glitch/features/firehose/index.jsx
M app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx
M app/javascript/flavours/glitch/features/public_timeline/index.jsx
M app/javascript/flavours/glitch/features/report/comment.jsx
M app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
M app/javascript/flavours/glitch/features/ui/components/report_modal.jsx
M app/javascript/flavours/glitch/features/ui/index.jsx
M app/javascript/flavours/glitch/initial_state.js
M app/javascript/flavours/glitch/store/middlewares/sounds.ts
M app/javascript/flavours/glitch/styles/components/misc.scss
M app/javascript/flavours/glitch/styles/components/modal.scss
M app/javascript/flavours/glitch/styles/components/status.scss
M app/javascript/flavours/glitch/styles/contrast/diff.scss
M app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
M app/javascript/flavours/glitch/styles/statuses.scss
M app/javascript/mastodon/actions/alerts.js
M app/javascript/mastodon/actions/compose.js
M app/javascript/mastodon/components/account.jsx
M app/javascript/mastodon/components/animated_number.tsx
M app/javascript/mastodon/components/autosuggest_hashtag.tsx
R app/javascript/mastodon/components/{common_counter.jsx => counters.tsx}
D app/javascript/mastodon/components/dismissable_banner.jsx
A app/javascript/mastodon/components/dismissable_banner.tsx
M app/javascript/mastodon/components/hashtag.jsx
M app/javascript/mastodon/components/media_gallery.jsx
M app/javascript/mastodon/components/server_banner.jsx
R app/javascript/mastodon/components/{short_number.jsx => short_number.tsx}
M app/javascript/mastodon/components/status_action_bar.jsx
M app/javascript/mastodon/components/status_content.jsx
M app/javascript/mastodon/features/account/components/header.jsx
M app/javascript/mastodon/features/community_timeline/index.jsx
M app/javascript/mastodon/features/directory/components/account_card.jsx
M app/javascript/mastodon/features/explore/components/story.jsx
M app/javascript/mastodon/features/explore/index.jsx
M app/javascript/mastodon/features/explore/links.jsx
M app/javascript/mastodon/features/explore/statuses.jsx
M app/javascript/mastodon/features/explore/tags.jsx
M app/javascript/mastodon/features/firehose/index.jsx
M app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx
M app/javascript/mastodon/features/notifications/containers/column_settings_container.js
M app/javascript/mastodon/features/public_timeline/index.jsx
M app/javascript/mastodon/features/report/comment.jsx
M app/javascript/mastodon/features/status/components/action_bar.jsx
M app/javascript/mastodon/features/ui/components/navigation_panel.jsx
M app/javascript/mastodon/features/ui/components/report_modal.jsx
M app/javascript/mastodon/features/ui/containers/notifications_container.js
M app/javascript/mastodon/features/ui/index.jsx
M app/javascript/mastodon/initial_state.js
M app/javascript/mastodon/locales/en.json
M app/javascript/mastodon/reducers/alerts.js
M app/javascript/mastodon/reducers/index.ts
D app/javascript/mastodon/reducers/missed_updates.ts
M app/javascript/mastodon/selectors/index.js
M app/javascript/mastodon/store/middlewares/sounds.ts
M app/javascript/styles/contrast/diff.scss
M app/javascript/styles/mailer.scss
M app/javascript/styles/mastodon-light/diff.scss
M app/javascript/styles/mastodon/components.scss
M app/javascript/styles/mastodon/statuses.scss
M app/lib/activitypub/activity.rb
M app/lib/activitypub/activity/flag.rb
M app/lib/emoji_formatter.rb
M app/lib/text_formatter.rb
M app/mailers/admin_mailer.rb
M app/mailers/notification_mailer.rb
M app/models/account_alias.rb
M app/models/domain_block.rb
M app/models/email_domain_block.rb
M app/models/preview_card_provider.rb
M app/models/trends.rb
M app/models/trends/links.rb
M app/models/trends/query.rb
M app/models/trends/statuses.rb
M app/models/user.rb
M app/serializers/initial_state_serializer.rb
M app/services/account_search_service.rb
M app/services/activitypub/process_account_service.rb
M app/services/appeal_service.rb
M app/services/notify_service.rb
M app/services/report_service.rb
M app/services/resolve_url_service.rb
M app/services/search_service.rb
M app/validators/status_length_validator.rb
M app/views/accounts/show.rss.ruby
M app/views/tags/show.rss.ruby
M app/views/well_known/host_meta/show.xml.ruby
M app/workers/feed_insert_worker.rb
M app/workers/merge_worker.rb
M app/workers/regeneration_worker.rb
M app/workers/unmerge_worker.rb
M config/application.rb
M config/boot.rb
M config/brakeman.ignore
M config/database.yml
M config/environment.rb
M config/environments/development.rb
M config/environments/production.rb
M config/environments/test.rb
M config/i18n-tasks.yml
M config/initializers/0_post_deployment_migrations.rb
M config/initializers/active_model_serializers.rb
M config/initializers/application_controller_renderer.rb
M config/initializers/assets.rb
M config/initializers/backtrace_silencers.rb
M config/initializers/cache_logging.rb
M config/initializers/chewy.rb
M config/initializers/content_security_policy.rb
M config/initializers/cookies_serializer.rb
M config/initializers/cors.rb
M config/initializers/devise.rb
M config/initializers/doorkeeper.rb
M config/initializers/fast_blank.rb
M config/initializers/ffmpeg.rb
M config/initializers/filter_parameter_logging.rb
M config/initializers/http_client_proxy.rb
M config/initializers/httplog.rb
M config/initializers/inflections.rb
M config/initializers/mail_delivery_job.rb
D config/initializers/makara.rb
M config/initializers/mime_types.rb
M config/initializers/oj.rb
M config/initializers/omniauth.rb
M config/initializers/open_uri_redirection.rb
M config/initializers/permissions_policy.rb
M config/initializers/pghero.rb
M config/initializers/preload_link_headers.rb
M config/initializers/premailer_rails.rb
M config/initializers/rack_attack.rb
M config/initializers/rack_attack_logging.rb
M config/initializers/redis.rb
M config/initializers/session_store.rb
M config/initializers/simple_form.rb
M config/initializers/stoplight.rb
M config/initializers/trusted_proxies.rb
M config/initializers/twitter_regex.rb
M config/initializers/webauthn.rb
M config/initializers/wrap_parameters.rb
M config/locales/sr-Latn.rb
M config/locales/sr.rb
M config/puma.rb
M config/routes/admin.rb
M db/migrate/20160220174730_create_accounts.rb
M db/migrate/20160220211917_create_statuses.rb
M db/migrate/20160221003140_create_users.rb
M db/migrate/20160221003621_create_follows.rb
M db/migrate/20160222122600_create_stream_entries.rb
M db/migrate/20160222143943_add_profile_fields_to_accounts.rb
M db/migrate/20160223162837_add_metadata_to_statuses.rb
M db/migrate/20160223164502_make_uris_nullable_in_statuses.rb
M db/migrate/20160223165723_add_url_to_statuses.rb
M db/migrate/20160223165855_add_url_to_accounts.rb
M db/migrate/20160223171800_create_favourites.rb
M db/migrate/20160224223247_create_mentions.rb
M db/migrate/20160227230233_add_attachment_avatar_to_accounts.rb
M db/migrate/20160305115639_add_devise_to_users.rb
M db/migrate/20160306172223_create_doorkeeper_tables.rb
M db/migrate/20160312193225_add_attachment_header_to_accounts.rb
M db/migrate/20160314164231_add_owner_to_application.rb
M db/migrate/20160316103650_add_missing_indices.rb
M db/migrate/20160322193748_add_avatar_remote_url_to_accounts.rb
M db/migrate/20160325130944_add_admin_to_users.rb
M db/migrate/20160826155805_add_superapp_to_oauth_applications.rb
M db/migrate/20160905150353_create_media_attachments.rb
M db/migrate/20160919221059_add_subscription_expires_at_to_accounts.rb
M db/migrate/20160920003904_remove_verify_token_from_accounts.rb
M db/migrate/20160926213048_remove_owner_from_application.rb
M db/migrate/20161003142332_add_confirmable_to_users.rb
M db/migrate/20161003145426_create_blocks.rb
M db/migrate/20161006213403_rails_settings_migration.rb
M db/migrate/20161009120834_create_domain_blocks.rb
M db/migrate/20161027172456_add_silenced_to_accounts.rb
M db/migrate/20161104173623_create_tags.rb
M db/migrate/20161105130633_create_statuses_tags_join_table.rb
M db/migrate/20161116162355_add_locale_to_users.rb
M db/migrate/20161119211120_create_notifications.rb
M db/migrate/20161122163057_remove_unneeded_indexes.rb
M db/migrate/20161123093447_add_sensitive_to_statuses.rb
M db/migrate/20161128103007_create_subscriptions.rb
M db/migrate/20161130142058_add_last_successful_delivery_at_to_subscriptions.rb
M db/migrate/20161130185319_add_visibility_to_statuses.rb
M db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb
M db/migrate/20161203164520_add_from_account_id_to_notifications.rb
M db/migrate/20161205214545_add_suspended_to_accounts.rb
M db/migrate/20161221152630_add_hidden_to_stream_entries.rb
M db/migrate/20161222201034_add_locked_to_accounts.rb
M db/migrate/20161222204147_create_follow_requests.rb
M db/migrate/20170105224407_add_shortcode_to_media_attachments.rb
M db/migrate/20170109120109_create_web_settings.rb
M db/migrate/20170112154826_migrate_settings.rb
M db/migrate/20170114194937_add_application_to_statuses.rb
M db/migrate/20170114203041_add_website_to_oauth_application.rb
M db/migrate/20170119214911_create_preview_cards.rb
M db/migrate/20170123162658_add_severity_to_domain_blocks.rb
M db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb
M db/migrate/20170125145934_add_spoiler_text_to_statuses.rb
M db/migrate/20170127165745_add_devise_two_factor_to_users.rb
M db/migrate/20170205175257_remove_devices.rb
M db/migrate/20170209184350_add_reply_to_statuses.rb
M db/migrate/20170214110202_create_reports.rb
M db/migrate/20170217012631_add_reblog_of_id_foreign_key_to_statuses.rb
M db/migrate/20170301222600_create_mutes.rb
M db/migrate/20170303212857_add_last_emailed_at_to_users.rb
M db/migrate/20170304202101_add_type_to_media_attachments.rb
M db/migrate/20170317193015_add_search_index_to_accounts.rb
M db/migrate/20170318214217_add_header_remote_url_to_accounts.rb
M db/migrate/20170322021028_add_lowercase_index_to_accounts.rb
M db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb
M db/migrate/20170322162804_add_search_index_to_tags.rb
M db/migrate/20170330021336_add_counter_caches.rb
M db/migrate/20170330163835_create_imports.rb
M db/migrate/20170330164118_add_attachment_data_to_imports.rb
M db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb
M db/migrate/20170405112956_add_index_on_mentions_status_id.rb
M db/migrate/20170406215816_add_notifications_and_favourites_indices.rb
M db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb
M db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb
M db/migrate/20170414132105_add_language_to_statuses.rb
M db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb
M db/migrate/20170423005413_add_allowed_languages_to_user.rb
M db/migrate/20170424003227_create_account_domain_blocks.rb
M db/migrate/20170424112722_add_status_id_index_to_statuses_tags.rb
M db/migrate/20170425131920_add_media_attachment_meta.rb
M db/migrate/20170425202925_add_oembed_to_preview_cards.rb
M db/migrate/20170427011934_re_add_owner_to_application.rb
M db/migrate/20170506235850_create_conversations.rb
M db/migrate/20170507000211_add_conversation_id_to_statuses.rb
M db/migrate/20170507141759_optimize_index_subscriptions.rb
M db/migrate/20170508230434_create_conversation_mutes.rb
M db/migrate/20170516072309_add_index_accounts_on_uri.rb
M db/migrate/20170520145338_change_language_filter_to_opt_out.rb
M db/migrate/20170601210557_add_index_on_media_attachments_account_id.rb
M db/migrate/20170604144747_add_foreign_keys_for_accounts.rb
M db/migrate/20170606113804_change_tag_search_index_to_btree.rb
M db/migrate/20170609145826_remove_default_language_from_statuses.rb
M db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb
M db/migrate/20170623152212_create_session_activations.rb
M db/migrate/20170624134742_add_description_to_session_activations.rb
M db/migrate/20170625140443_add_access_token_id_to_session_activations.rb
M db/migrate/20170711225116_fix_null_booleans.rb
M db/migrate/20170713112503_make_tag_search_case_insensitive.rb
M db/migrate/20170713175513_create_web_push_subscriptions.rb
M db/migrate/20170713190709_add_web_push_subscription_to_session_activations.rb
M db/migrate/20170714184731_add_domain_to_subscriptions.rb
M db/migrate/20170716191202_add_hide_notifications_to_mute.rb
M db/migrate/20170718211102_add_activitypub_to_accounts.rb
M db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb
M db/migrate/20170823162448_create_status_pins.rb
M db/migrate/20170824103029_add_timestamps_to_status_pins.rb
M db/migrate/20170829215220_remove_status_pins_account_index.rb
M db/migrate/20170901141119_truncate_preview_cards.rb
M db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb
M db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb
M db/migrate/20170905165803_add_local_to_statuses.rb
M db/migrate/20170913000752_create_site_uploads.rb
M db/migrate/20170917153509_create_custom_emojis.rb
M db/migrate/20170918125918_ids_to_bigints.rb
M db/migrate/20170920024819_status_ids_to_timestamp_ids.rb
M db/migrate/20170920032311_fix_reblogs_in_feeds.rb
M db/migrate/20170924022025_ids_to_bigints2.rb
M db/migrate/20170927215609_add_description_to_media_attachments.rb
M db/migrate/20170928082043_create_email_domain_blocks.rb
M db/migrate/20171005102658_create_account_moderation_notes.rb
M db/migrate/20171005171936_add_disabled_to_custom_emojis.rb
M db/migrate/20171006142024_add_uri_to_custom_emojis.rb
M db/migrate/20171010023049_add_foreign_key_to_account_moderation_notes.rb
M db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb
M db/migrate/20171020084748_add_visible_in_picker_to_custom_emoji.rb
M db/migrate/20171028221157_add_reblogs_to_follows.rb
M db/migrate/20171107143332_add_memorial_to_accounts.rb
M db/migrate/20171107143624_add_disabled_to_users.rb
M db/migrate/20171109012327_add_moderator_to_accounts.rb
M db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb
M db/migrate/20171114231651_create_lists.rb
M db/migrate/20171116161857_create_list_accounts.rb
M db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb
M db/migrate/20171119172437_create_admin_action_logs.rb
M db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb
M db/migrate/20171125024930_create_invites.rb
M db/migrate/20171125031751_add_invite_id_to_users.rb
M db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb
M db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb
M db/migrate/20171129172043_add_index_on_stream_entries.rb
M db/migrate/20171130000000_add_embed_url_to_preview_cards.rb
M db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb
M db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb
M db/migrate/20171226094803_more_faster_index_on_notifications.rb
M db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb
M db/migrate/20180109143959_add_remember_token_to_users.rb
M db/migrate/20180204034416_create_identities.rb
M db/migrate/20180206000000_change_user_id_nonnullable.rb
M db/migrate/20180211015820_create_backups.rb
M db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb
M db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb
M db/migrate/20180402031200_add_assigned_account_id_to_reports.rb
M db/migrate/20180402040909_create_report_notes.rb
M db/migrate/20180410204633_add_fields_to_accounts.rb
M db/migrate/20180416210259_add_uri_to_relationships.rb
M db/migrate/20180506221944_add_actor_type_to_accounts.rb
M db/migrate/20180510214435_add_access_token_id_to_web_push_subscriptions.rb
M db/migrate/20180510230049_migrate_web_push_subscriptions.rb
M db/migrate/20180528141303_fix_accounts_unique_index.rb
M db/migrate/20180608213548_reject_following_blocked_users.rb
M db/migrate/20180609104432_migrate_web_push_subscriptions2.rb
M db/migrate/20180615122121_add_autofollow_to_invites.rb
M db/migrate/20180616192031_add_chosen_languages_to_users.rb
M db/migrate/20180617162849_remove_unused_indexes.rb
M db/migrate/20180628181026_create_custom_filters.rb
M db/migrate/20180707154237_add_whole_word_to_custom_filter.rb
M db/migrate/20180711152640_create_relays.rb
M db/migrate/20180808175627_create_account_pins.rb
M db/migrate/20180812123222_change_relays_enabled.rb
M db/migrate/20180812162710_create_status_stats.rb
M db/migrate/20180812173710_copy_status_stats.rb
M db/migrate/20180814171349_add_confidential_to_doorkeeper_application.rb
M db/migrate/20180831171112_create_bookmarks.rb
M db/migrate/20180929222014_create_account_conversations.rb
M db/migrate/20181007025445_create_pghero_space_stats.rb
M db/migrate/20181010141500_add_silent_to_mentions.rb
M db/migrate/20181017170937_add_reject_reports_to_domain_blocks.rb
M db/migrate/20181018205649_add_unread_to_account_conversations.rb
M db/migrate/20181024224956_migrate_account_conversations.rb
M db/migrate/20181026034033_remove_faux_remote_account_duplicates.rb
M db/migrate/20181116165755_create_account_stats.rb
M db/migrate/20181116173541_copy_account_stats.rb
M db/migrate/20181127130500_identity_id_to_bigint.rb
M db/migrate/20181127165847_add_show_replies_to_lists.rb
M db/migrate/20181203003808_create_accounts_tags_join_table.rb
M db/migrate/20181203021853_add_discoverable_to_accounts.rb
M db/migrate/20181204193439_add_last_status_at_to_account_stats.rb
M db/migrate/20181204215309_create_account_tag_stats.rb
M db/migrate/20181207011115_downcase_custom_emoji_domains.rb
M db/migrate/20181213184704_create_account_warnings.rb
M db/migrate/20181213185533_create_account_warning_presets.rb
M db/migrate/20181219235220_add_created_by_application_id_to_users.rb
M db/migrate/20181226021420_add_also_known_as_to_accounts.rb
M db/migrate/20190103124649_create_scheduled_statuses.rb
M db/migrate/20190103124754_add_scheduled_status_id_to_media_attachments.rb
M db/migrate/20190117114553_create_tombstones.rb
M db/migrate/20190201012802_add_overwrite_to_imports.rb
M db/migrate/20190203180359_create_featured_tags.rb
M db/migrate/20190225031541_create_polls.rb
M db/migrate/20190225031625_create_poll_votes.rb
M db/migrate/20190226003449_add_poll_id_to_statuses.rb
M db/migrate/20190304152020_add_uri_to_poll_votes.rb
M db/migrate/20190306145741_add_lock_version_to_polls.rb
M db/migrate/20190307234537_add_approved_to_users.rb
M db/migrate/20190314181829_migrate_open_registrations_setting.rb
M db/migrate/20190316190352_create_account_identity_proofs.rb
M db/migrate/20190317135723_add_uri_to_reports.rb
M db/migrate/20190403141604_add_comment_to_invites.rb
M db/migrate/20190409054914_create_user_invite_requests.rb
M db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
M db/migrate/20190509164208_add_by_moderator_to_tombstone.rb
M db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
M db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb
M db/migrate/20190627222225_create_custom_emoji_categories.rb
M db/migrate/20190627222826_add_category_id_to_custom_emojis.rb
M db/migrate/20190701022101_add_trust_level_to_accounts.rb
M db/migrate/20190705002136_create_domain_allows.rb
M db/migrate/20190715164535_add_instance_actor.rb
M db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb
M db/migrate/20190729185330_add_score_to_tags.rb
M db/migrate/20190805123746_add_capabilities_to_tags.rb
M db/migrate/20190807135426_add_comments_to_domain_blocks.rb
M db/migrate/20190815225426_add_last_status_at_to_tags.rb
M db/migrate/20190819134503_add_deleted_at_to_statuses.rb
M db/migrate/20190820003045_update_statuses_index.rb
M db/migrate/20190823221802_add_local_index_to_statuses.rb
M db/migrate/20190901035623_add_max_score_to_tags.rb
M db/migrate/20190904222339_create_markers.rb
M db/migrate/20190914202517_create_account_migrations.rb
M db/migrate/20190915194355_create_account_aliases.rb
M db/migrate/20190927232842_add_voters_count_to_polls.rb
M db/migrate/20191001213028_add_lock_version_to_account_stats.rb
M db/migrate/20191007013357_update_pt_locales.rb
M db/migrate/20191031163205_change_list_account_follow_nullable.rb
M db/migrate/20191212003415_increase_backup_size.rb
M db/migrate/20191212163405_add_hide_collections_to_accounts.rb
M db/migrate/20191218153258_create_announcements.rb
M db/migrate/20200113125135_create_announcement_mutes.rb
M db/migrate/20200114113335_create_announcement_reactions.rb
M db/migrate/20200119112504_add_public_index_to_statuses.rb
M db/migrate/20200126203551_add_published_at_to_announcements.rb
M db/migrate/20200306035625_add_processing_to_media_attachments.rb
M db/migrate/20200309150742_add_forwarded_to_reports.rb
M db/migrate/20200312144258_add_title_to_account_warning_presets.rb
M db/migrate/20200312162302_add_status_ids_to_announcements.rb
M db/migrate/20200312185443_add_parent_id_to_email_domain_blocks.rb
M db/migrate/20200317021758_add_expires_at_to_mutes.rb
M db/migrate/20200407201300_create_unavailable_domains.rb
M db/migrate/20200407202420_migrate_unavailable_inboxes.rb
M db/migrate/20200417125749_add_storage_schema_version.rb
M db/migrate/20200508212852_reset_unique_jobs_locks.rb
M db/migrate/20200510110808_reset_web_app_secret.rb
M db/migrate/20200510181721_remove_duplicated_indexes_pghero.rb
M db/migrate/20200516180352_create_devices.rb
M db/migrate/20200516183822_create_one_time_keys.rb
M db/migrate/20200518083523_create_encrypted_messages.rb
M db/migrate/20200521180606_encrypted_message_ids_to_timestamp_ids.rb
M db/migrate/20200529214050_add_devices_url_to_accounts.rb
M db/migrate/20200601222558_create_system_keys.rb
M db/migrate/20200605155027_add_blurhash_to_preview_cards.rb
M db/migrate/20200608113046_add_sign_in_token_to_users.rb
M db/migrate/20200614002136_add_sensitized_to_accounts.rb
M db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb
M db/migrate/20200622213645_media_attachment_ids_to_timestamp_ids.rb
M db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb
M db/migrate/20200628133322_create_account_notes.rb
M db/migrate/20200630190240_create_webauthn_credentials.rb
M db/migrate/20200630190544_add_webauthn_id_to_users.rb
M db/migrate/20200908193330_create_account_deletion_requests.rb
M db/migrate/20200917192924_add_notify_to_follows.rb
M db/migrate/20200917193034_add_type_to_notifications.rb
M db/migrate/20200917222316_add_index_notifications_on_type.rb
M db/migrate/20201008202037_create_ip_blocks.rb
M db/migrate/20201008220312_add_sign_up_ip_to_users.rb
M db/migrate/20201017233919_add_suspension_origin_to_accounts.rb
M db/migrate/20201206004238_create_instances.rb
M db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb
M db/migrate/20210221045109_create_rules.rb
M db/migrate/20210306164523_account_ids_to_timestamp_ids.rb
M db/migrate/20210322164601_create_account_summaries.rb
M db/migrate/20210323114347_create_follow_recommendations.rb
M db/migrate/20210324171613_create_follow_recommendation_suppressions.rb
M db/migrate/20210416200740_create_canonical_email_blocks.rb
M db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb
M db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb
M db/migrate/20210505174616_update_follow_recommendations_to_version_2.rb
M db/migrate/20210609202149_create_login_activities.rb
M db/migrate/20210616214526_create_user_ips.rb
M db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb
M db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb
M db/migrate/20210722120340_create_account_statuses_cleanup_policies.rb
M db/migrate/20210904215403_add_edited_at_to_statuses.rb
M db/migrate/20210908220918_create_status_edits.rb
M db/migrate/20211031031021_create_preview_card_providers.rb
M db/migrate/20211112011713_add_language_to_preview_cards.rb
M db/migrate/20211115032527_add_trendable_to_preview_cards.rb
M db/migrate/20211123212714_add_link_type_to_preview_cards.rb
M db/migrate/20211213040746_update_account_summaries_to_version_2.rb
M db/migrate/20211231080958_add_category_to_reports.rb
M db/migrate/20220105163928_remove_mentions_status_id_index.rb
M db/migrate/20220115125126_add_report_id_to_account_warnings.rb
M db/migrate/20220115125341_fix_account_warning_actions.rb
M db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb
M db/migrate/20220124141035_create_appeals.rb
M db/migrate/20220202200743_add_trendable_to_accounts.rb
M db/migrate/20220202200926_add_trendable_to_statuses.rb
M db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb
M db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb
M db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb
M db/migrate/20220302232632_add_ordered_media_attachment_ids_to_statuses.rb
M db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb
M db/migrate/20220304195405_migrate_hide_network_preference.rb
M db/migrate/20220307094650_fix_featured_tags_constraints.rb
M db/migrate/20220309213005_fix_reblog_deleted_at.rb
M db/migrate/20220316233212_update_kurdish_locales.rb
M db/migrate/20220428112511_add_index_statuses_on_account_id.rb
M db/migrate/20220428112727_add_index_statuses_pins_on_status_id.rb
M db/migrate/20220428114454_add_index_reports_on_assigned_account_id.rb
M db/migrate/20220428114902_add_index_reports_on_action_taken_by_account_id.rb
M db/migrate/20220606044941_create_webhooks.rb
M db/migrate/20220611210335_create_user_roles.rb
M db/migrate/20220611212541_add_role_id_to_users.rb
M db/migrate/20220710102457_add_display_name_to_tags.rb
M db/migrate/20220714171049_create_tag_follows.rb
M db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb
M db/migrate/20220824233535_create_status_trends.rb
M db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
M db/migrate/20220829192633_add_languages_to_follows.rb
M db/migrate/20220829192658_add_languages_to_follow_requests.rb
M db/migrate/20221006061337_create_preview_card_trends.rb
M db/migrate/20221012181003_add_blurhash_to_site_uploads.rb
M db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb
M db/migrate/20221025171544_add_index_ip_blocks_on_ip.rb
M db/migrate/20221104133904_add_name_to_featured_tags.rb
M db/post_migrate/20190519130537_remove_boosts_widening_audience.rb
M db/post_migrate/20210308133107_remove_subscription_expires_at_from_accounts.rb
M db/post_migrate/20220118183123_remove_rememberable_from_users.rb
M db/seeds/01_web_app.rb
M db/seeds/02_instance_actor.rb
M db/seeds/03_roles.rb
M db/seeds/04_admin.rb
M lib/active_record/batches.rb
M lib/mastodon/premailer_webpack_strategy.rb
M lib/mastodon/snowflake.rb
M lib/rails/engine_extensions.rb
M lib/tasks/branding.rake
M lib/tasks/repo.rake
M package.json
M spec/controllers/admin/domain_blocks_controller_spec.rb
M spec/controllers/api/base_controller_spec.rb
M spec/controllers/api/v1/media_controller_spec.rb
M spec/controllers/api/v1/reports_controller_spec.rb
M spec/controllers/auth/registrations_controller_spec.rb
M spec/controllers/disputes/appeals_controller_spec.rb
M spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
M spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb
M spec/fabricators_spec.rb
M spec/helpers/application_helper_spec.rb
M spec/lib/status_filter_spec.rb
M spec/lib/status_finder_spec.rb
M spec/lib/webfinger_resource_spec.rb
M spec/mailers/admin_mailer_spec.rb
M spec/mailers/notification_mailer_spec.rb
M spec/mailers/previews/admin_mailer_preview.rb
M spec/mailers/previews/notification_mailer_preview.rb
M spec/models/account_migration_spec.rb
M spec/models/account_spec.rb
M spec/models/relationship_filter_spec.rb
M spec/models/user_role_spec.rb
M spec/policies/account_moderation_note_policy_spec.rb
M spec/policies/account_policy_spec.rb
M spec/policies/backup_policy_spec.rb
M spec/policies/custom_emoji_policy_spec.rb
M spec/policies/domain_block_policy_spec.rb
M spec/policies/email_domain_block_policy_spec.rb
M spec/policies/instance_policy_spec.rb
M spec/policies/invite_policy_spec.rb
M spec/policies/relay_policy_spec.rb
M spec/policies/report_note_policy_spec.rb
M spec/policies/report_policy_spec.rb
M spec/policies/settings_policy_spec.rb
M spec/policies/tag_policy_spec.rb
M spec/policies/user_policy_spec.rb
M spec/services/activitypub/process_account_service_spec.rb
M spec/services/activitypub/process_collection_service_spec.rb
M spec/services/activitypub/process_status_update_service_spec.rb
M spec/services/post_status_service_spec.rb
M spec/services/report_service_spec.rb
M spec/services/search_service_spec.rb
M spec/services/unallow_domain_service_spec.rb
M spec/spec_helper.rb
M spec/validators/blacklisted_email_validator_spec.rb
M yarn.lock
A .github/workflows/bundler-audit.yml => .github/workflows/bundler-audit.yml +40 -0
@@ 0,0 1,40 @@
name: Bundler Audit
on:
  push:
    branches-ignore:
      - 'dependabot/**'
    paths:
      - 'Gemfile*'
      - '.ruby-version'
      - '.bundler-audit.yml'
      - '.github/workflows/bundler-audit.yml'

  pull_request:
    paths:
      - 'Gemfile*'
      - '.ruby-version'
      - '.bundler-audit.yml'
      - '.github/workflows/bundler-audit.yml'

  schedule:
    - cron: '0 5 * * 1'

jobs:
  security:
    runs-on: ubuntu-latest

    steps:
      - name: Clone repository
        uses: actions/checkout@v3

      - name: Install native Ruby dependencies
        run: sudo apt-get install -y libicu-dev libidn11-dev

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

      - name: Run bundler-audit
        run: bundle exec bundler-audit

M .github/workflows/lint-ruby.yml => .github/workflows/lint-ruby.yml +5 -4
@@ 8,7 8,7 @@ on:
      - 'Gemfile*'
      - '.rubocop*.yml'
      - '.ruby-version'
      - '.bundler-audit.yml'
      - 'config/brakeman.ignore'
      - '**/*.rb'
      - '**/*.rake'
      - '.github/workflows/lint-ruby.yml'


@@ 18,7 18,7 @@ on:
      - 'Gemfile*'
      - '.rubocop*.yml'
      - '.ruby-version'
      - '.bundler-audit.yml'
      - 'config/brakeman.ignore'
      - '**/*.rb'
      - '**/*.rake'
      - '.github/workflows/lint-ruby.yml'


@@ 46,5 46,6 @@ jobs:
      - name: Run rubocop
        run: bundle exec rubocop

      - name: Run bundler-audit
        run: bundle exec bundler-audit
      - name: Run brakeman
        if: always() # Run both checks, even if the first failed
        run: bundle exec brakeman

M .github/workflows/rebase-needed.yml => .github/workflows/rebase-needed.yml +2 -11
@@ 1,17 1,8 @@
name: PR Needs Rebase

on:
  push:
    branches-ignore:
      - 'dependabot/**'
      - 'renovate/**'
      - 'l10n_main'
  pull_request_target:
    branches-ignore:
      - 'dependabot/**'
      - 'renovate/**'
      - 'l10n_main'
    types: [synchronize]
  schedule:
    - cron: '0 * * * *'

permissions:
  pull-requests: write

M .haml-lint_todo.yml => .haml-lint_todo.yml +5 -55
@@ 1,73 1,23 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2023-03-15 00:55:01 -0400 using Haml-Lint version 0.45.0.
# on 2023-07-11 23:58:05 +0200 using Haml-Lint version 0.48.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.

linters:
  # Offense count: 63
  # Offense count: 94
  RuboCop:
    exclude:
      - 'app/views/accounts/_og.html.haml'
      - 'app/views/admin/account_warnings/_account_warning.html.haml'
      - 'app/views/admin/accounts/index.html.haml'
      - 'app/views/admin/accounts/show.html.haml'
      - 'app/views/admin/announcements/edit.html.haml'
      - 'app/views/admin/announcements/new.html.haml'
      - 'app/views/admin/disputes/appeals/_appeal.html.haml'
      - 'app/views/admin/domain_blocks/edit.html.haml'
      - 'app/views/admin/domain_blocks/new.html.haml'
      - 'app/views/admin/ip_blocks/new.html.haml'
      - 'app/views/admin/reports/actions/preview.html.haml'
      - 'app/views/admin/reports/index.html.haml'
      - 'app/views/admin/reports/show.html.haml'
      - 'app/views/admin/roles/_form.html.haml'
      - 'app/views/admin/settings/about/show.html.haml'
      - 'app/views/admin/settings/appearance/show.html.haml'
      - 'app/views/admin/settings/registrations/show.html.haml'
      - 'app/views/admin/statuses/show.html.haml'
      - 'app/views/auth/registrations/new.html.haml'
      - 'app/views/disputes/strikes/show.html.haml'
      - 'app/views/filters/_filter_fields.html.haml'
      - 'app/views/invites/_form.html.haml'
      - 'app/views/layouts/application.html.haml'
      - 'app/views/layouts/error.html.haml'
      - 'app/views/notification_mailer/_status.html.haml'
      - 'app/views/settings/applications/_fields.html.haml'
      - 'app/views/settings/imports/show.html.haml'
      - 'app/views/settings/preferences/appearance/show.html.haml'
      - 'app/views/settings/preferences/other/show.html.haml'
      - 'app/views/statuses/_detailed_status.html.haml'
      - 'app/views/statuses/_poll.html.haml'
      - 'app/views/statuses/show.html.haml'
      - 'app/views/statuses_cleanup/show.html.haml'
      - 'app/views/user_mailer/warning.html.haml'
    enabled: false

  # Offense count: 913
  # Offense count: 960
  LineLength:
    enabled: false

  # Offense count: 22
  UnnecessaryStringOutput:
    exclude:
      - 'app/views/accounts/show.html.haml'
      - 'app/views/admin/custom_emojis/_custom_emoji.html.haml'
      - 'app/views/admin/relays/_relay.html.haml'
      - 'app/views/admin/rules/_rule.html.haml'
      - 'app/views/admin/statuses/index.html.haml'
      - 'app/views/auth/registrations/_sessions.html.haml'
      - 'app/views/disputes/strikes/show.html.haml'
      - 'app/views/notification_mailer/_status.html.haml'
      - 'app/views/settings/two_factor_authentication_methods/index.html.haml'
      - 'app/views/statuses/_detailed_status.html.haml'
      - 'app/views/statuses/_poll.html.haml'
      - 'app/views/statuses/_simple_status.html.haml'
      - 'app/views/user_mailer/suspicious_sign_in.html.haml'
      - 'app/views/user_mailer/webauthn_credential_added.html.haml'
      - 'app/views/user_mailer/webauthn_credential_deleted.html.haml'
      - 'app/views/user_mailer/welcome.html.haml'
    enabled: false

  # Offense count: 3
  ViewLength:

M .rubocop.yml => .rubocop.yml +5 -1
@@ 24,7 24,6 @@ AllCops:
  Exclude:
    - db/schema.rb
    - 'bin/*'
    - 'Rakefile'
    - 'node_modules/**/*'
    - 'Vagrantfile'
    - 'vendor/**/*'


@@ 192,6 191,11 @@ Style/RedundantBegin:
Style/RescueStandardError:
  EnforcedStyle: implicit

# Reason: Simplify some spec layouts
# https://docs.rubocop.org/rubocop/cops_style.html#stylesemicolon
Style/Semicolon:
  AllowAsExpressionSeparator: true

# Reason: Originally disabled for CodeClimate, and no config consensus has been found
# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray
Style/SymbolArray:

M .rubocop_todo.yml => .rubocop_todo.yml +0 -492
@@ 48,15 48,6 @@ Layout/SpaceInLambdaLiteral:
    - 'config/environments/production.rb'
    - 'config/initializers/content_security_policy.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowedMethods, AllowedPatterns.
Lint/AmbiguousBlockAssociation:
  Exclude:
    - 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
    - 'spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb'
    - 'spec/services/activitypub/process_status_update_service_spec.rb'
    - 'spec/services/post_status_service_spec.rb'

# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
  Exclude:


@@ 107,11 98,6 @@ Lint/OrAssignmentToConstant:
    - 'lib/sanitize_ext/sanitize_config.rb'

# This cop supports safe autocorrection (--autocorrect).
Lint/SendWithMixinArgument:
  Exclude:
    - 'config/application.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
  Exclude:


@@ 167,10 153,6 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
  Max: 27

Naming/AccessorMethodName:
  Exclude:
    - 'app/controllers/auth/sessions_controller.rb'

# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
# CheckDefinitionPathHierarchyRoots: lib, spec, test, src
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS


@@ 178,19 160,6 @@ Naming/FileName:
  Exclude:
    - 'config/locales/sr-Latn.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
  Exclude:
    - 'app/controllers/api/v1/bookmarks_controller.rb'
    - 'app/controllers/api/v1/favourites_controller.rb'
    - 'app/controllers/concerns/rate_limit_headers.rb'
    - 'app/lib/activitypub/activity.rb'
    - 'app/services/resolve_url_service.rb'
    - 'app/services/search_service.rb'
    - 'config/initializers/rack_attack.rb'

# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, normalcase, non_integer
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64


@@ 400,45 369,6 @@ RSpec/PendingWithoutReason:
  Exclude:
    - 'spec/models/account_spec.rb'

RSpec/StubbedMock:
  Exclude:
    - 'spec/controllers/api/base_controller_spec.rb'
    - 'spec/controllers/api/v1/media_controller_spec.rb'
    - 'spec/controllers/auth/registrations_controller_spec.rb'
    - 'spec/helpers/application_helper_spec.rb'
    - 'spec/lib/status_filter_spec.rb'
    - 'spec/lib/status_finder_spec.rb'
    - 'spec/lib/webfinger_resource_spec.rb'
    - 'spec/services/activitypub/process_collection_service_spec.rb'

RSpec/SubjectDeclaration:
  Exclude:
    - 'spec/controllers/admin/domain_blocks_controller_spec.rb'
    - 'spec/models/account_migration_spec.rb'
    - 'spec/models/account_spec.rb'
    - 'spec/models/relationship_filter_spec.rb'
    - 'spec/models/user_role_spec.rb'
    - 'spec/policies/account_moderation_note_policy_spec.rb'
    - 'spec/policies/account_policy_spec.rb'
    - 'spec/policies/backup_policy_spec.rb'
    - 'spec/policies/custom_emoji_policy_spec.rb'
    - 'spec/policies/domain_block_policy_spec.rb'
    - 'spec/policies/email_domain_block_policy_spec.rb'
    - 'spec/policies/instance_policy_spec.rb'
    - 'spec/policies/invite_policy_spec.rb'
    - 'spec/policies/relay_policy_spec.rb'
    - 'spec/policies/report_note_policy_spec.rb'
    - 'spec/policies/report_policy_spec.rb'
    - 'spec/policies/settings_policy_spec.rb'
    - 'spec/policies/tag_policy_spec.rb'
    - 'spec/policies/user_policy_spec.rb'
    - 'spec/services/activitypub/process_account_service_spec.rb'

RSpec/SubjectStub:
  Exclude:
    - 'spec/services/unallow_domain_service_spec.rb'
    - 'spec/validators/blacklisted_email_validator_spec.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationController:
  Exclude:


@@ 780,406 710,6 @@ Style/FormatStringToken:
    - 'lib/paperclip/color_extractor.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Style/FrozenStringLiteralComment:
  Exclude:
    - 'app/views/accounts/show.rss.ruby'
    - 'app/views/tags/show.rss.ruby'
    - 'app/views/well_known/host_meta/show.xml.ruby'
    - 'config/application.rb'
    - 'config/boot.rb'
    - 'config/environment.rb'
    - 'config/environments/development.rb'
    - 'config/environments/production.rb'
    - 'config/environments/test.rb'
    - 'config/initializers/0_post_deployment_migrations.rb'
    - 'config/initializers/active_model_serializers.rb'
    - 'config/initializers/application_controller_renderer.rb'
    - 'config/initializers/assets.rb'
    - 'config/initializers/backtrace_silencers.rb'
    - 'config/initializers/cache_logging.rb'
    - 'config/initializers/chewy.rb'
    - 'config/initializers/content_security_policy.rb'
    - 'config/initializers/cookies_serializer.rb'
    - 'config/initializers/cors.rb'
    - 'config/initializers/devise.rb'
    - 'config/initializers/doorkeeper.rb'
    - 'config/initializers/fast_blank.rb'
    - 'config/initializers/ffmpeg.rb'
    - 'config/initializers/filter_parameter_logging.rb'
    - 'config/initializers/http_client_proxy.rb'
    - 'config/initializers/httplog.rb'
    - 'config/initializers/inflections.rb'
    - 'config/initializers/mail_delivery_job.rb'
    - 'config/initializers/makara.rb'
    - 'config/initializers/mime_types.rb'
    - 'config/initializers/oj.rb'
    - 'config/initializers/omniauth.rb'
    - 'config/initializers/open_uri_redirection.rb'
    - 'config/initializers/permissions_policy.rb'
    - 'config/initializers/pghero.rb'
    - 'config/initializers/preload_link_headers.rb'
    - 'config/initializers/premailer_rails.rb'
    - 'config/initializers/rack_attack_logging.rb'
    - 'config/initializers/redis.rb'
    - 'config/initializers/session_store.rb'
    - 'config/initializers/simple_form.rb'
    - 'config/initializers/stoplight.rb'
    - 'config/initializers/trusted_proxies.rb'
    - 'config/initializers/twitter_regex.rb'
    - 'config/initializers/webauthn.rb'
    - 'config/initializers/wrap_parameters.rb'
    - 'config/locales/sr-Latn.rb'
    - 'config/locales/sr.rb'
    - 'config/puma.rb'
    - 'db/migrate/20160220174730_create_accounts.rb'
    - 'db/migrate/20160220211917_create_statuses.rb'
    - 'db/migrate/20160221003140_create_users.rb'
    - 'db/migrate/20160221003621_create_follows.rb'
    - 'db/migrate/20160222122600_create_stream_entries.rb'
    - 'db/migrate/20160222143943_add_profile_fields_to_accounts.rb'
    - 'db/migrate/20160223162837_add_metadata_to_statuses.rb'
    - 'db/migrate/20160223164502_make_uris_nullable_in_statuses.rb'
    - 'db/migrate/20160223165723_add_url_to_statuses.rb'
    - 'db/migrate/20160223165855_add_url_to_accounts.rb'
    - 'db/migrate/20160223171800_create_favourites.rb'
    - 'db/migrate/20160224223247_create_mentions.rb'
    - 'db/migrate/20160227230233_add_attachment_avatar_to_accounts.rb'
    - 'db/migrate/20160305115639_add_devise_to_users.rb'
    - 'db/migrate/20160306172223_create_doorkeeper_tables.rb'
    - 'db/migrate/20160312193225_add_attachment_header_to_accounts.rb'
    - 'db/migrate/20160314164231_add_owner_to_application.rb'
    - 'db/migrate/20160316103650_add_missing_indices.rb'
    - 'db/migrate/20160322193748_add_avatar_remote_url_to_accounts.rb'
    - 'db/migrate/20160325130944_add_admin_to_users.rb'
    - 'db/migrate/20160826155805_add_superapp_to_oauth_applications.rb'
    - 'db/migrate/20160905150353_create_media_attachments.rb'
    - 'db/migrate/20160919221059_add_subscription_expires_at_to_accounts.rb'
    - 'db/migrate/20160920003904_remove_verify_token_from_accounts.rb'
    - 'db/migrate/20160926213048_remove_owner_from_application.rb'
    - 'db/migrate/20161003142332_add_confirmable_to_users.rb'
    - 'db/migrate/20161003145426_create_blocks.rb'
    - 'db/migrate/20161006213403_rails_settings_migration.rb'
    - 'db/migrate/20161009120834_create_domain_blocks.rb'
    - 'db/migrate/20161027172456_add_silenced_to_accounts.rb'
    - 'db/migrate/20161104173623_create_tags.rb'
    - 'db/migrate/20161105130633_create_statuses_tags_join_table.rb'
    - 'db/migrate/20161116162355_add_locale_to_users.rb'
    - 'db/migrate/20161119211120_create_notifications.rb'
    - 'db/migrate/20161122163057_remove_unneeded_indexes.rb'
    - 'db/migrate/20161123093447_add_sensitive_to_statuses.rb'
    - 'db/migrate/20161128103007_create_subscriptions.rb'
    - 'db/migrate/20161130142058_add_last_successful_delivery_at_to_subscriptions.rb'
    - 'db/migrate/20161130185319_add_visibility_to_statuses.rb'
    - 'db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb'
    - 'db/migrate/20161203164520_add_from_account_id_to_notifications.rb'
    - 'db/migrate/20161205214545_add_suspended_to_accounts.rb'
    - 'db/migrate/20161221152630_add_hidden_to_stream_entries.rb'
    - 'db/migrate/20161222201034_add_locked_to_accounts.rb'
    - 'db/migrate/20161222204147_create_follow_requests.rb'
    - 'db/migrate/20170105224407_add_shortcode_to_media_attachments.rb'
    - 'db/migrate/20170109120109_create_web_settings.rb'
    - 'db/migrate/20170112154826_migrate_settings.rb'
    - 'db/migrate/20170114194937_add_application_to_statuses.rb'
    - 'db/migrate/20170114203041_add_website_to_oauth_application.rb'
    - 'db/migrate/20170119214911_create_preview_cards.rb'
    - 'db/migrate/20170123162658_add_severity_to_domain_blocks.rb'
    - 'db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb'
    - 'db/migrate/20170125145934_add_spoiler_text_to_statuses.rb'
    - 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb'
    - 'db/migrate/20170205175257_remove_devices.rb'
    - 'db/migrate/20170209184350_add_reply_to_statuses.rb'
    - 'db/migrate/20170214110202_create_reports.rb'
    - 'db/migrate/20170217012631_add_reblog_of_id_foreign_key_to_statuses.rb'
    - 'db/migrate/20170301222600_create_mutes.rb'
    - 'db/migrate/20170303212857_add_last_emailed_at_to_users.rb'
    - 'db/migrate/20170304202101_add_type_to_media_attachments.rb'
    - 'db/migrate/20170317193015_add_search_index_to_accounts.rb'
    - 'db/migrate/20170318214217_add_header_remote_url_to_accounts.rb'
    - 'db/migrate/20170322021028_add_lowercase_index_to_accounts.rb'
    - 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb'
    - 'db/migrate/20170322162804_add_search_index_to_tags.rb'
    - 'db/migrate/20170330021336_add_counter_caches.rb'
    - 'db/migrate/20170330163835_create_imports.rb'
    - 'db/migrate/20170330164118_add_attachment_data_to_imports.rb'
    - 'db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb'
    - 'db/migrate/20170405112956_add_index_on_mentions_status_id.rb'
    - 'db/migrate/20170406215816_add_notifications_and_favourites_indices.rb'
    - 'db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb'
    - 'db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb'
    - 'db/migrate/20170414132105_add_language_to_statuses.rb'
    - 'db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb'
    - 'db/migrate/20170423005413_add_allowed_languages_to_user.rb'
    - 'db/migrate/20170424003227_create_account_domain_blocks.rb'
    - 'db/migrate/20170424112722_add_status_id_index_to_statuses_tags.rb'
    - 'db/migrate/20170425131920_add_media_attachment_meta.rb'
    - 'db/migrate/20170425202925_add_oembed_to_preview_cards.rb'
    - 'db/migrate/20170427011934_re_add_owner_to_application.rb'
    - 'db/migrate/20170506235850_create_conversations.rb'
    - 'db/migrate/20170507000211_add_conversation_id_to_statuses.rb'
    - 'db/migrate/20170507141759_optimize_index_subscriptions.rb'
    - 'db/migrate/20170508230434_create_conversation_mutes.rb'
    - 'db/migrate/20170516072309_add_index_accounts_on_uri.rb'
    - 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb'
    - 'db/migrate/20170601210557_add_index_on_media_attachments_account_id.rb'
    - 'db/migrate/20170604144747_add_foreign_keys_for_accounts.rb'
    - 'db/migrate/20170606113804_change_tag_search_index_to_btree.rb'
    - 'db/migrate/20170609145826_remove_default_language_from_statuses.rb'
    - 'db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb'
    - 'db/migrate/20170623152212_create_session_activations.rb'
    - 'db/migrate/20170624134742_add_description_to_session_activations.rb'
    - 'db/migrate/20170625140443_add_access_token_id_to_session_activations.rb'
    - 'db/migrate/20170711225116_fix_null_booleans.rb'
    - 'db/migrate/20170713112503_make_tag_search_case_insensitive.rb'
    - 'db/migrate/20170713175513_create_web_push_subscriptions.rb'
    - 'db/migrate/20170713190709_add_web_push_subscription_to_session_activations.rb'
    - 'db/migrate/20170714184731_add_domain_to_subscriptions.rb'
    - 'db/migrate/20170716191202_add_hide_notifications_to_mute.rb'
    - 'db/migrate/20170718211102_add_activitypub_to_accounts.rb'
    - 'db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb'
    - 'db/migrate/20170823162448_create_status_pins.rb'
    - 'db/migrate/20170824103029_add_timestamps_to_status_pins.rb'
    - 'db/migrate/20170829215220_remove_status_pins_account_index.rb'
    - 'db/migrate/20170901141119_truncate_preview_cards.rb'
    - 'db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb'
    - 'db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb'
    - 'db/migrate/20170905165803_add_local_to_statuses.rb'
    - 'db/migrate/20170913000752_create_site_uploads.rb'
    - 'db/migrate/20170917153509_create_custom_emojis.rb'
    - 'db/migrate/20170918125918_ids_to_bigints.rb'
    - 'db/migrate/20170920024819_status_ids_to_timestamp_ids.rb'
    - 'db/migrate/20170920032311_fix_reblogs_in_feeds.rb'
    - 'db/migrate/20170924022025_ids_to_bigints2.rb'
    - 'db/migrate/20170927215609_add_description_to_media_attachments.rb'
    - 'db/migrate/20170928082043_create_email_domain_blocks.rb'
    - 'db/migrate/20171005102658_create_account_moderation_notes.rb'
    - 'db/migrate/20171005171936_add_disabled_to_custom_emojis.rb'
    - 'db/migrate/20171006142024_add_uri_to_custom_emojis.rb'
    - 'db/migrate/20171010023049_add_foreign_key_to_account_moderation_notes.rb'
    - 'db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb'
    - 'db/migrate/20171020084748_add_visible_in_picker_to_custom_emoji.rb'
    - 'db/migrate/20171028221157_add_reblogs_to_follows.rb'
    - 'db/migrate/20171107143332_add_memorial_to_accounts.rb'
    - 'db/migrate/20171107143624_add_disabled_to_users.rb'
    - 'db/migrate/20171109012327_add_moderator_to_accounts.rb'
    - 'db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb'
    - 'db/migrate/20171114231651_create_lists.rb'
    - 'db/migrate/20171116161857_create_list_accounts.rb'
    - 'db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb'
    - 'db/migrate/20171119172437_create_admin_action_logs.rb'
    - 'db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb'
    - 'db/migrate/20171125024930_create_invites.rb'
    - 'db/migrate/20171125031751_add_invite_id_to_users.rb'
    - 'db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb'
    - 'db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb'
    - 'db/migrate/20171129172043_add_index_on_stream_entries.rb'
    - 'db/migrate/20171130000000_add_embed_url_to_preview_cards.rb'
    - 'db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb'
    - 'db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb'
    - 'db/migrate/20171226094803_more_faster_index_on_notifications.rb'
    - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
    - 'db/migrate/20180109143959_add_remember_token_to_users.rb'
    - 'db/migrate/20180204034416_create_identities.rb'
    - 'db/migrate/20180206000000_change_user_id_nonnullable.rb'
    - 'db/migrate/20180211015820_create_backups.rb'
    - 'db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb'
    - 'db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb'
    - 'db/migrate/20180402031200_add_assigned_account_id_to_reports.rb'
    - 'db/migrate/20180402040909_create_report_notes.rb'
    - 'db/migrate/20180410204633_add_fields_to_accounts.rb'
    - 'db/migrate/20180416210259_add_uri_to_relationships.rb'
    - 'db/migrate/20180506221944_add_actor_type_to_accounts.rb'
    - 'db/migrate/20180510214435_add_access_token_id_to_web_push_subscriptions.rb'
    - 'db/migrate/20180510230049_migrate_web_push_subscriptions.rb'
    - 'db/migrate/20180528141303_fix_accounts_unique_index.rb'
    - 'db/migrate/20180608213548_reject_following_blocked_users.rb'
    - 'db/migrate/20180609104432_migrate_web_push_subscriptions2.rb'
    - 'db/migrate/20180615122121_add_autofollow_to_invites.rb'
    - 'db/migrate/20180616192031_add_chosen_languages_to_users.rb'
    - 'db/migrate/20180617162849_remove_unused_indexes.rb'
    - 'db/migrate/20180628181026_create_custom_filters.rb'
    - 'db/migrate/20180707154237_add_whole_word_to_custom_filter.rb'
    - 'db/migrate/20180711152640_create_relays.rb'
    - 'db/migrate/20180808175627_create_account_pins.rb'
    - 'db/migrate/20180812123222_change_relays_enabled.rb'
    - 'db/migrate/20180812162710_create_status_stats.rb'
    - 'db/migrate/20180812173710_copy_status_stats.rb'
    - 'db/migrate/20180814171349_add_confidential_to_doorkeeper_application.rb'
    - 'db/migrate/20180831171112_create_bookmarks.rb'
    - 'db/migrate/20180929222014_create_account_conversations.rb'
    - 'db/migrate/20181007025445_create_pghero_space_stats.rb'
    - 'db/migrate/20181010141500_add_silent_to_mentions.rb'
    - 'db/migrate/20181017170937_add_reject_reports_to_domain_blocks.rb'
    - 'db/migrate/20181018205649_add_unread_to_account_conversations.rb'
    - 'db/migrate/20181024224956_migrate_account_conversations.rb'
    - 'db/migrate/20181026034033_remove_faux_remote_account_duplicates.rb'
    - 'db/migrate/20181116165755_create_account_stats.rb'
    - 'db/migrate/20181116173541_copy_account_stats.rb'
    - 'db/migrate/20181127130500_identity_id_to_bigint.rb'
    - 'db/migrate/20181127165847_add_show_replies_to_lists.rb'
    - 'db/migrate/20181203003808_create_accounts_tags_join_table.rb'
    - 'db/migrate/20181203021853_add_discoverable_to_accounts.rb'
    - 'db/migrate/20181204193439_add_last_status_at_to_account_stats.rb'
    - 'db/migrate/20181204215309_create_account_tag_stats.rb'
    - 'db/migrate/20181207011115_downcase_custom_emoji_domains.rb'
    - 'db/migrate/20181213184704_create_account_warnings.rb'
    - 'db/migrate/20181213185533_create_account_warning_presets.rb'
    - 'db/migrate/20181219235220_add_created_by_application_id_to_users.rb'
    - 'db/migrate/20181226021420_add_also_known_as_to_accounts.rb'
    - 'db/migrate/20190103124649_create_scheduled_statuses.rb'
    - 'db/migrate/20190103124754_add_scheduled_status_id_to_media_attachments.rb'
    - 'db/migrate/20190117114553_create_tombstones.rb'
    - 'db/migrate/20190201012802_add_overwrite_to_imports.rb'
    - 'db/migrate/20190203180359_create_featured_tags.rb'
    - 'db/migrate/20190225031541_create_polls.rb'
    - 'db/migrate/20190225031625_create_poll_votes.rb'
    - 'db/migrate/20190226003449_add_poll_id_to_statuses.rb'
    - 'db/migrate/20190304152020_add_uri_to_poll_votes.rb'
    - 'db/migrate/20190306145741_add_lock_version_to_polls.rb'
    - 'db/migrate/20190307234537_add_approved_to_users.rb'
    - 'db/migrate/20190314181829_migrate_open_registrations_setting.rb'
    - 'db/migrate/20190316190352_create_account_identity_proofs.rb'
    - 'db/migrate/20190317135723_add_uri_to_reports.rb'
    - 'db/migrate/20190403141604_add_comment_to_invites.rb'
    - 'db/migrate/20190409054914_create_user_invite_requests.rb'
    - 'db/migrate/20190420025523_add_blurhash_to_media_attachments.rb'
    - 'db/migrate/20190509164208_add_by_moderator_to_tombstone.rb'
    - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
    - 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
    - 'db/migrate/20190627222225_create_custom_emoji_categories.rb'
    - 'db/migrate/20190627222826_add_category_id_to_custom_emojis.rb'
    - 'db/migrate/20190701022101_add_trust_level_to_accounts.rb'
    - 'db/migrate/20190705002136_create_domain_allows.rb'
    - 'db/migrate/20190715164535_add_instance_actor.rb'
    - 'db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb'
    - 'db/migrate/20190729185330_add_score_to_tags.rb'
    - 'db/migrate/20190805123746_add_capabilities_to_tags.rb'
    - 'db/migrate/20190807135426_add_comments_to_domain_blocks.rb'
    - 'db/migrate/20190815225426_add_last_status_at_to_tags.rb'
    - 'db/migrate/20190819134503_add_deleted_at_to_statuses.rb'
    - 'db/migrate/20190820003045_update_statuses_index.rb'
    - 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
    - 'db/migrate/20190901035623_add_max_score_to_tags.rb'
    - 'db/migrate/20190904222339_create_markers.rb'
    - 'db/migrate/20190914202517_create_account_migrations.rb'
    - 'db/migrate/20190915194355_create_account_aliases.rb'
    - 'db/migrate/20190927232842_add_voters_count_to_polls.rb'
    - 'db/migrate/20191001213028_add_lock_version_to_account_stats.rb'
    - 'db/migrate/20191007013357_update_pt_locales.rb'
    - 'db/migrate/20191031163205_change_list_account_follow_nullable.rb'
    - 'db/migrate/20191212003415_increase_backup_size.rb'
    - 'db/migrate/20191212163405_add_hide_collections_to_accounts.rb'
    - 'db/migrate/20191218153258_create_announcements.rb'
    - 'db/migrate/20200113125135_create_announcement_mutes.rb'
    - 'db/migrate/20200114113335_create_announcement_reactions.rb'
    - 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
    - 'db/migrate/20200126203551_add_published_at_to_announcements.rb'
    - 'db/migrate/20200306035625_add_processing_to_media_attachments.rb'
    - 'db/migrate/20200309150742_add_forwarded_to_reports.rb'
    - 'db/migrate/20200312144258_add_title_to_account_warning_presets.rb'
    - 'db/migrate/20200312162302_add_status_ids_to_announcements.rb'
    - 'db/migrate/20200312185443_add_parent_id_to_email_domain_blocks.rb'
    - 'db/migrate/20200317021758_add_expires_at_to_mutes.rb'
    - 'db/migrate/20200407201300_create_unavailable_domains.rb'
    - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb'
    - 'db/migrate/20200417125749_add_storage_schema_version.rb'
    - 'db/migrate/20200508212852_reset_unique_jobs_locks.rb'
    - 'db/migrate/20200510110808_reset_web_app_secret.rb'
    - 'db/migrate/20200510181721_remove_duplicated_indexes_pghero.rb'
    - 'db/migrate/20200516180352_create_devices.rb'
    - 'db/migrate/20200516183822_create_one_time_keys.rb'
    - 'db/migrate/20200518083523_create_encrypted_messages.rb'
    - 'db/migrate/20200521180606_encrypted_message_ids_to_timestamp_ids.rb'
    - 'db/migrate/20200529214050_add_devices_url_to_accounts.rb'
    - 'db/migrate/20200601222558_create_system_keys.rb'
    - 'db/migrate/20200605155027_add_blurhash_to_preview_cards.rb'
    - 'db/migrate/20200608113046_add_sign_in_token_to_users.rb'
    - 'db/migrate/20200614002136_add_sensitized_to_accounts.rb'
    - 'db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb'
    - 'db/migrate/20200622213645_media_attachment_ids_to_timestamp_ids.rb'
    - 'db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb'
    - 'db/migrate/20200628133322_create_account_notes.rb'
    - 'db/migrate/20200630190240_create_webauthn_credentials.rb'
    - 'db/migrate/20200630190544_add_webauthn_id_to_users.rb'
    - 'db/migrate/20200908193330_create_account_deletion_requests.rb'
    - 'db/migrate/20200917192924_add_notify_to_follows.rb'
    - 'db/migrate/20200917193034_add_type_to_notifications.rb'
    - 'db/migrate/20200917222316_add_index_notifications_on_type.rb'
    - 'db/migrate/20201008202037_create_ip_blocks.rb'
    - 'db/migrate/20201008220312_add_sign_up_ip_to_users.rb'
    - 'db/migrate/20201017233919_add_suspension_origin_to_accounts.rb'
    - 'db/migrate/20201206004238_create_instances.rb'
    - 'db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb'
    - 'db/migrate/20210221045109_create_rules.rb'
    - 'db/migrate/20210306164523_account_ids_to_timestamp_ids.rb'
    - 'db/migrate/20210322164601_create_account_summaries.rb'
    - 'db/migrate/20210323114347_create_follow_recommendations.rb'
    - 'db/migrate/20210324171613_create_follow_recommendation_suppressions.rb'
    - 'db/migrate/20210416200740_create_canonical_email_blocks.rb'
    - 'db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb'
    - 'db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb'
    - 'db/migrate/20210505174616_update_follow_recommendations_to_version_2.rb'
    - 'db/migrate/20210609202149_create_login_activities.rb'
    - 'db/migrate/20210616214526_create_user_ips.rb'
    - 'db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb'
    - 'db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb'
    - 'db/migrate/20210722120340_create_account_statuses_cleanup_policies.rb'
    - 'db/migrate/20210904215403_add_edited_at_to_statuses.rb'
    - 'db/migrate/20210908220918_create_status_edits.rb'
    - 'db/migrate/20211031031021_create_preview_card_providers.rb'
    - 'db/migrate/20211112011713_add_language_to_preview_cards.rb'
    - 'db/migrate/20211115032527_add_trendable_to_preview_cards.rb'
    - 'db/migrate/20211123212714_add_link_type_to_preview_cards.rb'
    - 'db/migrate/20211213040746_update_account_summaries_to_version_2.rb'
    - 'db/migrate/20211231080958_add_category_to_reports.rb'
    - 'db/migrate/20220105163928_remove_mentions_status_id_index.rb'
    - 'db/migrate/20220115125126_add_report_id_to_account_warnings.rb'
    - 'db/migrate/20220115125341_fix_account_warning_actions.rb'
    - 'db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb'
    - 'db/migrate/20220124141035_create_appeals.rb'
    - 'db/migrate/20220202200743_add_trendable_to_accounts.rb'
    - 'db/migrate/20220202200926_add_trendable_to_statuses.rb'
    - 'db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb'
    - 'db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb'
    - 'db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb'
    - 'db/migrate/20220302232632_add_ordered_media_attachment_ids_to_statuses.rb'
    - 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb'
    - 'db/migrate/20220304195405_migrate_hide_network_preference.rb'
    - 'db/migrate/20220307094650_fix_featured_tags_constraints.rb'
    - 'db/migrate/20220309213005_fix_reblog_deleted_at.rb'
    - 'db/migrate/20220316233212_update_kurdish_locales.rb'
    - 'db/migrate/20220428112511_add_index_statuses_on_account_id.rb'
    - 'db/migrate/20220428112727_add_index_statuses_pins_on_status_id.rb'
    - 'db/migrate/20220428114454_add_index_reports_on_assigned_account_id.rb'
    - 'db/migrate/20220428114902_add_index_reports_on_action_taken_by_account_id.rb'
    - 'db/migrate/20220606044941_create_webhooks.rb'
    - 'db/migrate/20220611210335_create_user_roles.rb'
    - 'db/migrate/20220611212541_add_role_id_to_users.rb'
    - 'db/migrate/20220710102457_add_display_name_to_tags.rb'
    - 'db/migrate/20220714171049_create_tag_follows.rb'
    - 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb'
    - 'db/migrate/20220824233535_create_status_trends.rb'
    - 'db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb'
    - 'db/migrate/20220829192633_add_languages_to_follows.rb'
    - 'db/migrate/20220829192658_add_languages_to_follow_requests.rb'
    - 'db/migrate/20221006061337_create_preview_card_trends.rb'
    - 'db/migrate/20221012181003_add_blurhash_to_site_uploads.rb'
    - 'db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb'
    - 'db/migrate/20221025171544_add_index_ip_blocks_on_ip.rb'
    - 'db/migrate/20221104133904_add_name_to_featured_tags.rb'
    - 'db/post_migrate/20190519130537_remove_boosts_widening_audience.rb'
    - 'db/post_migrate/20210308133107_remove_subscription_expires_at_from_accounts.rb'
    - 'db/post_migrate/20220118183123_remove_rememberable_from_users.rb'
    - 'db/seeds/01_web_app.rb'
    - 'db/seeds/02_instance_actor.rb'
    - 'db/seeds/03_roles.rb'
    - 'db/seeds/04_admin.rb'
    - 'lib/rails/engine_extensions.rb'
    - 'lib/tasks/branding.rake'
    - 'spec/fabricators_spec.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Style/GlobalStdStream:
  Exclude:
    - 'config/boot.rb'


@@ 1341,13 871,6 @@ Style/SafeNavigation:
    - 'app/models/status.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
  Exclude:
    - 'spec/services/activitypub/process_status_update_service_spec.rb'
    - 'spec/validators/blacklisted_email_validator_spec.rb'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: only_raise, only_fail, semantic
Style/SignalException:


@@ 1360,21 883,6 @@ Style/SingleArgumentDig:
  Exclude:
    - 'lib/webpacker/manifest_extensions.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Style/SlicingWithRange:
  Exclude:
    - 'app/lib/emoji_formatter.rb'
    - 'app/lib/text_formatter.rb'
    - 'app/models/account_alias.rb'
    - 'app/models/domain_block.rb'
    - 'app/models/email_domain_block.rb'
    - 'app/models/preview_card_provider.rb'
    - 'app/validators/status_length_validator.rb'
    - 'db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb'
    - 'lib/active_record/batches.rb'
    - 'lib/mastodon/premailer_webpack_strategy.rb'
    - 'lib/tasks/repo.rake'

# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_parentheses, require_no_parentheses

M CHANGELOG.md => CHANGELOG.md +1 -1
@@ 143,7 143,7 @@ All notable changes to this project will be documented in this file.
- Add instance activity API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22833))
- Add setting for status page URL ([Gargron](https://github.com/mastodon/mastodon/pull/23390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23499))
  - REST API changes:
    - Add `configuration.urls.status` attribute to the object returned by `GET /api/v1/instance`
    - Add `configuration.urls.status` attribute to the object returned by `GET /api/v2/instance`
- Add `account.approved` webhook ([Saiv46](https://github.com/mastodon/mastodon/pull/22938))
- Add 12 hours option to polls ([Pleclown](https://github.com/mastodon/mastodon/pull/21131))
- Add dropdown menu item to open admin interface for remote domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21895))

M Gemfile => Gemfile +1 -2
@@ 11,7 11,6 @@ gem 'rack', '~> 2.2.7'

gem 'haml-rails', '~>2.0'
gem 'pg', '~> 1.5'
gem 'makara', '~> 0.5'
gem 'pghero'
gem 'dotenv-rails', '~> 2.8'



@@ 159,7 158,7 @@ group :development do
  gem 'letter_opener_web', '~> 2.0'

  # Security analysis CLI tools
  gem 'brakeman', '~> 5.4', require: false
  gem 'brakeman', '~> 6.0', require: false
  gem 'bundler-audit', '~> 0.9', require: false

  # Linter CLI for HAML files

M Gemfile.lock => Gemfile.lock +6 -9
@@ 130,7 130,7 @@ GEM
    blurhash (0.1.7)
    bootsnap (1.16.0)
      msgpack (~> 1.2)
    brakeman (5.4.1)
    brakeman (6.0.0)
    browser (5.3.1)
    brpoplpush-redis_script (0.1.3)
      concurrent-ruby (~> 1.0, >= 1.0.5)


@@ 146,7 146,7 @@ GEM
      sshkit (>= 1.9.0)
    capistrano-bundler (2.1.0)
      capistrano (~> 3.1)
    capistrano-rails (1.6.2)
    capistrano-rails (1.6.3)
      capistrano (~> 3.1)
      capistrano-bundler (>= 1.1, < 3)
    capistrano-rbenv (2.2.0)


@@ 291,11 291,11 @@ GEM
      activesupport (>= 5.1)
      haml (>= 4.0.6)
      railties (>= 5.1)
    haml_lint (0.45.0)
    haml_lint (0.48.0)
      haml (>= 4.0, < 6.2)
      parallel (~> 1.10)
      rainbow
      rubocop (>= 0.50.0)
      rubocop (>= 1.0)
      sysexits (~> 1.1)
    hashdiff (1.0.1)
    hashie (5.0.0)


@@ 399,8 399,6 @@ GEM
      net-imap
      net-pop
      net-smtp
    makara (0.5.1)
      activerecord (>= 5.2.0)
    marcel (1.0.2)
    mario-redis-lock (1.2.1)
      redis (>= 3.0.5)


@@ 670,7 668,7 @@ GEM
      actionpack (>= 5.2)
      activesupport (>= 5.2)
      sprockets (>= 3.0.0)
    sshkit (1.21.4)
    sshkit (1.21.5)
      net-scp (>= 1.1.2)
      net-ssh (>= 2.8.0)
    stackprof (0.2.25)


@@ 767,7 765,7 @@ DEPENDENCIES
  binding_of_caller (~> 1.0)
  blurhash (~> 0.1)
  bootsnap (~> 1.16.0)
  brakeman (~> 5.4)
  brakeman (~> 6.0)
  browser
  bundler-audit (~> 0.9)
  capistrano (~> 3.17)


@@ 815,7 813,6 @@ DEPENDENCIES
  letter_opener_web (~> 2.0)
  link_header (~> 0.0)
  lograge (~> 0.12)
  makara (~> 0.5)
  mario-redis-lock (~> 1.2)
  memory_profiler
  mime-types (~> 3.4.1)

M Rakefile => Rakefile +3 -1
@@ 1,6 1,8 @@
# frozen_string_literal: true

# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require File.expand_path('../config/application', __FILE__)
require File.expand_path('config/application', __dir__)

Rails.application.load_tasks

M app/controllers/api/v1/bookmarks_controller.rb => app/controllers/api/v1/bookmarks_controller.rb +1 -1
@@ 21,7 21,7 @@ class Api::V1::BookmarksController < Api::BaseController
  end

  def results
    @_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id(
    @results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id(
      limit_param(DEFAULT_STATUSES_LIMIT),
      params_slice(:max_id, :since_id, :min_id)
    )

M app/controllers/api/v1/favourites_controller.rb => app/controllers/api/v1/favourites_controller.rb +1 -1
@@ 21,7 21,7 @@ class Api::V1::FavouritesController < Api::BaseController
  end

  def results
    @_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id(
    @results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id(
      limit_param(DEFAULT_STATUSES_LIMIT),
      params_slice(:max_id, :since_id, :min_id)
    )

M app/controllers/api/v1/reports_controller.rb => app/controllers/api/v1/reports_controller.rb +1 -1
@@ 23,6 23,6 @@ class Api::V1::ReportsController < Api::BaseController
  end

  def report_params
    params.permit(:account_id, :comment, :category, :forward, status_ids: [], rule_ids: [])
    params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
  end
end

M app/controllers/api/v1/timelines/home_controller.rb => app/controllers/api/v1/timelines/home_controller.rb +5 -2
@@ 6,11 6,14 @@ class Api::V1::Timelines::HomeController < Api::BaseController
  after_action :insert_pagination_headers, unless: -> { @statuses.empty? }

  def show
    @statuses = load_statuses
    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
      @statuses = load_statuses
      @relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
    end

    render json: @statuses,
           each_serializer: REST::StatusSerializer,
           relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
           relationships: @relationships,
           status: account_home_feed.regenerating? ? 206 : 200
  end


M app/controllers/auth/sessions_controller.rb => app/controllers/auth/sessions_controller.rb +1 -1
@@ 129,7 129,7 @@ class Auth::SessionsController < Devise::SessionsController
    redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout')
  end

  def set_attempt_session(user)
  def register_attempt_in_session(user)
    session[:attempt_user_id]         = user.id
    session[:attempt_user_updated_at] = user.updated_at.to_s
  end

M app/controllers/concerns/rate_limit_headers.rb => app/controllers/concerns/rate_limit_headers.rb +1 -1
@@ 61,7 61,7 @@ module RateLimitHeaders
  end

  def request_time
    @_request_time ||= Time.now.utc
    @request_time ||= Time.now.utc
  end

  def reset_period_offset

M app/controllers/concerns/two_factor_authentication_concern.rb => app/controllers/concerns/two_factor_authentication_concern.rb +1 -1
@@ 75,7 75,7 @@ module TwoFactorAuthenticationConcern
  end

  def prompt_for_two_factor(user)
    set_attempt_session(user)
    register_attempt_in_session(user)

    use_pack 'auth'


M app/helpers/accounts_helper.rb => app/helpers/accounts_helper.rb +1 -1
@@ 22,7 22,7 @@ module AccountsHelper
  def account_action_button(account)
    return if account.memorial? || account.moved?

    link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
    link_to ActivityPub::TagManager.instance.url_for(account), class: 'button', target: '_new' do
      safe_join([logo_as_symbol, t('accounts.follow')])
    end
  end

M app/javascript/flavours/glitch/components/animated_number.tsx => app/javascript/flavours/glitch/components/animated_number.tsx +1 -1
@@ 5,7 5,7 @@ import { TransitionMotion, spring } from 'react-motion';

import { reduceMotion } from '../initial_state';

import ShortNumber from './short_number';
import { ShortNumber } from './short_number';

const obfuscatedCount = (count: number) => {
  if (count < 0) {

M app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx => app/javascript/flavours/glitch/components/autosuggest_hashtag.tsx +1 -1
@@ 1,6 1,6 @@
import { FormattedMessage } from 'react-intl';

import ShortNumber from 'flavours/glitch/components/short_number';
import { ShortNumber } from 'flavours/glitch/components/short_number';

interface Props {
  tag: {

R app/javascript/flavours/glitch/components/common_counter.jsx => app/javascript/flavours/glitch/components/counters.tsx +43 -57
@@ 1,59 1,45 @@
// @ts-check
import React from 'react';

import { FormattedMessage } from 'react-intl';
/**
 * Returns custom renderer for one of the common counter types
 * @param {"statuses" | "following" | "followers"} counterType
 * Type of the counter
 * @param {boolean} isBold Whether display number must be displayed in bold
 * @returns {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
 * Renderer function
 * @throws If counterType is not covered by this function
 */
export function counterRenderer(counterType, isBold = true) {
  /**
   * @type {(displayNumber: JSX.Element) => JSX.Element}
   */
  const renderCounter = isBold
    ? (displayNumber) => <strong>{displayNumber}</strong>
    : (displayNumber) => displayNumber;

  switch (counterType) {
  case 'statuses': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.statuses_counter'
        defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'following': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.following_counter'
        defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'followers': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.followers_counter'
        defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  default: throw Error(`Incorrect counter name: ${counterType}. Ensure it accepted by commonCounter function`);
  }
}
export const StatusesCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.statuses_counter'
    defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

export const FollowingCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.following_counter'
    defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

export const FollowersCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.followers_counter'
    defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

D app/javascript/flavours/glitch/components/dismissable_banner.jsx => app/javascript/flavours/glitch/components/dismissable_banner.jsx +0 -55
@@ 1,55 0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { injectIntl, defineMessages } from 'react-intl';

import { bannerSettings } from 'flavours/glitch/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

class DismissableBanner extends PureComponent {

  static propTypes = {
    id: PropTypes.string.isRequired,
    children: PropTypes.node,
    intl: PropTypes.object.isRequired,
  };

  state = {
    visible: !bannerSettings.get(this.props.id),
  };

  handleDismiss = () => {
    const { id } = this.props;
    this.setState({ visible: false }, () => bannerSettings.set(id, true));
  };

  render () {
    const { visible } = this.state;

    if (!visible) {
      return null;
    }

    const { children, intl } = this.props;

    return (
      <div className='dismissable-banner'>
        <div className='dismissable-banner__message'>
          {children}
        </div>

        <div className='dismissable-banner__action'>
          <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} />
        </div>
      </div>
    );
  }

}

export default injectIntl(DismissableBanner);

A app/javascript/flavours/glitch/components/dismissable_banner.tsx => app/javascript/flavours/glitch/components/dismissable_banner.tsx +47 -0
@@ 0,0 1,47 @@
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { bannerSettings } from 'flavours/glitch/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

interface Props {
  id: string;
}

export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
  id,
  children,
}) => {
  const [visible, setVisible] = useState(!bannerSettings.get(id));
  const intl = useIntl();

  const handleDismiss = useCallback(() => {
    setVisible(false);
    bannerSettings.set(id, true);
  }, [id]);

  if (!visible) {
    return null;
  }

  return (
    <div className='dismissable-banner'>
      <div className='dismissable-banner__message'>{children}</div>

      <div className='dismissable-banner__action'>
        <IconButton
          icon='times'
          title={intl.formatMessage(messages.dismiss)}
          onClick={handleDismiss}
        />
      </div>
    </div>
  );
};

M app/javascript/flavours/glitch/components/hashtag.jsx => app/javascript/flavours/glitch/components/hashtag.jsx +1 -1
@@ 10,7 10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';

import { Sparklines, SparklinesCurve } from 'react-sparklines';

import ShortNumber from 'flavours/glitch/components/short_number';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';

import Permalink from './permalink';

M app/javascript/flavours/glitch/components/media_gallery.jsx => app/javascript/flavours/glitch/components/media_gallery.jsx +8 -2
@@ 354,7 354,10 @@ class MediaGallery extends PureComponent {
    if (uncached) {
      spoilerButton = (
        <button type='button' disabled className='spoiler-button__overlay'>
          <span className='spoiler-button__overlay__label'><FormattedMessage id='status.uncached_media_warning' defaultMessage='Not available' /></span>
          <span className='spoiler-button__overlay__label'>
            <FormattedMessage id='status.uncached_media_warning' defaultMessage='Preview not available' />
            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.open' defaultMessage='Click to open' /></span>
          </span>
        </button>
      );
    } else if (visible) {


@@ 362,7 365,10 @@ class MediaGallery extends PureComponent {
    } else {
      spoilerButton = (
        <button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>
          <span className='spoiler-button__overlay__label'>{sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}</span>
          <span className='spoiler-button__overlay__label'>
            {sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}
            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
          </span>
        </button>
      );
    }

M app/javascript/flavours/glitch/components/server_banner.jsx => app/javascript/flavours/glitch/components/server_banner.jsx +1 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';

import { fetchServer } from 'flavours/glitch/actions/server';
import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image';
import ShortNumber from 'flavours/glitch/components/short_number';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import Account from 'flavours/glitch/containers/account_container';
import { domain } from 'flavours/glitch/initial_state';

R app/javascript/flavours/glitch/components/short_number.jsx => app/javascript/flavours/glitch/components/short_number.tsx +59 -83
@@ 1,68 1,49 @@
import PropTypes from 'prop-types';
import { memo } from 'react';

import { FormattedMessage, FormattedNumber } from 'react-intl';

import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers';
// @ts-check

/**
 * @callback ShortNumberRenderer
 * @param {JSX.Element} displayNumber Number to display
 * @param {number} pluralReady Number used for pluralization
 * @returns {JSX.Element} Final render of number
 */
type ShortNumberRenderer = (
  displayNumber: JSX.Element,
  pluralReady: number
) => JSX.Element;

/**
 * @typedef {object} ShortNumberProps
 * @property {number} value Number to display in short variant
 * @property {ShortNumberRenderer} [renderer]
 * Custom renderer for numbers, provided as a prop. If another renderer
 * passed as a child of this component, this prop won't be used.
 * @property {ShortNumberRenderer} [children]
 * Custom renderer for numbers, provided as a child. If another renderer
 * passed as a prop of this component, this one will be used instead.
 */
interface ShortNumberProps {
  value: number;
  renderer?: ShortNumberRenderer;
  children?: ShortNumberRenderer;
}

/**
 * Component that renders short big number to a shorter version
 * @param {ShortNumberProps} param0 Props for the component
 * @returns {JSX.Element} Rendered number
 */
function ShortNumber({ value, renderer, children }) {
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
  value,
  renderer,
  children,
}) => {
  const shortNumber = toShortNumber(value);
  const [, division] = shortNumber;

  if (children != null && renderer != null) {
    console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
  if (children && renderer) {
    console.warn(
      'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.'
    );
  }

  const customRenderer = children != null ? children : renderer;
  const customRenderer = children || renderer || null;

  const displayNumber = <ShortNumberCounter value={shortNumber} />;

  return customRenderer != null
    ? customRenderer(displayNumber, pluralReady(value, division))
    : displayNumber;
}

ShortNumber.propTypes = {
  value: PropTypes.number.isRequired,
  renderer: PropTypes.func,
  children: PropTypes.func,
  return (
    customRenderer?.(displayNumber, pluralReady(value, division)) ||
    displayNumber
  );
};
export const ShortNumber = memo(ShortNumberRenderer);

/**
 * @typedef {object} ShortNumberCounterProps
 * @property {import('../utils/number').ShortNumber} value Short number
 */

/**
 * Renders short number into corresponding localizable react fragment
 * @param {ShortNumberCounterProps} param0 Props for the component
 * @returns {JSX.Element} FormattedMessage ready to be embedded in code
 */
function ShortNumberCounter({ value }) {
interface ShortNumberCounterProps {
  value: number[];
}
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
  const [rawNumber, unit, maxFractionDigits = 0] = value;

  const count = (


@@ 72,43 53,38 @@ function ShortNumberCounter({ value }) {
    />
  );

  let values = { count, rawNumber };
  const values = { count, rawNumber };

  switch (unit) {
  case DECIMAL_UNITS.THOUSAND: {
    return (
      <FormattedMessage
        id='units.short.thousand'
        defaultMessage='{count}K'
        values={values}
      />
    );
    case DECIMAL_UNITS.THOUSAND: {
      return (
        <FormattedMessage
          id='units.short.thousand'
          defaultMessage='{count}K'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.MILLION: {
      return (
        <FormattedMessage
          id='units.short.million'
          defaultMessage='{count}M'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.BILLION: {
      return (
        <FormattedMessage
          id='units.short.billion'
          defaultMessage='{count}B'
          values={values}
        />
      );
    }
    // Not sure if we should go farther - @Sasha-Sorokin
    default:
      return count;
  }
  case DECIMAL_UNITS.MILLION: {
    return (
      <FormattedMessage
        id='units.short.million'
        defaultMessage='{count}M'
        values={values}
      />
    );
  }
  case DECIMAL_UNITS.BILLION: {
    return (
      <FormattedMessage
        id='units.short.billion'
        defaultMessage='{count}B'
        values={values}
      />
    );
  }
  // Not sure if we should go farther - @Sasha-Sorokin
  default: return count;
  }
}

ShortNumberCounter.propTypes = {
  value: PropTypes.arrayOf(PropTypes.number),
};

export default memo(ShortNumber);

M app/javascript/flavours/glitch/components/status_content.jsx => app/javascript/flavours/glitch/components/status_content.jsx +1 -1
@@ 97,7 97,7 @@ class TranslateButton extends PureComponent {
    }

    return (
      <button className='status__content__read-more-button' onClick={onClick}>
      <button className='status__content__translate-button' onClick={onClick}>
        <FormattedMessage id='status.translate' defaultMessage='Translate' />
      </button>
    );

M app/javascript/flavours/glitch/features/account/components/header.jsx => app/javascript/flavours/glitch/features/account/components/header.jsx +4 -4
@@ 196,14 196,14 @@ class Header extends ImmutablePureComponent {
      if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
        actionBtn = '';
      } else if (account.getIn(['relationship', 'requested'])) {
        actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
        actionBtn = <Button className={classNames({ 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
      } else if (!account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
        actionBtn = <Button className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
      } else if (account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
        actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
      }
    } else if (profileLink) {
      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
      actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
    }

    if (account.get('moved') && !account.getIn(['relationship', 'following'])) {

M app/javascript/flavours/glitch/features/community_timeline/index.jsx => app/javascript/flavours/glitch/features/community_timeline/index.jsx +1 -1
@@ 12,7 12,7 @@ import { connectCommunityStream } from 'flavours/glitch/actions/streaming';
import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import { domain } from 'flavours/glitch/initial_state';


M app/javascript/flavours/glitch/features/directory/components/account_card.jsx => app/javascript/flavours/glitch/features/directory/components/account_card.jsx +6 -6
@@ 20,7 20,7 @@ import Button from 'flavours/glitch/components/button';
import { DisplayName } from 'flavours/glitch/components/display_name';
import { IconButton } from 'flavours/glitch/components/icon_button';
import Permalink from 'flavours/glitch/components/permalink';
import ShortNumber from 'flavours/glitch/components/short_number';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state';
import { makeGetAccount } from 'flavours/glitch/selectors';



@@ 171,16 171,16 @@ class AccountCard extends ImmutablePureComponent {
      if (!account.get('relationship')) { // Wait until the relationship is loaded
        actionBtn = '';
      } else if (account.getIn(['relationship', 'requested'])) {
        actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
        actionBtn = <Button  text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
      } else if (account.getIn(['relationship', 'muting'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
        actionBtn = <Button  text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
      } else if (!account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
      } else if (account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
        actionBtn = <Button  text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
      }
    } else {
      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
      actionBtn = <Button  text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
    }

    return (

M app/javascript/flavours/glitch/features/explore/components/story.jsx => app/javascript/flavours/glitch/features/explore/components/story.jsx +1 -1
@@ 5,7 5,7 @@ import classNames from 'classnames';

import { Blurhash } from 'flavours/glitch/components/blurhash';
import { accountsCountRenderer } from 'flavours/glitch/components/hashtag';
import ShortNumber from 'flavours/glitch/components/short_number';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';



M app/javascript/flavours/glitch/features/explore/index.jsx => app/javascript/flavours/glitch/features/explore/index.jsx +2 -2
@@ 11,7 11,7 @@ import { connect } from 'react-redux';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import Search from 'flavours/glitch/features/compose/containers/search_container';
import { showTrends } from 'flavours/glitch/initial_state';
import { trendsEnabled } from 'flavours/glitch/initial_state';

import Links from './links';
import SearchResults from './results';


@@ 28,7 28,7 @@ const messages = defineMessages({

const mapStateToProps = state => ({
  layout: state.getIn(['meta', 'layout']),
  isSearching: state.getIn(['search', 'submitted']) || !showTrends,
  isSearching: state.getIn(['search', 'submitted']) || !trendsEnabled,
});

class Explore extends PureComponent {

M app/javascript/flavours/glitch/features/explore/links.jsx => app/javascript/flavours/glitch/features/explore/links.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';

import Story from './components/story';

M app/javascript/flavours/glitch/features/explore/statuses.jsx => app/javascript/flavours/glitch/features/explore/statuses.jsx +2 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash';

import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import StatusList from 'flavours/glitch/components/status_list';
import { getStatusList } from 'flavours/glitch/selectors';



@@ 52,6 52,7 @@ class Statuses extends PureComponent {

        <StatusList
          trackScroll
          timelineId='explore'
          statusIds={statusIds}
          scrollKey='explore-statuses'
          hasMore={hasMore}

M app/javascript/flavours/glitch/features/explore/tags.jsx => app/javascript/flavours/glitch/features/explore/tags.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';


M app/javascript/flavours/glitch/features/firehose/index.jsx => app/javascript/flavours/glitch/features/firehose/index.jsx +1 -1
@@ 10,7 10,7 @@ import { addColumn } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import SettingText from 'flavours/glitch/components/setting_text';
import initialState, { domain } from 'flavours/glitch/initial_state';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';

M app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx => app/javascript/flavours/glitch/features/home_timeline/components/explore_prompt.jsx +1 -1
@@ 4,7 4,7 @@ import { FormattedMessage } from 'react-intl';

import { Link } from 'react-router-dom';

import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import background from 'mastodon/../images/friends-cropped.png';



M app/javascript/flavours/glitch/features/public_timeline/index.jsx => app/javascript/flavours/glitch/features/public_timeline/index.jsx +1 -1
@@ 12,7 12,7 @@ import { connectPublicStream } from 'flavours/glitch/actions/streaming';
import { expandPublicTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import { domain } from 'flavours/glitch/initial_state';


M app/javascript/flavours/glitch/features/report/comment.jsx => app/javascript/flavours/glitch/features/report/comment.jsx +102 -68
@@ 1,87 1,121 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { useCallback, useEffect, useRef } from 'react';

import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';

import { OrderedSet, List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { shallowEqual } from 'react-redux';
import { createSelector } from 'reselect';

import Toggle from 'react-toggle';

import { fetchAccount } from 'flavours/glitch/actions/accounts';
import Button from 'flavours/glitch/components/button';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';

const messages = defineMessages({
  placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
});

class Comment extends PureComponent {

  static propTypes = {
    onSubmit: PropTypes.func.isRequired,
    comment: PropTypes.string.isRequired,
    onChangeComment: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    isSubmitting: PropTypes.bool,
    forward: PropTypes.bool,
    isRemote: PropTypes.bool,
    domain: PropTypes.string,
    onChangeForward: PropTypes.func.isRequired,
  };

  handleClick = () => {
    const { onSubmit } = this.props;
    onSubmit();
  };

  handleChange = e => {
    const { onChangeComment } = this.props;
    onChangeComment(e.target.value);
  };

  handleKeyDown = e => {
const selectRepliedToAccountIds = createSelector(
  [
    (state) => state.get('statuses'),
    (_, statusIds) => statusIds,
  ],
  (statusesMap, statusIds) => statusIds.map((statusId) => statusesMap.getIn([statusId, 'in_reply_to_account_id'])),
  {
    resultEqualityCheck: shallowEqual,
  }
);

const Comment = ({ comment, domain, statusIds, isRemote, isSubmitting, selectedDomains, onSubmit, onChangeComment, onToggleDomain }) => {
  const intl = useIntl();

  const dispatch = useAppDispatch();
  const loadedRef = useRef(false);

  const handleClick = useCallback(() => onSubmit(), [onSubmit]);
  const handleChange = useCallback((e) => onChangeComment(e.target.value), [onChangeComment]);
  const handleToggleDomain = useCallback(e => onToggleDomain(e.target.value, e.target.checked), [onToggleDomain]);

  const handleKeyDown = useCallback((e) => {
    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      this.handleClick();
      handleClick();
    }
  };

  handleForwardChange = e => {
    const { onChangeForward } = this.props;
    onChangeForward(e.target.checked);
  };

  render () {
    const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;

    return (
      <>
        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>

        <textarea
          className='report-dialog-modal__textarea'
          placeholder={intl.formatMessage(messages.placeholder)}
          value={comment}
          onChange={this.handleChange}
          onKeyDown={this.handleKeyDown}
          disabled={isSubmitting}
        />

        {isRemote && (
          <>
            <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>

            <label className='report-dialog-modal__toggle'>
              <Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
  }, [handleClick]);

  // Memoize accountIds since we don't want it to trigger `useEffect` on each render
  const accountIds = useAppSelector((state) => domain ? selectRepliedToAccountIds(state, statusIds) : ImmutableList());

  // While we could memoize `availableDomains`, it is pretty inexpensive to recompute
  const accountsMap = useAppSelector((state) => state.get('accounts'));
  const availableDomains = domain ? OrderedSet([domain]).union(accountIds.map((accountId) => accountsMap.getIn([accountId, 'acct'], '').split('@')[1]).filter(domain => !!domain)) : OrderedSet();

  useEffect(() => {
    if (loadedRef.current) {
      return;
    }

    loadedRef.current = true;

    // First, pre-select known domains
    availableDomains.forEach((domain) => {
      onToggleDomain(domain, true);
    });

    // Then, fetch missing replied-to accounts
    const unknownAccounts = OrderedSet(accountIds.filter(accountId => accountId && !accountsMap.has(accountId)));
    unknownAccounts.forEach((accountId) => {
      dispatch(fetchAccount(accountId));
    });
  });

  return (
    <>
      <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>

      <textarea
        className='report-dialog-modal__textarea'
        placeholder={intl.formatMessage(messages.placeholder)}
        value={comment}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        disabled={isSubmitting}
      />

      {isRemote && (
        <>
          <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>

          { availableDomains.map((domain) => (
            <label className='report-dialog-modal__toggle' key={`toggle-${domain}`}>
              <Toggle checked={selectedDomains.includes(domain)} disabled={isSubmitting} onChange={handleToggleDomain} value={domain} />
              <FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
            </label>
          </>
        )}

        <div className='flex-spacer' />
          ))}
        </>
      )}

        <div className='report-dialog-modal__actions'>
          <Button onClick={this.handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
        </div>
      </>
    );
  }
      <div className='flex-spacer' />

      <div className='report-dialog-modal__actions'>
        <Button onClick={handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
      </div>
    </>
  );
}

export default injectIntl(Comment);
Comment.propTypes = {
  comment: PropTypes.string.isRequired,
  domain: PropTypes.string,
  statusIds: ImmutablePropTypes.list.isRequired,
  isRemote: PropTypes.bool,
  isSubmitting: PropTypes.bool,
  selectedDomains: ImmutablePropTypes.set.isRequired,
  onSubmit: PropTypes.func.isRequired,
  onChangeComment: PropTypes.func.isRequired,
  onToggleDomain: PropTypes.func.isRequired,
};

export default Comment;

M app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx => app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +2 -2
@@ 4,7 4,7 @@ import { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';

import NavigationPortal from 'flavours/glitch/components/navigation_portal';
import { timelinePreview, showTrends } from 'flavours/glitch/initial_state';
import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { preferencesLink } from 'flavours/glitch/utils/backend_links';

import ColumnLink from './column_link';


@@ 60,7 60,7 @@ class NavigationPanel extends Component {
          </>
        )}

        {showTrends ? (
        {trendsEnabled ? (
          <ColumnLink transparent to='/explore' icon='hashtag' text={intl.formatMessage(messages.explore)} />
        ) : (
          <ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />

M app/javascript/flavours/glitch/features/ui/components/report_modal.jsx => app/javascript/flavours/glitch/features/ui/components/report_modal.jsx +18 -14
@@ 46,25 46,26 @@ class ReportModal extends ImmutablePureComponent {
  state = {
    step: 'category',
    selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
    selectedDomains: OrderedSet(),
    comment: '',
    category: null,
    selectedRuleIds: OrderedSet(),
    forward: true,
    isSubmitting: false,
    isSubmitted: false,
  };

  handleSubmit = () => {
    const { dispatch, accountId } = this.props;
    const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
    const { selectedStatusIds, selectedDomains, comment, category, selectedRuleIds } = this.state;

    this.setState({ isSubmitting: true });

    dispatch(submitReport({
      account_id: accountId,
      status_ids: selectedStatusIds.toArray(),
      selected_domains: selectedDomains.toArray(),
      comment,
      forward,
      forward: selectedDomains.size > 0,
      category,
      rule_ids: selectedRuleIds.toArray(),
    }, this.handleSuccess, this.handleFail));


@@ 88,13 89,19 @@ class ReportModal extends ImmutablePureComponent {
    }
  };

  handleRuleToggle = (ruleId, checked) => {
    const { selectedRuleIds } = this.state;
  handleDomainToggle = (domain, checked) => {
    if (checked) {
      this.setState((state) => ({ selectedDomains: state.selectedDomains.add(domain) }));
    } else {
      this.setState((state) => ({ selectedDomains: state.selectedDomains.remove(domain) }));
    }
  };

  handleRuleToggle = (ruleId, checked) => {
    if (checked) {
      this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
      this.setState((state) => ({ selectedRuleIds: state.selectedRuleIds.add(ruleId) }));
    } else {
      this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
      this.setState((state) => ({ selectedRuleIds: state.selectedRuleIds.remove(ruleId) }));
    }
  };



@@ 106,10 113,6 @@ class ReportModal extends ImmutablePureComponent {
    this.setState({ comment });
  };

  handleChangeForward = forward => {
    this.setState({ forward });
  };

  handleNextStep = step => {
    this.setState({ step });
  };


@@ 138,8 141,8 @@ class ReportModal extends ImmutablePureComponent {
      step,
      selectedStatusIds,
      selectedRuleIds,
      selectedDomains,
      comment,
      forward,
      category,
      isSubmitting,
      isSubmitted,


@@ 187,10 190,11 @@ class ReportModal extends ImmutablePureComponent {
          isSubmitting={isSubmitting}
          isRemote={isRemote}
          comment={comment}
          forward={forward}
          domain={domain}
          onChangeComment={this.handleChangeComment}
          onChangeForward={this.handleChangeForward}
          statusIds={selectedStatusIds}
          selectedDomains={selectedDomains}
          onToggleDomain={this.handleDomainToggle}
        />
      );
      break;

M app/javascript/flavours/glitch/features/ui/index.jsx => app/javascript/flavours/glitch/features/ui/index.jsx +2 -2
@@ 23,7 23,7 @@ import PermaLink from 'flavours/glitch/components/permalink';
import PictureInPicture from 'flavours/glitch/features/picture_in_picture';
import { layoutFromWindow } from 'flavours/glitch/is_mobile';

import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state';

import BundleColumnError from './components/bundle_column_error';
import Header from './components/header';


@@ 178,7 178,7 @@ class SwitchingColumnsArea extends PureComponent {
      }
    } else if (singleUserMode && owner && initialState?.accounts[owner]) {
      redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
    } else if (showTrends && trendsAsLanding) {
    } else if (trendsEnabled && trendsAsLanding) {
      redirect = <Redirect from='/' to='/explore' exact />;
    } else {
      redirect = <Redirect from='/' to='/about' exact />;

M app/javascript/flavours/glitch/initial_state.js => app/javascript/flavours/glitch/initial_state.js +4 -2
@@ 70,12 70,13 @@
 * @property {boolean} reduce_motion
 * @property {string} repository
 * @property {boolean} search_enabled
 * @property {boolean} trends_enabled
 * @property {boolean} single_user_mode
 * @property {string} source_url
 * @property {string} streaming_api_base_url
 * @property {boolean} timeline_preview
 * @property {string} title
 * @property {boolean} trends
 * @property {boolean} show_trends
 * @property {boolean} trends_as_landing_page
 * @property {boolean} unfollow_modal
 * @property {boolean} use_blurhash


@@ 139,7 140,8 @@ export const reduceMotion = getMeta('reduce_motion');
export const registrationsOpen = getMeta('registrations_open');
export const repository = getMeta('repository');
export const searchEnabled = getMeta('search_enabled');
export const showTrends = getMeta('trends');
export const trendsEnabled = getMeta('trends_enabled');
export const showTrends = getMeta('show_trends');
export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
export const timelinePreview = getMeta('timeline_preview');

M app/javascript/flavours/glitch/store/middlewares/sounds.ts => app/javascript/flavours/glitch/store/middlewares/sounds.ts +11 -6
@@ 1,5 1,8 @@
import type { Middleware, AnyAction } from 'redux';

import ready from 'flavours/glitch/ready';
import { assetHost } from 'flavours/glitch/utils/config';

import type { RootState } from '..';

interface AudioSource {


@@ 35,18 38,20 @@ export const soundsMiddleware = (): Middleware<
  Record<string, never>,
  RootState
> => {
  const soundCache: { [key: string]: HTMLAudioElement } = {
    boop: createAudio([
  const soundCache: { [key: string]: HTMLAudioElement } = {};

  void ready(() => {
    soundCache.boop = createAudio([
      {
        src: '/sounds/boop.ogg',
        src: `${assetHost}/sounds/boop.ogg`,
        type: 'audio/ogg',
      },
      {
        src: '/sounds/boop.mp3',
        src: `${assetHost}/sounds/boop.mp3`,
        type: 'audio/mpeg',
      },
    ]),
  };
    ]);
  });

  return () =>
    (next) =>

M app/javascript/flavours/glitch/styles/components/misc.scss => app/javascript/flavours/glitch/styles/components/misc.scss +14 -19
@@ 150,7 150,6 @@

  .layout-multiple-columns &.button--with-bell {
    font-size: 12px;
    padding: 0 8px;
  }
}



@@ 1343,34 1342,30 @@ button.icon-button.active i.fa-retweet {
  }

  &__overlay {
    display: block;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba($black, 0.5);
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
    border: 0;

    &__label {
      display: inline-block;
      background: rgba($base-overlay-background, 0.5);
      border-radius: 8px;
      padding: 8px 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      flex-direction: column;
      color: $primary-text-color;
      font-weight: 500;
      font-size: 14px;
    }

    &:hover,
    &:focus,
    &:active {
      .spoiler-button__overlay__label {
        background: rgba($base-overlay-background, 0.8);
      }
    }

    &:disabled {
      .spoiler-button__overlay__label {
        background: rgba($base-overlay-background, 0.5);
      }
    &__action {
      font-weight: 400;
      font-size: 13px;
    }
  }
}

M app/javascript/flavours/glitch/styles/components/modal.scss => app/javascript/flavours/glitch/styles/components/modal.scss +1 -0
@@ 709,6 709,7 @@
  &__toggle {
    display: flex;
    align-items: center;
    margin-bottom: 10px;

    & > span {
      font-size: 17px;

M app/javascript/flavours/glitch/styles/components/status.scss => app/javascript/flavours/glitch/styles/components/status.scss +0 -4
@@ 657,10 657,6 @@ a.status__display-name,
  color: inherit;
}

.detailed-status .button.logo-button {
  margin-bottom: 15px;
}

.detailed-status__display-name {
  color: $secondary-text-color;
  display: block;

M app/javascript/flavours/glitch/styles/contrast/diff.scss => app/javascript/flavours/glitch/styles/contrast/diff.scss +2 -1
@@ 15,7 15,8 @@
.status__content a,
.link-footer a,
.reply-indicator__content a,
.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  text-decoration: underline;

  &:hover,

M app/javascript/flavours/glitch/styles/mastodon-light/diff.scss => app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +0 -8
@@ 627,14 627,6 @@ html {
  }
}

.button.logo-button {
  color: $white;

  svg {
    fill: $white;
  }
}

.notification__filter-bar button.active::after,
.account__section-headline a.active::after {
  border-color: transparent transparent $white;

M app/javascript/flavours/glitch/styles/statuses.scss => app/javascript/flavours/glitch/styles/statuses.scss +2 -61
@@ 73,66 73,6 @@
  }
}

.button.logo-button {
  flex: 0 auto;
  font-size: 14px;
  background: darken($ui-highlight-color, 2%);
  color: $primary-text-color;
  text-transform: none;
  line-height: 1.2;
  height: auto;
  min-height: 36px;
  min-width: 88px;
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
  padding: 0 15px;
  border: 0;

  svg {
    width: 20px;
    height: auto;
    vertical-align: middle;
    margin-inline-end: 5px;
    fill: $primary-text-color;
  }

  &:active,
  &:focus,
  &:hover {
    background: $ui-highlight-color;
  }

  &:disabled,
  &.disabled {
    &:active,
    &:focus,
    &:hover {
      background: $ui-primary-color;
    }
  }

  &.button--destructive {
    &:active,
    &:focus,
    &:hover {
      background: $error-red;
    }
  }

  @media screen and (max-width: $no-gap-breakpoint) {
    svg {
      display: none;
    }
  }
}

a.button.logo-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.embed {
  .status__content[data-spoiler='folded'] {
    .e-content {


@@ 261,7 201,8 @@ a.button.logo-button {
  }
}

.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  display: block;
  font-size: 15px;
  line-height: 20px;

M app/javascript/mastodon/actions/alerts.js => app/javascript/mastodon/actions/alerts.js +29 -33
@@ 12,52 12,48 @@ export const ALERT_DISMISS = 'ALERT_DISMISS';
export const ALERT_CLEAR   = 'ALERT_CLEAR';
export const ALERT_NOOP    = 'ALERT_NOOP';

export function dismissAlert(alert) {
  return {
    type: ALERT_DISMISS,
    alert,
  };
}
export const dismissAlert = alert => ({
  type: ALERT_DISMISS,
  alert,
});

export function clearAlert() {
  return {
    type: ALERT_CLEAR,
  };
}
export const clearAlert = () => ({
  type: ALERT_CLEAR,
});

export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
  return {
    type: ALERT_SHOW,
    title,
    message,
    message_values,
  };
}
export const showAlert = alert => ({
  type: ALERT_SHOW,
  alert,
});

export function showAlertForError(error, skipNotFound = false) {
export const showAlertForError = (error, skipNotFound = false) => {
  if (error.response) {
    const { data, status, statusText, headers } = error.response;

    // Skip these errors as they are reflected in the UI
    if (skipNotFound && (status === 404 || status === 410)) {
      // Skip these errors as they are reflected in the UI
      return { type: ALERT_NOOP };
    }

    // Rate limit errors
    if (status === 429 && headers['x-ratelimit-reset']) {
      const reset_date = new Date(headers['x-ratelimit-reset']);
      return showAlert(messages.rateLimitedTitle, messages.rateLimitedMessage, { 'retry_time': reset_date });
      return showAlert({
        title: messages.rateLimitedTitle,
        message: messages.rateLimitedMessage,
        values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
      });
    }

    let message = statusText;
    let title   = `${status}`;
    return showAlert({
      title: `${status}`,
      message: data.error || statusText,
    });
  }

    if (data.error) {
      message = data.error;
    }
  console.error(error);

    return showAlert(title, message);
  } else {
    console.error(error);
    return showAlert();
  }
  return showAlert({
    title: messages.unexpectedTitle,
    message: messages.unexpectedMessage,
  });
}

M app/javascript/mastodon/actions/compose.js => app/javascript/mastodon/actions/compose.js +14 -4
@@ 82,6 82,8 @@ export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
const messages = defineMessages({
  uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
  uploadErrorPoll:  { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
  open: { id: 'compose.published.open', defaultMessage: 'Open' },
  published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
});

export const ensureComposeIsVisible = (getState, routerHistory) => {


@@ 242,6 244,13 @@ export function submitCompose(routerHistory) {
        }
        insertIfOnline(`account:${response.data.account.id}`);
      }

      dispatch(showAlert({
        message: messages.published,
        action: messages.open,
        dismissAfter: 10000,
        onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
      }));
    }).catch(function (error) {
      dispatch(submitComposeFail(error));
    });


@@ 271,18 280,19 @@ export function submitComposeFail(error) {
export function uploadCompose(files) {
  return function (dispatch, getState) {
    const uploadLimit = 4;
    const media  = getState().getIn(['compose', 'media_attachments']);
    const pending  = getState().getIn(['compose', 'pending_media_attachments']);
    const media = getState().getIn(['compose', 'media_attachments']);
    const pending = getState().getIn(['compose', 'pending_media_attachments']);
    const progress = new Array(files.length).fill(0);

    let total = Array.from(files).reduce((a, v) => a + v.size, 0);

    if (files.length + media.size + pending > uploadLimit) {
      dispatch(showAlert(undefined, messages.uploadErrorLimit));
      dispatch(showAlert({ message: messages.uploadErrorLimit }));
      return;
    }

    if (getState().getIn(['compose', 'poll'])) {
      dispatch(showAlert(undefined, messages.uploadErrorPoll));
      dispatch(showAlert({ message: messages.uploadErrorPoll }));
      return;
    }


M app/javascript/mastodon/components/account.jsx => app/javascript/mastodon/components/account.jsx +3 -3
@@ 8,15 8,15 @@ import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import { counterRenderer } from 'mastodon/components/common_counter';
import { EmptyAccount } from 'mastodon/components/empty_account';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { VerifiedBadge } from 'mastodon/components/verified_badge';

import { me } from '../initial_state';

import { Avatar } from './avatar';
import Button from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp';


@@ 160,7 160,7 @@ class Account extends ImmutablePureComponent {
              <DisplayName account={account} />
              {!minimal && (
                <div className='account__details'>
                  <ShortNumber value={account.get('followers_count')} renderer={counterRenderer('followers')} /> {verification} {muteTimeRemaining}
                  <ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
                </div>
              )}
            </div>

M app/javascript/mastodon/components/animated_number.tsx => app/javascript/mastodon/components/animated_number.tsx +1 -1
@@ 4,7 4,7 @@ import { TransitionMotion, spring } from 'react-motion';

import { reduceMotion } from '../initial_state';

import ShortNumber from './short_number';
import { ShortNumber } from './short_number';

const obfuscatedCount = (count: number) => {
  if (count < 0) {

M app/javascript/mastodon/components/autosuggest_hashtag.tsx => app/javascript/mastodon/components/autosuggest_hashtag.tsx +1 -1
@@ 1,6 1,6 @@
import { FormattedMessage } from 'react-intl';

import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';

interface Props {
  tag: {

R app/javascript/mastodon/components/common_counter.jsx => app/javascript/mastodon/components/counters.tsx +42 -57
@@ 1,60 1,45 @@
// @ts-check
import React from 'react';

import { FormattedMessage } from 'react-intl';

/**
 * Returns custom renderer for one of the common counter types
 * @param {"statuses" | "following" | "followers"} counterType
 * Type of the counter
 * @param {boolean} isBold Whether display number must be displayed in bold
 * @returns {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
 * Renderer function
 * @throws If counterType is not covered by this function
 */
export function counterRenderer(counterType, isBold = true) {
  /**
   * @type {(displayNumber: JSX.Element) => JSX.Element}
   */
  const renderCounter = isBold
    ? (displayNumber) => <strong>{displayNumber}</strong>
    : (displayNumber) => displayNumber;
export const StatusesCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.statuses_counter'
    defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

export const FollowingCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.following_counter'
    defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

  switch (counterType) {
  case 'statuses': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.statuses_counter'
        defaultMessage='{count, plural, one {{counter} Post} other {{counter} Posts}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'following': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.following_counter'
        defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  case 'followers': {
    return (displayNumber, pluralReady) => (
      <FormattedMessage
        id='account.followers_counter'
        defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
        values={{
          count: pluralReady,
          counter: renderCounter(displayNumber),
        }}
      />
    );
  }
  default: throw Error(`Incorrect counter name: ${counterType}. Ensure it accepted by commonCounter function`);
  }
}
export const FollowersCounter = (
  displayNumber: React.ReactNode,
  pluralReady: number
) => (
  <FormattedMessage
    id='account.followers_counter'
    defaultMessage='{count, plural, one {{counter} Follower} other {{counter} Followers}}'
    values={{
      count: pluralReady,
      counter: <strong>{displayNumber}</strong>,
    }}
  />
);

D app/javascript/mastodon/components/dismissable_banner.jsx => app/javascript/mastodon/components/dismissable_banner.jsx +0 -55
@@ 1,55 0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import { injectIntl, defineMessages } from 'react-intl';

import { bannerSettings } from 'mastodon/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

class DismissableBanner extends PureComponent {

  static propTypes = {
    id: PropTypes.string.isRequired,
    children: PropTypes.node,
    intl: PropTypes.object.isRequired,
  };

  state = {
    visible: !bannerSettings.get(this.props.id),
  };

  handleDismiss = () => {
    const { id } = this.props;
    this.setState({ visible: false }, () => bannerSettings.set(id, true));
  };

  render () {
    const { visible } = this.state;

    if (!visible) {
      return null;
    }

    const { children, intl } = this.props;

    return (
      <div className='dismissable-banner'>
        <div className='dismissable-banner__message'>
          {children}
        </div>

        <div className='dismissable-banner__action'>
          <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} />
        </div>
      </div>
    );
  }

}

export default injectIntl(DismissableBanner);

A app/javascript/mastodon/components/dismissable_banner.tsx => app/javascript/mastodon/components/dismissable_banner.tsx +47 -0
@@ 0,0 1,47 @@
import type { PropsWithChildren } from 'react';
import { useCallback, useState } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { bannerSettings } from 'mastodon/settings';

import { IconButton } from './icon_button';

const messages = defineMessages({
  dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
});

interface Props {
  id: string;
}

export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
  id,
  children,
}) => {
  const [visible, setVisible] = useState(!bannerSettings.get(id));
  const intl = useIntl();

  const handleDismiss = useCallback(() => {
    setVisible(false);
    bannerSettings.set(id, true);
  }, [id]);

  if (!visible) {
    return null;
  }

  return (
    <div className='dismissable-banner'>
      <div className='dismissable-banner__message'>{children}</div>

      <div className='dismissable-banner__action'>
        <IconButton
          icon='times'
          title={intl.formatMessage(messages.dismiss)}
          onClick={handleDismiss}
        />
      </div>
    </div>
  );
};

M app/javascript/mastodon/components/hashtag.jsx => app/javascript/mastodon/components/hashtag.jsx +1 -1
@@ 11,7 11,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';

import { Sparklines, SparklinesCurve } from 'react-sparklines';

import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';

class SilentErrorBoundary extends Component {

M app/javascript/mastodon/components/media_gallery.jsx => app/javascript/mastodon/components/media_gallery.jsx +8 -2
@@ 321,7 321,10 @@ class MediaGallery extends PureComponent {
    if (uncached) {
      spoilerButton = (
        <button type='button' disabled className='spoiler-button__overlay'>
          <span className='spoiler-button__overlay__label'><FormattedMessage id='status.uncached_media_warning' defaultMessage='Not available' /></span>
          <span className='spoiler-button__overlay__label'>
            <FormattedMessage id='status.uncached_media_warning' defaultMessage='Preview not available' />
            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.open' defaultMessage='Click to open' /></span>
          </span>
        </button>
      );
    } else if (visible) {


@@ 329,7 332,10 @@ class MediaGallery extends PureComponent {
    } else {
      spoilerButton = (
        <button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>
          <span className='spoiler-button__overlay__label'>{sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}</span>
          <span className='spoiler-button__overlay__label'>
            {sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}
            <span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
          </span>
        </button>
      );
    }

M app/javascript/mastodon/components/server_banner.jsx => app/javascript/mastodon/components/server_banner.jsx +1 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';

import { fetchServer } from 'mastodon/actions/server';
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import Account from 'mastodon/containers/account_container';
import { domain } from 'mastodon/initial_state';

R app/javascript/mastodon/components/short_number.jsx => app/javascript/mastodon/components/short_number.tsx +59 -84
@@ 1,69 1,49 @@
import PropTypes from 'prop-types';
import { memo } from 'react';

import { FormattedMessage, FormattedNumber } from 'react-intl';

import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers';

// @ts-check
type ShortNumberRenderer = (
  displayNumber: JSX.Element,
  pluralReady: number
) => JSX.Element;

/**
 * @callback ShortNumberRenderer
 * @param {JSX.Element} displayNumber Number to display
 * @param {number} pluralReady Number used for pluralization
 * @returns {JSX.Element} Final render of number
 */

/**
 * @typedef {object} ShortNumberProps
 * @property {number} value Number to display in short variant
 * @property {ShortNumberRenderer} [renderer]
 * Custom renderer for numbers, provided as a prop. If another renderer
 * passed as a child of this component, this prop won't be used.
 * @property {ShortNumberRenderer} [children]
 * Custom renderer for numbers, provided as a child. If another renderer
 * passed as a prop of this component, this one will be used instead.
 */
interface ShortNumberProps {
  value: number;
  renderer?: ShortNumberRenderer;
  children?: ShortNumberRenderer;
}

/**
 * Component that renders short big number to a shorter version
 * @param {ShortNumberProps} param0 Props for the component
 * @returns {JSX.Element} Rendered number
 */
function ShortNumber({ value, renderer, children }) {
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
  value,
  renderer,
  children,
}) => {
  const shortNumber = toShortNumber(value);
  const [, division] = shortNumber;

  if (children != null && renderer != null) {
    console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
  if (children && renderer) {
    console.warn(
      'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.'
    );
  }

  const customRenderer = children != null ? children : renderer;
  const customRenderer = children || renderer || null;

  const displayNumber = <ShortNumberCounter value={shortNumber} />;

  return customRenderer != null
    ? customRenderer(displayNumber, pluralReady(value, division))
    : displayNumber;
}

ShortNumber.propTypes = {
  value: PropTypes.number.isRequired,
  renderer: PropTypes.func,
  children: PropTypes.func,
  return (
    customRenderer?.(displayNumber, pluralReady(value, division)) ||
    displayNumber
  );
};
export const ShortNumber = memo(ShortNumberRenderer);

/**
 * @typedef {object} ShortNumberCounterProps
 * @property {import('../utils/number').ShortNumber} value Short number
 */

/**
 * Renders short number into corresponding localizable react fragment
 * @param {ShortNumberCounterProps} param0 Props for the component
 * @returns {JSX.Element} FormattedMessage ready to be embedded in code
 */
function ShortNumberCounter({ value }) {
interface ShortNumberCounterProps {
  value: number[];
}
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
  const [rawNumber, unit, maxFractionDigits = 0] = value;

  const count = (


@@ 73,43 53,38 @@ function ShortNumberCounter({ value }) {
    />
  );

  let values = { count, rawNumber };
  const values = { count, rawNumber };

  switch (unit) {
  case DECIMAL_UNITS.THOUSAND: {
    return (
      <FormattedMessage
        id='units.short.thousand'
        defaultMessage='{count}K'
        values={values}
      />
    );
    case DECIMAL_UNITS.THOUSAND: {
      return (
        <FormattedMessage
          id='units.short.thousand'
          defaultMessage='{count}K'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.MILLION: {
      return (
        <FormattedMessage
          id='units.short.million'
          defaultMessage='{count}M'
          values={values}
        />
      );
    }
    case DECIMAL_UNITS.BILLION: {
      return (
        <FormattedMessage
          id='units.short.billion'
          defaultMessage='{count}B'
          values={values}
        />
      );
    }
    // Not sure if we should go farther - @Sasha-Sorokin
    default:
      return count;
  }
  case DECIMAL_UNITS.MILLION: {
    return (
      <FormattedMessage
        id='units.short.million'
        defaultMessage='{count}M'
        values={values}
      />
    );
  }
  case DECIMAL_UNITS.BILLION: {
    return (
      <FormattedMessage
        id='units.short.billion'
        defaultMessage='{count}B'
        values={values}
      />
    );
  }
  // Not sure if we should go farther - @Sasha-Sorokin
  default: return count;
  }
}

ShortNumberCounter.propTypes = {
  value: PropTypes.arrayOf(PropTypes.number),
};

export default memo(ShortNumber);

M app/javascript/mastodon/components/status_action_bar.jsx => app/javascript/mastodon/components/status_action_bar.jsx +51 -51
@@ 237,7 237,6 @@ class StatusActionBar extends ImmutablePureComponent {
    const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
    const { signedIn, permissions } = this.context.identity;

    const anonymousAccess    = !signedIn;
    const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility'));
    const pinnableStatus     = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
    const mutingConversation = status.get('muted');


@@ 263,71 262,73 @@ class StatusActionBar extends ImmutablePureComponent {
      menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
    }

    menu.push(null);

    menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });

    if (writtenByMe && pinnableStatus) {
      menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
    }

    menu.push(null);

    if (writtenByMe || withDismiss) {
      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
    if (signedIn) {
      menu.push(null);
    }

    if (writtenByMe) {
      menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
      menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
    } else {
      menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
      menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
      menu.push(null);
      menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });

      if (relationship && relationship.get('muting')) {
        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
      } else {
        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
      if (writtenByMe && pinnableStatus) {
        menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
      }

      if (relationship && relationship.get('blocking')) {
        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
      } else {
        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
      }
      menu.push(null);

      if (!this.props.onFilter) {
        menu.push(null);
        menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
      if (writtenByMe || withDismiss) {
        menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
        menu.push(null);
      }

      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });

      if (account.get('acct') !== account.get('username')) {
        const domain = account.get('acct').split('@')[1];

      if (writtenByMe) {
        menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
        menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
        menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
      } else {
        menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
        menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
        menu.push(null);

        if (relationship && relationship.get('domain_blocking')) {
          menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
        if (relationship && relationship.get('muting')) {
          menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
        } else {
          menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
          menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
        }
      }

      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
        menu.push(null);
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
          menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
          menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
        if (relationship && relationship.get('blocking')) {
          menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
        } else {
          menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
        }

        if (!this.props.onFilter) {
          menu.push(null);
          menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
          menu.push(null);
        }
        if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {

        menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });

        if (account.get('acct') !== account.get('username')) {
          const domain = account.get('acct').split('@')[1];
          menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });

          menu.push(null);

          if (relationship && relationship.get('domain_blocking')) {
            menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
          } else {
            menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
          }
        }

        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
          menu.push(null);
          if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
            menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
            menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
          }
          if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
            const domain = account.get('acct').split('@')[1];
            menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
          }
        }
      }
    }


@@ 371,7 372,6 @@ class StatusActionBar extends ImmutablePureComponent {
        <div className='status__action-bar__dropdown'>
          <DropdownMenuContainer
            scrollKey={scrollKey}
            disabled={anonymousAccess}
            status={status}
            items={menu}
            icon='ellipsis-h'

M app/javascript/mastodon/components/status_content.jsx => app/javascript/mastodon/components/status_content.jsx +1 -1
@@ 44,7 44,7 @@ class TranslateButton extends PureComponent {
    }

    return (
      <button className='status__content__read-more-button' onClick={onClick}>
      <button className='status__content__translate-button' onClick={onClick}>
        <FormattedMessage id='status.translate' defaultMessage='Translate' />
      </button>
    );

M app/javascript/mastodon/features/account/components/header.jsx => app/javascript/mastodon/features/account/components/header.jsx +9 -10
@@ 11,10 11,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component';

import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button';
import { counterRenderer } from 'mastodon/components/common_counter';
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
import { Icon }  from 'mastodon/components/icon';
import { IconButton } from 'mastodon/components/icon_button';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { autoPlayGif, me, domain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';


@@ 264,14 264,14 @@ class Header extends ImmutablePureComponent {
      if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
        actionBtn = '';
      } else if (account.getIn(['relationship', 'requested'])) {
        actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
        actionBtn = <Button className={classNames({ 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
      } else if (!account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
      } else if (account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
        actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
      }
    } else {
      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
      actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
    }

    if (account.get('moved') && !account.getIn(['relationship', 'following'])) {


@@ 290,7 290,6 @@ class Header extends ImmutablePureComponent {

    if (isRemote) {
      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
      menu.push(null);
    }

    if ('share' in navigator) {


@@ 451,21 450,21 @@ class Header extends ImmutablePureComponent {
                <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
                  <ShortNumber
                    value={account.get('statuses_count')}
                    renderer={counterRenderer('statuses')}
                    renderer={StatusesCounter}
                  />
                </NavLink>

                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
                  <ShortNumber
                    value={account.get('following_count')}
                    renderer={counterRenderer('following')}
                    renderer={FollowingCounter}
                  />
                </NavLink>

                <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
                  <ShortNumber
                    value={account.get('followers_count')}
                    renderer={counterRenderer('followers')}
                    renderer={FollowersCounter}
                  />
                </NavLink>
              </div>

M app/javascript/mastodon/features/community_timeline/index.jsx => app/javascript/mastodon/features/community_timeline/index.jsx +1 -1
@@ 7,7 7,7 @@ import { Helmet } from 'react-helmet';

import { connect } from 'react-redux';

import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';

import { addColumn, removeColumn, moveColumn } from '../../actions/columns';

M app/javascript/mastodon/features/directory/components/account_card.jsx => app/javascript/mastodon/features/directory/components/account_card.jsx +6 -6
@@ 19,7 19,7 @@ import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar';
import Button from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import ShortNumber from 'mastodon/components/short_number';
import { ShortNumber } from 'mastodon/components/short_number';
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
import { makeGetAccount } from 'mastodon/selectors';



@@ 160,16 160,16 @@ class AccountCard extends ImmutablePureComponent {
      if (!account.get('relationship')) { // Wait until the relationship is loaded
        actionBtn = '';
      } else if (account.getIn(['relationship', 'requested'])) {
        actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
        actionBtn = <Button  text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
      } else if (account.getIn(['relationship', 'muting'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
        actionBtn = <Button  text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
      } else if (!account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
      } else if (account.getIn(['relationship', 'blocking'])) {
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
        actionBtn = <Button  text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
      }
    } else {
      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
      actionBtn = <Button  text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
    }

    return (

M app/javascript/mastodon/features/explore/components/story.jsx => app/javascript/mastodon/features/explore/components/story.jsx +1 -1
@@ 5,7 5,7 @@ import classNames from 'classnames';

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

export default class Story extends PureComponent {

M app/javascript/mastodon/features/explore/index.jsx => app/javascript/mastodon/features/explore/index.jsx +2 -2
@@ 11,7 11,7 @@ import { connect } from 'react-redux';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { showTrends } from 'mastodon/initial_state';
import { trendsEnabled } from 'mastodon/initial_state';

import Links from './links';
import SearchResults from './results';


@@ 26,7 26,7 @@ const messages = defineMessages({

const mapStateToProps = state => ({
  layout: state.getIn(['meta', 'layout']),
  isSearching: state.getIn(['search', 'submitted']) || !showTrends,
  isSearching: state.getIn(['search', 'submitted']) || !trendsEnabled,
});

class Explore extends PureComponent {

M app/javascript/mastodon/features/explore/links.jsx => app/javascript/mastodon/features/explore/links.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingLinks } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';

import Story from './components/story';

M app/javascript/mastodon/features/explore/statuses.jsx => app/javascript/mastodon/features/explore/statuses.jsx +2 -1
@@ 9,7 9,7 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash';

import { fetchTrendingStatuses, expandTrendingStatuses } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import StatusList from 'mastodon/components/status_list';
import { getStatusList } from 'mastodon/selectors';



@@ 52,6 52,7 @@ class Statuses extends PureComponent {

        <StatusList
          trackScroll
          timelineId='explore'
          statusIds={statusIds}
          scrollKey='explore-statuses'
          hasMore={hasMore}

M app/javascript/mastodon/features/explore/tags.jsx => app/javascript/mastodon/features/explore/tags.jsx +1 -1
@@ 7,7 7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';


M app/javascript/mastodon/features/firehose/index.jsx => app/javascript/mastodon/features/firehose/index.jsx +1 -1
@@ 10,7 10,7 @@ import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';


M app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx => app/javascript/mastodon/features/home_timeline/components/explore_prompt.jsx +1 -1
@@ 5,7 5,7 @@ import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';

import background from 'mastodon/../images/friends-cropped.png';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';


export const ExplorePrompt = () => (

M app/javascript/mastodon/features/notifications/containers/column_settings_container.js => app/javascript/mastodon/features/notifications/containers/column_settings_container.js +2 -2
@@ 32,7 32,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
          if (permission === 'granted') {
            dispatch(changePushNotifications(path.slice(1), checked));
          } else {
            dispatch(showAlert(undefined, messages.permissionDenied));
            dispatch(showAlert({ message: messages.permissionDenied }));
          }
        }));
      } else {


@@ 47,7 47,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
          if (permission === 'granted') {
            dispatch(changeSetting(['notifications', ...path], checked));
          } else {
            dispatch(showAlert(undefined, messages.permissionDenied));
            dispatch(showAlert({ message: messages.permissionDenied }));
          }
        }));
      } else {

M app/javascript/mastodon/features/public_timeline/index.jsx => app/javascript/mastodon/features/public_timeline/index.jsx +1 -1
@@ 7,7 7,7 @@ import { Helmet } from 'react-helmet';

import { connect } from 'react-redux';

import DismissableBanner from 'mastodon/components/dismissable_banner';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { domain } from 'mastodon/initial_state';

import { addColumn, removeColumn, moveColumn } from '../../actions/columns';

M app/javascript/mastodon/features/report/comment.jsx => app/javascript/mastodon/features/report/comment.jsx +102 -68
@@ 1,87 1,121 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { useCallback, useEffect, useRef } from 'react';

import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';

import { OrderedSet, List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { shallowEqual } from 'react-redux';
import { createSelector } from 'reselect';

import Toggle from 'react-toggle';

import { fetchAccount } from 'mastodon/actions/accounts';
import Button from 'mastodon/components/button';
import { useAppDispatch, useAppSelector } from 'mastodon/store';

const messages = defineMessages({
  placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
});

class Comment extends PureComponent {

  static propTypes = {
    onSubmit: PropTypes.func.isRequired,
    comment: PropTypes.string.isRequired,
    onChangeComment: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    isSubmitting: PropTypes.bool,
    forward: PropTypes.bool,
    isRemote: PropTypes.bool,
    domain: PropTypes.string,
    onChangeForward: PropTypes.func.isRequired,
  };

  handleClick = () => {
    const { onSubmit } = this.props;
    onSubmit();
  };

  handleChange = e => {
    const { onChangeComment } = this.props;
    onChangeComment(e.target.value);
  };

  handleKeyDown = e => {
const selectRepliedToAccountIds = createSelector(
  [
    (state) => state.get('statuses'),
    (_, statusIds) => statusIds,
  ],
  (statusesMap, statusIds) => statusIds.map((statusId) => statusesMap.getIn([statusId, 'in_reply_to_account_id'])),
  {
    resultEqualityCheck: shallowEqual,
  }
);

const Comment = ({ comment, domain, statusIds, isRemote, isSubmitting, selectedDomains, onSubmit, onChangeComment, onToggleDomain }) => {
  const intl = useIntl();

  const dispatch = useAppDispatch();
  const loadedRef = useRef(false);

  const handleClick = useCallback(() => onSubmit(), [onSubmit]);
  const handleChange = useCallback((e) => onChangeComment(e.target.value), [onChangeComment]);
  const handleToggleDomain = useCallback(e => onToggleDomain(e.target.value, e.target.checked), [onToggleDomain]);

  const handleKeyDown = useCallback((e) => {
    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
      this.handleClick();
      handleClick();
    }
  };

  handleForwardChange = e => {
    const { onChangeForward } = this.props;
    onChangeForward(e.target.checked);
  };

  render () {
    const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props;

    return (
      <>
        <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>

        <textarea
          className='report-dialog-modal__textarea'
          placeholder={intl.formatMessage(messages.placeholder)}
          value={comment}
          onChange={this.handleChange}
          onKeyDown={this.handleKeyDown}
          disabled={isSubmitting}
        />

        {isRemote && (
          <>
            <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>

            <label className='report-dialog-modal__toggle'>
              <Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
  }, [handleClick]);

  // Memoize accountIds since we don't want it to trigger `useEffect` on each render
  const accountIds = useAppSelector((state) => domain ? selectRepliedToAccountIds(state, statusIds) : ImmutableList());

  // While we could memoize `availableDomains`, it is pretty inexpensive to recompute
  const accountsMap = useAppSelector((state) => state.get('accounts'));
  const availableDomains = domain ? OrderedSet([domain]).union(accountIds.map((accountId) => accountsMap.getIn([accountId, 'acct'], '').split('@')[1]).filter(domain => !!domain)) : OrderedSet();

  useEffect(() => {
    if (loadedRef.current) {
      return;
    }

    loadedRef.current = true;

    // First, pre-select known domains
    availableDomains.forEach((domain) => {
      onToggleDomain(domain, true);
    });

    // Then, fetch missing replied-to accounts
    const unknownAccounts = OrderedSet(accountIds.filter(accountId => accountId && !accountsMap.has(accountId)));
    unknownAccounts.forEach((accountId) => {
      dispatch(fetchAccount(accountId));
    });
  });

  return (
    <>
      <h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3>

      <textarea
        className='report-dialog-modal__textarea'
        placeholder={intl.formatMessage(messages.placeholder)}
        value={comment}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        disabled={isSubmitting}
      />

      {isRemote && (
        <>
          <p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>

          { availableDomains.map((domain) => (
            <label className='report-dialog-modal__toggle' key={`toggle-${domain}`}>
              <Toggle checked={selectedDomains.includes(domain)} disabled={isSubmitting} onChange={handleToggleDomain} value={domain} />
              <FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} />
            </label>
          </>
        )}

        <div className='flex-spacer' />
          ))}
        </>
      )}

        <div className='report-dialog-modal__actions'>
          <Button onClick={this.handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
        </div>
      </>
    );
  }
      <div className='flex-spacer' />

      <div className='report-dialog-modal__actions'>
        <Button onClick={handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
      </div>
    </>
  );
}

export default injectIntl(Comment);
Comment.propTypes = {
  comment: PropTypes.string.isRequired,
  domain: PropTypes.string,
  statusIds: ImmutablePropTypes.list.isRequired,
  isRemote: PropTypes.bool,
  isSubmitting: PropTypes.bool,
  selectedDomains: ImmutablePropTypes.set.isRequired,
  onSubmit: PropTypes.func.isRequired,
  onChangeComment: PropTypes.func.isRequired,
  onToggleDomain: PropTypes.func.isRequired,
};

export default Comment;

M app/javascript/mastodon/features/status/components/action_bar.jsx => app/javascript/mastodon/features/status/components/action_bar.jsx +52 -49
@@ 195,71 195,74 @@ class ActionBar extends PureComponent {

    let menu = [];

    if (publicStatus) {
      if (isRemote) {
        menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
      }
    if (publicStatus && isRemote) {
      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
    }

      menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
    menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });

      if ('share' in navigator) {
        menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare });
      }
    if (publicStatus && 'share' in navigator) {
      menu.push({ text: intl.formatMessage(messages.share), action: this.handleShare });
    }

    if (publicStatus) {
      menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
      menu.push(null);
    }

    if (writtenByMe) {
      if (pinnableStatus) {
        menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
        menu.push(null);
      }

      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
      menu.push(null);
      menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
      menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
    } else {
      menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
    if (signedIn) {
      menu.push(null);

      if (relationship && relationship.get('muting')) {
        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
      } else {
        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
      }
      if (writtenByMe) {
        if (pinnableStatus) {
          menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
          menu.push(null);
        }

      if (relationship && relationship.get('blocking')) {
        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
        menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
        menu.push(null);
        menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
        menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
        menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
      } else {
        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
      }

      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });

      if (account.get('acct') !== account.get('username')) {
        const domain = account.get('acct').split('@')[1];

        menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
        menu.push(null);

        if (relationship && relationship.get('domain_blocking')) {
          menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
        if (relationship && relationship.get('muting')) {
          menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
        } else {
          menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
          menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
        }
      }

      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
        menu.push(null);
        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
          menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
          menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
        if (relationship && relationship.get('blocking')) {
          menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
        } else {
          menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
        }
        if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {

        menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport, dangerous: true });

        if (account.get('acct') !== account.get('username')) {
          const domain = account.get('acct').split('@')[1];
          menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });

          menu.push(null);

          if (relationship && relationship.get('domain_blocking')) {
            menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
          } else {
            menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
          }
        }

        if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
          menu.push(null);
          if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
            menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
            menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
          }
          if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
            const domain = account.get('acct').split('@')[1];
            menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
          }
        }
      }
    }


@@ 292,7 295,7 @@ class ActionBar extends PureComponent {
        <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>

        <div className='detailed-status__action-bar-dropdown'>
          <DropdownMenuContainer size={18} icon='ellipsis-h' disabled={!signedIn} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
          <DropdownMenuContainer size={18} icon='ellipsis-h' status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
        </div>
      </div>
    );

M app/javascript/mastodon/features/ui/components/navigation_panel.jsx => app/javascript/mastodon/features/ui/components/navigation_panel.jsx +2 -2
@@ 7,7 7,7 @@ import { Link } from 'react-router-dom';

import { WordmarkLogo } from 'mastodon/components/logo';
import NavigationPortal from 'mastodon/components/navigation_portal';
import { timelinePreview, showTrends } from 'mastodon/initial_state';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';

import ColumnLink from './column_link';
import DisabledAccountBanner from './disabled_account_banner';


@@ 65,7 65,7 @@ class NavigationPanel extends Component {
          </>
        )}

        {showTrends ? (
        {trendsEnabled ? (
          <ColumnLink transparent to='/explore' icon='hashtag' text={intl.formatMessage(messages.explore)} />
        ) : (
          <ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />

M app/javascript/mastodon/features/ui/components/report_modal.jsx => app/javascript/mastodon/features/ui/components/report_modal.jsx +18 -14
@@ 45,25 45,26 @@ class ReportModal extends ImmutablePureComponent {
  state = {
    step: 'category',
    selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
    selectedDomains: OrderedSet(),
    comment: '',
    category: null,
    selectedRuleIds: OrderedSet(),
    forward: true,
    isSubmitting: false,
    isSubmitted: false,
  };

  handleSubmit = () => {
    const { dispatch, accountId } = this.props;
    const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
    const { selectedStatusIds, selectedDomains, comment, category, selectedRuleIds } = this.state;

    this.setState({ isSubmitting: true });

    dispatch(submitReport({
      account_id: accountId,
      status_ids: selectedStatusIds.toArray(),
      selected_domains: selectedDomains.toArray(),
      comment,
      forward,
      forward: selectedDomains.size > 0,
      category,
      rule_ids: selectedRuleIds.toArray(),
    }, this.handleSuccess, this.handleFail));


@@ 87,13 88,19 @@ class ReportModal extends ImmutablePureComponent {
    }
  };

  handleRuleToggle = (ruleId, checked) => {
    const { selectedRuleIds } = this.state;
  handleDomainToggle = (domain, checked) => {
    if (checked) {
      this.setState((state) => ({ selectedDomains: state.selectedDomains.add(domain) }));
    } else {
      this.setState((state) => ({ selectedDomains: state.selectedDomains.remove(domain) }));
    }
  };

  handleRuleToggle = (ruleId, checked) => {
    if (checked) {
      this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
      this.setState((state) => ({ selectedRuleIds: state.selectedRuleIds.add(ruleId) }));
    } else {
      this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
      this.setState((state) => ({ selectedRuleIds: state.selectedRuleIds.remove(ruleId) }));
    }
  };



@@ 105,10 112,6 @@ class ReportModal extends ImmutablePureComponent {
    this.setState({ comment });
  };

  handleChangeForward = forward => {
    this.setState({ forward });
  };

  handleNextStep = step => {
    this.setState({ step });
  };


@@ 136,8 139,8 @@ class ReportModal extends ImmutablePureComponent {
      step,
      selectedStatusIds,
      selectedRuleIds,
      selectedDomains,
      comment,
      forward,
      category,
      isSubmitting,
      isSubmitted,


@@ 185,10 188,11 @@ class ReportModal extends ImmutablePureComponent {
          isSubmitting={isSubmitting}
          isRemote={isRemote}
          comment={comment}
          forward={forward}
          domain={domain}
          onChangeComment={this.handleChangeComment}
          onChangeForward={this.handleChangeForward}
          statusIds={selectedStatusIds}
          selectedDomains={selectedDomains}
          onToggleDomain={this.handleDomainToggle}
        />
      );
      break;

M app/javascript/mastodon/features/ui/containers/notifications_container.js => app/javascript/mastodon/features/ui/containers/notifications_container.js +19 -18
@@ 7,26 7,27 @@ import { NotificationStack } from 'react-notification';
import { dismissAlert } from '../../../actions/alerts';
import { getAlerts } from '../../../selectors';

const mapStateToProps = (state, { intl }) => {
  const notifications = getAlerts(state);
const formatIfNeeded = (intl, message, values) => {
  if (typeof message === 'object') {
    return intl.formatMessage(message, values);
  }

  notifications.forEach(notification => ['title', 'message'].forEach(key => {
    const value = notification[key];

    if (typeof value === 'object') {
      notification[key] = intl.formatMessage(value, notification[`${key}_values`]);
    }
  }));

  return { notifications };
  return message;
};

const mapDispatchToProps = (dispatch) => {
  return {
    onDismiss: alert => {
      dispatch(dismissAlert(alert));
    },
  };
};
const mapStateToProps = (state, { intl }) => ({
  notifications: getAlerts(state).map(alert => ({
    ...alert,
    action: formatIfNeeded(intl, alert.action, alert.values),
    title: formatIfNeeded(intl, alert.title, alert.values),
    message: formatIfNeeded(intl, alert.message, alert.values),
  })),
});

const mapDispatchToProps = (dispatch) => ({
  onDismiss (alert) {
    dispatch(dismissAlert(alert));
  },
});

export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));

M app/javascript/mastodon/features/ui/index.jsx => app/javascript/mastodon/features/ui/index.jsx +2 -2
@@ 22,7 22,7 @@ import { clearHeight } from '../../actions/height_cache';
import { expandNotifications } from '../../actions/notifications';
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
import { expandHomeTimeline } from '../../actions/timelines';
import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state';

import BundleColumnError from './components/bundle_column_error';
import Header from './components/header';


@@ 170,7 170,7 @@ class SwitchingColumnsArea extends PureComponent {
      }
    } else if (singleUserMode && owner && initialState?.accounts[owner]) {
      redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
    } else if (showTrends && trendsAsLanding) {
    } else if (trendsEnabled && trendsAsLanding) {
      redirect = <Redirect from='/' to='/explore' exact />;
    } else {
      redirect = <Redirect from='/' to='/about' exact />;

M app/javascript/mastodon/initial_state.js => app/javascript/mastodon/initial_state.js +4 -2
@@ 69,12 69,13 @@
 * @property {boolean} reduce_motion
 * @property {string} repository
 * @property {boolean} search_enabled
 * @property {boolean} trends_enabled
 * @property {boolean} single_user_mode
 * @property {string} source_url
 * @property {string} streaming_api_base_url
 * @property {boolean} timeline_preview
 * @property {string} title
 * @property {boolean} trends
 * @property {boolean} show_trends
 * @property {boolean} trends_as_landing_page
 * @property {boolean} unfollow_modal
 * @property {boolean} use_blurhash


@@ 122,7 123,8 @@ export const reduceMotion = getMeta('reduce_motion');
export const registrationsOpen = getMeta('registrations_open');
export const repository = getMeta('repository');
export const searchEnabled = getMeta('search_enabled');
export const showTrends = getMeta('trends');
export const trendsEnabled = getMeta('trends_enabled');
export const showTrends = getMeta('show_trends');
export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
export const timelinePreview = getMeta('timeline_preview');

M app/javascript/mastodon/locales/en.json => app/javascript/mastodon/locales/en.json +5 -1
@@ 135,6 135,8 @@
  "community.column_settings.remote_only": "Remote only",
  "compose.language.change": "Change language",
  "compose.language.search": "Search languages...",
  "compose.published.body": "Post published.",
  "compose.published.open": "Open",
  "compose_form.direct_message_warning_learn_more": "Learn more",
  "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.",
  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",


@@ 616,6 618,8 @@
  "status.history.created": "{name} created {date}",
  "status.history.edited": "{name} edited {date}",
  "status.load_more": "Load more",
  "status.media.open": "Click to open",
  "status.media.show": "Click to show",
  "status.media_hidden": "Media hidden",
  "status.mention": "Mention @{name}",
  "status.more": "More",


@@ 646,7 650,7 @@
  "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}",
  "status.translate": "Translate",
  "status.translated_from_with": "Translated from {lang} using {provider}",
  "status.uncached_media_warning": "Not available",
  "status.uncached_media_warning": "Preview not available",
  "status.unmute_conversation": "Unmute conversation",
  "status.unpin": "Unpin from profile",
  "subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",

M app/javascript/mastodon/reducers/alerts.js => app/javascript/mastodon/reducers/alerts.js +11 -8
@@ 1,4 1,4 @@
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { List as ImmutableList } from 'immutable';

import {
  ALERT_SHOW,


@@ 8,17 8,20 @@ import {

const initialState = ImmutableList([]);

let id = 0;

const addAlert = (state, alert) =>
  state.push({
    key: id++,
    ...alert,
  });

export default function alerts(state = initialState, action) {
  switch(action.type) {
  case ALERT_SHOW:
    return state.push(ImmutableMap({
      key: state.size > 0 ? state.last().get('key') + 1 : 0,
      title: action.title,
      message: action.message,
      message_values: action.message_values,
    }));
    return addAlert(state, action.alert);
  case ALERT_DISMISS:
    return state.filterNot(item => item.get('key') === action.alert.key);
    return state.filterNot(item => item.key === action.alert.key);
  case ALERT_CLEAR:
    return state.clear();
  default:

M app/javascript/mastodon/reducers/index.ts => app/javascript/mastodon/reducers/index.ts +0 -2
@@ 26,7 26,6 @@ import lists from './lists';
import markers from './markers';
import media_attachments from './media_attachments';
import meta from './meta';
import { missedUpdatesReducer } from './missed_updates';
import { modalReducer } from './modal';
import mutes from './mutes';
import notifications from './notifications';


@@ 82,7 81,6 @@ const reducers = {
  suggestions,
  polls,
  trends,
  missed_updates: missedUpdatesReducer,
  markers,
  picture_in_picture,
  history,

D app/javascript/mastodon/reducers/missed_updates.ts => app/javascript/mastodon/reducers/missed_updates.ts +0 -33
@@ 1,33 0,0 @@
import { Record } from 'immutable';

import type { Action } from 'redux';

import { focusApp, unfocusApp } from '../actions/app';
import { NOTIFICATIONS_UPDATE } from '../actions/notifications';

interface MissedUpdatesState {
  focused: boolean;
  unread: number;
}
const initialState = Record<MissedUpdatesState>({
  focused: true,
  unread: 0,
})();

export function missedUpdatesReducer(
  state = initialState,
  action: Action<string>
) {
  switch (action.type) {
    case focusApp.type:
      return state.set('focused', true).set('unread', 0);
    case unfocusApp.type:
      return state.set('focused', false);
    case NOTIFICATIONS_UPDATE:
      return state.get('focused')
        ? state
        : state.update('unread', (x) => x + 1);
    default:
      return state;
  }
}

M app/javascript/mastodon/selectors/index.js => app/javascript/mastodon/selectors/index.js +9 -19
@@ 84,26 84,16 @@ export const makeGetPictureInPicture = () => {
  }));
};

const getAlertsBase = state => state.get('alerts');

export const getAlerts = createSelector([getAlertsBase], (base) => {
  let arr = [];

  base.forEach(item => {
    arr.push({
      message: item.get('message'),
      message_values: item.get('message_values'),
      title: item.get('title'),
      key: item.get('key'),
      dismissAfter: 5000,
      barStyle: {
        zIndex: 200,
      },
    });
  });
const ALERT_DEFAULTS = {
  dismissAfter: 5000,
  style: false,
};

  return arr;
});
export const getAlerts = createSelector(state => state.get('alerts'), alerts =>
  alerts.map(item => ({
    ...ALERT_DEFAULTS,
    ...item,
  })).toArray());

export const makeGetNotification = () => createSelector([
  (_, base)             => base,

M app/javascript/mastodon/store/middlewares/sounds.ts => app/javascript/mastodon/store/middlewares/sounds.ts +11 -6
@@ 1,5 1,8 @@
import type { Middleware, AnyAction } from 'redux';

import ready from 'mastodon/ready';
import { assetHost } from 'mastodon/utils/config';

import type { RootState } from '..';

interface AudioSource {


@@ 35,18 38,20 @@ export const soundsMiddleware = (): Middleware<
  Record<string, never>,
  RootState
> => {
  const soundCache: { [key: string]: HTMLAudioElement } = {
    boop: createAudio([
  const soundCache: { [key: string]: HTMLAudioElement } = {};

  void ready(() => {
    soundCache.boop = createAudio([
      {
        src: '/sounds/boop.ogg',
        src: `${assetHost}/sounds/boop.ogg`,
        type: 'audio/ogg',
      },
      {
        src: '/sounds/boop.mp3',
        src: `${assetHost}/sounds/boop.mp3`,
        type: 'audio/mpeg',
      },
    ]),
  };
    ]);
  });

  return () =>
    (next) =>

M app/javascript/styles/contrast/diff.scss => app/javascript/styles/contrast/diff.scss +2 -1
@@ 15,7 15,8 @@
.status__content a,
.link-footer a,
.reply-indicator__content a,
.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  text-decoration: underline;

  &:hover,

M app/javascript/styles/mailer.scss => app/javascript/styles/mailer.scss +1 -1
@@ 541,7 541,7 @@ ul.rules-list {
  padding-top: 0;
}

@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) {
@media only screen and (device-width >= 768px) and (device-width <= 1024px) and (orientation: landscape) {
  body {
    min-height: 1024px !important;
  }

M app/javascript/styles/mastodon-light/diff.scss => app/javascript/styles/mastodon-light/diff.scss +0 -8
@@ 627,14 627,6 @@ html {
  }
}

.button.logo-button {
  color: $white;

  svg {
    fill: $white;
  }
}

.notification__filter-bar button.active::after,
.account__section-headline a.active::after {
  border-color: transparent transparent $white;

M app/javascript/styles/mastodon/components.scss => app/javascript/styles/mastodon/components.scss +77 -23
@@ 981,7 981,8 @@ body > [data-popper-placement] {
  max-height: 22px * 15; // 15 lines is roughly above 500 characters
}

.status__content__read-more-button {
.status__content__read-more-button,
.status__content__translate-button {
  display: block;
  font-size: 15px;
  line-height: 22px;


@@ 1669,10 1670,6 @@ a.account__display-name {
  color: inherit;
}

.detailed-status .button.logo-button {
  margin-bottom: 15px;
}

.detailed-status__display-name {
  color: $darker-text-color;
  display: flex;


@@ 4212,34 4209,31 @@ a.status-card.compact:hover {
  }

  &__overlay {
    display: block;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba($black, 0.5);
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 4px;

    &__label {
      display: inline-block;
      background: rgba($base-overlay-background, 0.5);
      border-radius: 8px;
      padding: 8px 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      flex-direction: column;
      color: $primary-text-color;
      font-weight: 500;
      font-size: 14px;
    }

    &:hover,
    &:focus,
    &:active {
      .spoiler-button__overlay__label {
        background: rgba($base-overlay-background, 0.8);
      }
    }

    &:disabled {
      .spoiler-button__overlay__label {
        background: rgba($base-overlay-background, 0.5);
      }
    &__action {
      font-weight: 400;
      font-size: 13px;
    }
  }
}


@@ 5786,6 5780,7 @@ a.status-card.compact:hover {
  &__toggle {
    display: flex;
    align-items: center;
    margin-bottom: 10px;

    & > span {
      font-size: 17px;


@@ 9076,3 9071,62 @@ noscript {
    }
  }
}

.notification-list {
  position: fixed;
  bottom: 2rem;
  inset-inline-start: 0;
  z-index: 999;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.notification-bar {
  flex: 0 0 auto;
  position: relative;
  inset-inline-start: -100%;
  width: auto;
  padding: 15px;
  margin: 0;
  color: $white;
  background: rgba($black, 0.85);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(lighten($classic-base-color, 4%), 0.85);
  border-radius: 8px;
  box-shadow: 0 10px 15px -3px rgba($base-shadow-color, 0.25),
    0 4px 6px -4px rgba($base-shadow-color, 0.25);
  cursor: default;
  transition: 0.5s cubic-bezier(0.89, 0.01, 0.5, 1.1);
  transform: translateZ(0);
  font-size: 15px;
  line-height: 21px;

  &.notification-bar-active {
    inset-inline-start: 1rem;
  }
}

.notification-bar-title {
  margin-inline-end: 5px;
}

.notification-bar-title,
.notification-bar-action {
  font-weight: 700;
}

.notification-bar-action {
  text-transform: uppercase;
  margin-inline-start: 10px;
  cursor: pointer;
  color: $blurple-300;
  border-radius: 4px;
  padding: 0 4px;

  &:hover,
  &:focus,
  &:active {
    background: rgba($ui-base-color, 0.85);
  }
}

M app/javascript/styles/mastodon/statuses.scss => app/javascript/styles/mastodon/statuses.scss +0 -60
@@ 77,66 77,6 @@
  }
}

.button.logo-button {
  flex: 0 auto;
  font-size: 14px;
  background: darken($ui-highlight-color, 2%);
  color: $primary-text-color;
  text-transform: none;
  line-height: 1.2;
  height: auto;
  min-height: 36px;
  min-width: 88px;
  white-space: normal;
  overflow-wrap: break-word;
  hyphens: auto;
  padding: 0 15px;
  border: 0;

  svg {
    width: 20px;
    height: auto;
    vertical-align: middle;
    margin-inline-end: 5px;
    fill: $primary-text-color;
  }

  &:active,
  &:focus,
  &:hover {
    background: $ui-highlight-color;
  }

  &:disabled,
  &.disabled {
    &:active,
    &:focus,
    &:hover {
      background: $ui-primary-color;
    }
  }

  &.button--destructive {
    &:active,
    &:focus,
    &:hover {
      background: $error-red;
    }
  }

  @media screen and (max-width: $no-gap-breakpoint) {
    svg {
      display: none;
    }
  }
}

a.button.logo-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.embed {
  .status__content[data-spoiler='folded'] {
    .e-content {

M app/lib/activitypub/activity.rb => app/lib/activitypub/activity.rb +2 -2
@@ 143,11 143,11 @@ class ActivityPub::Activity
  end

  def follow_request_from_object
    @follow_request ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
    @follow_request_from_object ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
  end

  def follow_from_object
    @follow ||= ::Follow.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
    @follow_from_object ||= ::Follow.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
  end

  def fetch_remote_original_status

M app/lib/activitypub/activity/flag.rb => app/lib/activitypub/activity/flag.rb +5 -4
@@ 4,13 4,14 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
  def perform
    return if skip_reports?

    target_accounts            = object_uris.filter_map { |uri| account_from_uri(uri) }.select(&:local?)
    target_statuses_by_account = object_uris.filter_map { |uri| status_from_uri(uri) }.select(&:local?).group_by(&:account_id)
    target_accounts            = object_uris.filter_map { |uri| account_from_uri(uri) }
    target_statuses_by_account = object_uris.filter_map { |uri| status_from_uri(uri) }.group_by(&:account_id)

    target_accounts.each do |target_account|
      target_statuses = target_statuses_by_account[target_account.id]
      target_statuses     = target_statuses_by_account[target_account.id]
      replied_to_accounts = Account.local.where(id: target_statuses.filter_map(&:in_reply_to_account_id))

      next if target_account.suspended?
      next if target_account.suspended? || (!target_account.local? && replied_to_accounts.none?)

      ReportService.new.call(
        @account,

M app/lib/emoji_formatter.rb => app/lib/emoji_formatter.rb +1 -1
@@ 53,7 53,7 @@ class EmojiFormatter
        end
      end

      result << Nokogiri::XML::Text.new(text[last_index..-1], tree.document)
      result << Nokogiri::XML::Text.new(text[last_index..], tree.document)
      node.replace(result)
    end


M app/lib/text_formatter.rb => app/lib/text_formatter.rb +3 -3
@@ 57,8 57,8 @@ class TextFormatter

      prefix      = url.match(URL_PREFIX_REGEX).to_s
      display_url = url[prefix.length, 30]
      suffix      = url[prefix.length + 30..-1]
      cutoff      = url[prefix.length..-1].length > 30
      suffix      = url[prefix.length + 30..]
      cutoff      = url[prefix.length..].length > 30

      <<~HTML.squish.html_safe # rubocop:disable Rails/OutputSafety
        <a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}" translate="no"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>


@@ 84,7 84,7 @@ class TextFormatter
      indices.last
    end

    result << h(text[last_index..-1])
    result << h(text[last_index..])

    result
  end

M app/mailers/admin_mailer.rb => app/mailers/admin_mailer.rb +26 -19
@@ 6,45 6,52 @@ class AdminMailer < ApplicationMailer
  helper :accounts
  helper :languages

  def new_report(recipient, report)
    @report   = report
    @me       = recipient
    @instance = Rails.configuration.x.local_domain
  before_action :process_params
  before_action :set_instance

  default to: -> { @me.user_email }

  def new_report(report)
    @report = report

    locale_for_account(@me) do
      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_report.subject', instance: @instance, id: @report.id)
      mail subject: default_i18n_subject(instance: @instance, id: @report.id)
    end
  end

  def new_appeal(recipient, appeal)
    @appeal   = appeal
    @me       = recipient
    @instance = Rails.configuration.x.local_domain
  def new_appeal(appeal)
    @appeal = appeal

    locale_for_account(@me) do
      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_appeal.subject', instance: @instance, username: @appeal.account.username)
      mail subject: default_i18n_subject(instance: @instance, username: @appeal.account.username)
    end
  end

  def new_pending_account(recipient, user)
    @account  = user.account
    @me       = recipient
    @instance = Rails.configuration.x.local_domain
  def new_pending_account(user)
    @account = user.account

    locale_for_account(@me) do
      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_pending_account.subject', instance: @instance, username: @account.username)
      mail subject: default_i18n_subject(instance: @instance, username: @account.username)
    end
  end

  def new_trends(recipient, links, tags, statuses)
  def new_trends(links, tags, statuses)
    @links                  = links
    @tags                   = tags
    @statuses               = statuses
    @me                     = recipient
    @instance               = Rails.configuration.x.local_domain

    locale_for_account(@me) do
      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_trends.subject', instance: @instance)
      mail subject: default_i18n_subject(instance: @instance)
    end
  end

  private

  def process_params
    @me = params[:recipient]
  end

  def set_instance
    @instance = Rails.configuration.x.local_domain
  end
end

M app/mailers/notification_mailer.rb => app/mailers/notification_mailer.rb +32 -39
@@ 1,83 1,76 @@
# frozen_string_literal: true

class NotificationMailer < ApplicationMailer
  helper :accounts
  helper :statuses
  helper :accounts,
         :statuses,
         :routing

  helper RoutingHelper
  before_action :process_params
  before_action :set_status, only: [:mention, :favourite, :reblog]
  before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]

  def mention(recipient, notification)
    @me     = recipient
    @user   = recipient.user
    @type   = 'mention'
    @status = notification.target_status
  default to: -> { email_address_with_name(@user.email, @me.username) }

  def mention
    return unless @user.functional? && @status.present?

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

  def follow(recipient, notification)
    @me      = recipient
    @user    = recipient.user
    @type    = 'follow'
    @account = notification.from_account

  def follow
    return unless @user.functional?

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

  def favourite(recipient, notification)
    @me      = recipient
    @user    = recipient.user
    @type    = 'favourite'
    @account = notification.from_account
    @status  = notification.target_status

  def favourite
    return unless @user.functional? && @status.present?

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

  def reblog(recipient, notification)
    @me      = recipient
    @user    = recipient.user
    @type    = 'reblog'
    @account = notification.from_account
    @status  = notification.target_status

  def reblog
    return unless @user.functional? && @status.present?

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

  def follow_request(recipient, notification)
    @me      = recipient
    @user    = recipient.user
    @type    = 'follow_request'
    @account = notification.from_account

  def follow_request
    return unless @user.functional?

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

  private

  def process_params
    @notification = params[:notification]
    @me = params[:recipient]
    @user = @me.user
    @type = action_name
  end

  def set_status
    @status = @notification.target_status
  end

  def set_account
    @account = @notification.from_account
  end

  def thread_by_conversation(conversation)
    return if conversation.nil?


M app/models/account_alias.rb => app/models/account_alias.rb +1 -1
@@ 25,7 25,7 @@ class AccountAlias < ApplicationRecord

  def acct=(val)
    val = val.to_s.strip
    super(val.start_with?('@') ? val[1..-1] : val)
    super(val.start_with?('@') ? val[1..] : val)
  end

  def pretty_acct

M app/models/domain_block.rb => app/models/domain_block.rb +1 -1
@@ 69,7 69,7 @@ class DomainBlock < ApplicationRecord

      uri      = Addressable::URI.new.tap { |u| u.host = domain.strip.delete('/') }
      segments = uri.normalized_host.split('.')
      variants = segments.map.with_index { |_, i| segments[i..-1].join('.') }
      variants = segments.map.with_index { |_, i| segments[i..].join('.') }

      where(domain: variants).order(Arel.sql('char_length(domain) desc')).first
    rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError

M app/models/email_domain_block.rb => app/models/email_domain_block.rb +1 -1
@@ 64,7 64,7 @@ class EmailDomainBlock < ApplicationRecord

        segments = uri.normalized_host.split('.')

        segments.map.with_index { |_, i| segments[i..-1].join('.') }
        segments.map.with_index { |_, i| segments[i..].join('.') }
      end
    end


M app/models/preview_card_provider.rb => app/models/preview_card_provider.rb +1 -1
@@ 54,6 54,6 @@ class PreviewCardProvider < ApplicationRecord

  def self.matching_domain(domain)
    segments = domain.split('.')
    where(domain: segments.map.with_index { |_, i| segments[i..-1].join('.') }).order(Arel.sql('char_length(domain) desc')).first
    where(domain: segments.map.with_index { |_, i| segments[i..].join('.') }).order(Arel.sql('char_length(domain) desc')).first
  end
end

M app/models/trends.rb => app/models/trends.rb +1 -1
@@ 38,7 38,7 @@ module Trends
      statuses = user.allows_trending_statuses_review_emails? ? statuses_requiring_review : []
      next if links.empty? && tags.empty? && statuses.empty?

      AdminMailer.new_trends(user.account, links, tags, statuses).deliver_later!
      AdminMailer.with(recipient: user.account).new_trends(links, tags, statuses).deliver_later! if user.allows_trends_review_emails?
    end
  end


M app/models/trends/links.rb => app/models/trends/links.rb +19 -7
@@ 3,6 3,8 @@
class Trends::Links < Trends::Base
  PREFIX = 'trending_links'

  BATCH_SIZE = 100

  self.default_options = {
    threshold: 5,
    review_threshold: 3,


@@ 67,8 69,21 @@ class Trends::Links < Trends::Base
  end

  def refresh(at_time = Time.now.utc)
    preview_cards = PreviewCard.where(id: (recently_used_ids(at_time) + PreviewCardTrend.pluck(:preview_card_id)).uniq)
    calculate_scores(preview_cards, at_time)
    # First, recalculate scores for links that were trending previously. We split the queries
    # to avoid having to load all of the IDs into Ruby just to send them back into Postgres
    PreviewCard.where(id: PreviewCardTrend.select(:preview_card_id)).find_in_batches(batch_size: BATCH_SIZE) do |preview_cards|
      calculate_scores(preview_cards, at_time)
    end

    # Then, calculate scores for links that were used today. There are potentially some
    # duplicate items here that we might process one more time, but that should be fine
    PreviewCard.where(id: recently_used_ids(at_time)).find_in_batches(batch_size: BATCH_SIZE) do |preview_cards|
      calculate_scores(preview_cards, at_time)
    end

    # Now that all trends have up-to-date scores, and all the ones below the threshold have
    # been removed, we can recalculate their positions
    PreviewCardTrend.connection.exec_update('UPDATE preview_card_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM preview_card_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE preview_card_trends.id = t0.id')
  end

  def request_review


@@ 139,10 154,7 @@ class Trends::Links < Trends::Base
    to_insert = items.filter { |(score, _)| score >= options[:decay_threshold] }
    to_delete = items.filter { |(score, _)| score < options[:decay_threshold] }

    PreviewCardTrend.transaction do
      PreviewCardTrend.upsert_all(to_insert.map { |(score, preview_card)| { preview_card_id: preview_card.id, score: score, language: preview_card.language, allowed: preview_card.trendable? || false } }, unique_by: :preview_card_id) if to_insert.any?
      PreviewCardTrend.where(preview_card_id: to_delete.map { |(_, preview_card)| preview_card.id }).delete_all if to_delete.any?
      PreviewCardTrend.connection.exec_update('UPDATE preview_card_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM preview_card_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE preview_card_trends.id = t0.id')
    end
    PreviewCardTrend.upsert_all(to_insert.map { |(score, preview_card)| { preview_card_id: preview_card.id, score: score, language: preview_card.language, allowed: preview_card.trendable? || false } }, unique_by: :preview_card_id) if to_insert.any?
    PreviewCardTrend.where(preview_card_id: to_delete.map { |(_, preview_card)| preview_card.id }).delete_all if to_delete.any?
  end
end

M app/models/trends/query.rb => app/models/trends/query.rb +18 -6
@@ 68,12 68,10 @@ class Trends::Query
  alias to_a to_ary

  def to_arel
    tmp_ids = ids

    if tmp_ids.empty?
    if ids_for_key.empty?
      klass.none
    else
      scope = klass.joins("join unnest(array[#{tmp_ids.join(',')}]) with ordinality as x (id, ordering) on #{klass.table_name}.id = x.id").reorder('x.ordering')
      scope = klass.joins(sanitized_join_sql).reorder('x.ordering')
      scope = scope.offset(@offset) if @offset.present?
      scope = scope.limit(@limit) if @limit.present?
      scope


@@ 95,8 93,22 @@ class Trends::Query
    self
  end

  def ids
    redis.zrevrange(key, 0, -1).map(&:to_i)
  def ids_for_key
    @ids_for_key ||= redis.zrevrange(key, 0, -1).map(&:to_i)
  end

  def sanitized_join_sql
    ActiveRecord::Base.sanitize_sql_array(join_sql_array)
  end

  def join_sql_array
    [join_sql_query, ids_for_key]
  end

  def join_sql_query
    <<~SQL.squish
      JOIN unnest(array[?]) WITH ordinality AS x (id, ordering) ON #{klass.table_name}.id = x.id
    SQL
  end

  def perform_queries

M app/models/trends/statuses.rb => app/models/trends/statuses.rb +19 -7
@@ 3,6 3,8 @@
class Trends::Statuses < Trends::Base
  PREFIX = 'trending_statuses'

  BATCH_SIZE = 100

  self.default_options = {
    threshold: 5,
    review_threshold: 3,


@@ 58,8 60,21 @@ class Trends::Statuses < Trends::Base
  end

  def refresh(at_time = Time.now.utc)
    statuses = Status.where(id: (recently_used_ids(at_time) + StatusTrend.pluck(:status_id)).uniq).includes(:status_stat, :account)
    calculate_scores(statuses, at_time)
    # First, recalculate scores for statuses that were trending previously. We split the queries
    # to avoid having to load all of the IDs into Ruby just to send them back into Postgres
    Status.where(id: StatusTrend.select(:status_id)).includes(:status_stat, :account).find_in_batches(batch_size: BATCH_SIZE) do |statuses|
      calculate_scores(statuses, at_time)
    end

    # Then, calculate scores for statuses that were used today. There are potentially some
    # duplicate items here that we might process one more time, but that should be fine
    Status.where(id: recently_used_ids(at_time)).includes(:status_stat, :account).find_in_batches(batch_size: BATCH_SIZE) do |statuses|
      calculate_scores(statuses, at_time)
    end

    # Now that all trends have up-to-date scores, and all the ones below the threshold have
    # been removed, we can recalculate their positions
    StatusTrend.connection.exec_update('UPDATE status_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM status_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE status_trends.id = t0.id')
  end

  def request_review


@@ 117,10 132,7 @@ class Trends::Statuses < Trends::Base
    to_insert = items.filter { |(score, _)| score >= options[:decay_threshold] }
    to_delete = items.filter { |(score, _)| score < options[:decay_threshold] }

    StatusTrend.transaction do
      StatusTrend.upsert_all(to_insert.map { |(score, status)| { status_id: status.id, account_id: status.account_id, score: score, language: status.language, allowed: status.trendable? || false } }, unique_by: :status_id) if to_insert.any?
      StatusTrend.where(status_id: to_delete.map { |(_, status)| status.id }).delete_all if to_delete.any?
      StatusTrend.connection.exec_update('UPDATE status_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM status_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE status_trends.id = t0.id')
    end
    StatusTrend.upsert_all(to_insert.map { |(score, status)| { status_id: status.id, account_id: status.account_id, score: score, language: status.language, allowed: status.trendable? || false } }, unique_by: :status_id) if to_insert.any?
    StatusTrend.where(status_id: to_delete.map { |(_, status)| status.id }).delete_all if to_delete.any?
  end
end

M app/models/user.rb => app/models/user.rb +1 -1
@@ 475,7 475,7 @@ class User < ApplicationRecord
    User.those_who_can(:manage_users).includes(:account).find_each do |u|
      next unless u.allows_pending_account_emails?

      AdminMailer.new_pending_account(u.account, self).deliver_later
      AdminMailer.with(recipient: u.account).new_pending_account(self).deliver_later
    end
  end


M app/serializers/initial_state_serializer.rb => app/serializers/initial_state_serializer.rb +2 -2
@@ 39,7 39,7 @@ class InitialStateSerializer < ActiveModel::Serializer
      limited_federation_mode: Rails.configuration.x.whitelist_mode,
      mascot: instance_presenter.mascot&.file&.url,
      profile_directory: Setting.profile_directory,
      trends: Setting.trends,
      trends_enabled: Setting.trends,
      registrations_open: Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode,
      timeline_preview: Setting.timeline_preview,
      activity_api_enabled: Setting.activity_api_enabled,


@@ 62,9 62,9 @@ class InitialStateSerializer < ActiveModel::Serializer
      store[:advanced_layout]   = object.current_account.user.setting_advanced_layout
      store[:use_blurhash]      = object.current_account.user.setting_use_blurhash
      store[:use_pending_items] = object.current_account.user.setting_use_pending_items
      store[:trends]            = Setting.trends && object.current_account.user.setting_trends
      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

M app/services/account_search_service.rb => app/services/account_search_service.rb +6 -2
@@ 133,8 133,12 @@ class AccountSearchService < BaseService
  end

  def must_clause
    fields = %w(username username.* display_name display_name.*)
    fields << 'text' << 'text.*' if options[:use_searchable_text]
    if options[:start_with_hashtag]
      fields = %w(text text.*)
    else
      fields = %w(username username.* display_name display_name.*)
      fields << 'text' << 'text.*' if options[:use_searchable_text]
    end

    [
      {

M app/services/activitypub/process_account_service.rb => app/services/activitypub/process_account_service.rb +3 -0
@@ 76,6 76,9 @@ class ActivityPub::ProcessAccountService < BaseService
    @account.suspended_at      = domain_block.created_at if auto_suspend?
    @account.suspension_origin = :local if auto_suspend?
    @account.silenced_at       = domain_block.created_at if auto_silence?

    set_immediate_protocol_attributes!

    @account.save
  end


M app/services/appeal_service.rb => app/services/appeal_service.rb +1 -1
@@ 23,7 23,7 @@ class AppealService < BaseService

  def notify_staff!
    User.those_who_can(:manage_appeals).includes(:account).each do |u|
      AdminMailer.new_appeal(u.account, @appeal).deliver_later if u.allows_appeal_emails?
      AdminMailer.with(recipient: u.account).new_appeal(@appeal).deliver_later if u.allows_appeal_emails?
    end
  end
end

M app/services/notify_service.rb => app/services/notify_service.rb +6 -1
@@ 162,7 162,12 @@ class NotifyService < BaseService
  end

  def send_email!
    NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes) if NotificationMailer.respond_to?(@notification.type)
    return unless NotificationMailer.respond_to?(@notification.type)

    NotificationMailer
      .with(recipient: @recipient, notification: @notification)
      .public_send(@notification.type)
      .deliver_later(wait: 2.minutes)
  end

  def email_needed?

M app/services/report_service.rb => app/services/report_service.rb +28 -8
@@ 16,7 16,11 @@ class ReportService < BaseService

    create_report!
    notify_staff!
    forward_to_origin! if forward?

    if forward?
      forward_to_origin!
      forward_to_replied_to!
    end

    @report
  end


@@ 29,7 33,7 @@ class ReportService < BaseService
      status_ids: reported_status_ids,
      comment: @comment,
      uri: @options[:uri],
      forwarded: forward?,
      forwarded: forward_to_origin?,
      category: @category,
      rule_ids: @rule_ids
    )


@@ 40,22 44,38 @@ class ReportService < BaseService

    User.those_who_can(:manage_reports).includes(:account).each do |u|
      LocalNotificationWorker.perform_async(u.account_id, @report.id, 'Report', 'admin.report')
      AdminMailer.new_report(u.account, @report).deliver_later if u.allows_report_emails?
      AdminMailer.with(recipient: u.account).new_report(@report).deliver_later if u.allows_report_emails?
    end
  end

  def forward_to_origin!
    ActivityPub::DeliveryWorker.perform_async(
      payload,
      some_local_account.id,
      @target_account.inbox_url
    )
    return unless forward_to_origin?

    # Send report to the server where the account originates from
    ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, @target_account.inbox_url)
  end

  def forward_to_replied_to!
    # Send report to servers to which the account was replying to, so they also have a chance to act
    inbox_urls = Account.remote.where(domain: forward_to_domains).where(id: Status.where(id: reported_status_ids).where.not(in_reply_to_account_id: nil).select(:in_reply_to_account_id)).inboxes - [@target_account.inbox_url]

    inbox_urls.each do |inbox_url|
      ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
    end
  end

  def forward?
    !@target_account.local? && ActiveModel::Type::Boolean.new.cast(@options[:forward])
  end

  def forward_to_origin?
    forward? && forward_to_domains.include?(@target_account.domain)
  end

  def forward_to_domains
    @forward_to_domains ||= (@options[:forward_to_domains] || [@target_account.domain]).filter_map { |domain| TagManager.instance.normalize_domain(domain&.strip) }.uniq
  end

  def reported_status_ids
    return AccountStatusesFilter.new(@target_account, @source_account).results.with_discarded.find(Array(@status_ids)).pluck(:id) if @source_account.local?


M app/services/resolve_url_service.rb => app/services/resolve_url_service.rb +1 -1
@@ 63,7 63,7 @@ class ResolveURLService < BaseService
  end

  def fetch_resource_service
    @_fetch_resource_service ||= FetchResourceService.new
    @fetch_resource_service ||= FetchResourceService.new
  end

  def resource_url

M app/services/search_service.rb => app/services/search_service.rb +5 -4
@@ 33,7 33,8 @@ class SearchService < BaseService
      resolve: @resolve,
      offset: @offset,
      use_searchable_text: true,
      following: @following
      following: @following,
      start_with_hashtag: @query.start_with?('#')
    )
  end



@@ 81,7 82,7 @@ class SearchService < BaseService
  end

  def url_resource
    @_url_resource ||= ResolveURLService.new.call(@query, on_behalf_of: @account)
    @url_resource ||= ResolveURLService.new.call(@query, on_behalf_of: @account)
  end

  def url_resource_symbol


@@ 91,11 92,11 @@ class SearchService < BaseService
  def full_text_searchable?
    return false unless Chewy.enabled?

    statuses_search? && !@account.nil? && !((@query.start_with?('#') || @query.include?('@')) && !@query.include?(' '))
    statuses_search? && !@account.nil? && !(@query.include?('@') && !@query.include?(' '))
  end

  def account_searchable?
    account_search? && !(@query.start_with?('#') || (@query.include?('@') && @query.include?(' ')))
    account_search? && !(@query.include?('@') && @query.include?(' '))
  end

  def hashtag_searchable?

M app/validators/status_length_validator.rb => app/validators/status_length_validator.rb +1 -1
@@ 53,7 53,7 @@ class StatusLengthValidator < ActiveModel::Validator
      entity[:indices].last
    end

    result << str[last_index..-1]
    result << str[last_index..]
    result
  end
end

M app/views/accounts/show.rss.ruby => app/views/accounts/show.rss.ruby +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

RSS::Builder.build do |doc|
  doc.title(display_name(@account))
  doc.description(I18n.t('rss.descriptions.account', acct: @account.local_username_and_domain))

M app/views/tags/show.rss.ruby => app/views/tags/show.rss.ruby +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

RSS::Builder.build do |doc|
  doc.title("##{@tag.display_name}")
  doc.description(I18n.t('rss.descriptions.tag', hashtag: @tag.display_name))

M app/views/well_known/host_meta/show.xml.ruby => app/views/well_known/host_meta/show.xml.ruby +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

doc = Ox::Document.new(version: '1.0')

doc << Ox::Element.new('XRD').tap do |xrd|

M app/workers/feed_insert_worker.rb => app/workers/feed_insert_worker.rb +16 -12
@@ 4,21 4,25 @@ class FeedInsertWorker
  include Sidekiq::Worker

  def perform(status_id, id, type = 'home', options = {})
    @type      = type.to_sym
    @status    = Status.find(status_id)
    @options   = options.symbolize_keys
    ApplicationRecord.connected_to(role: :primary) do
      @type      = type.to_sym
      @status    = Status.find(status_id)
      @options   = options.symbolize_keys

    case @type
    when :home, :tags
      @follower = Account.find(id)
    when :list
      @list     = List.find(id)
      @follower = @list.account
    when :direct
      @account  = Account.find(id)
      case @type
      when :home, :tags
        @follower = Account.find(id)
      when :list
        @list     = List.find(id)
        @follower = @list.account
      when :direct
        @account  = Account.find(id)
      end
    end

    check_and_insert
    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
      check_and_insert
    end
  rescue ActiveRecord::RecordNotFound
    true
  end

M app/workers/merge_worker.rb => app/workers/merge_worker.rb +8 -1
@@ 5,7 5,14 @@ class MergeWorker
  include Redisable

  def perform(from_account_id, into_account_id)
    FeedManager.instance.merge_into_home(Account.find(from_account_id), Account.find(into_account_id))
    ApplicationRecord.connected_to(role: :primary) do
      @from_account = Account.find(from_account_id)
      @into_account = Account.find(into_account_id)
    end

    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
      FeedManager.instance.merge_into_home(@from_account, @into_account)
    end
  rescue ActiveRecord::RecordNotFound
    true
  ensure

M app/workers/regeneration_worker.rb => app/workers/regeneration_worker.rb +7 -2
@@ 6,8 6,13 @@ class RegenerationWorker
  sidekiq_options lock: :until_executed

  def perform(account_id, _ = :home)
    account = Account.find(account_id)
    PrecomputeFeedService.new.call(account)
    ApplicationRecord.connected_to(role: :primary) do
      @account = Account.find(account_id)
    end

    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
      PrecomputeFeedService.new.call(@account)
    end
  rescue ActiveRecord::RecordNotFound
    true
  end

M app/workers/unmerge_worker.rb => app/workers/unmerge_worker.rb +8 -1
@@ 6,7 6,14 @@ class UnmergeWorker
  sidekiq_options queue: 'pull'

  def perform(from_account_id, into_account_id)
    FeedManager.instance.unmerge_from_home(Account.find(from_account_id), Account.find(into_account_id))
    ApplicationRecord.connected_to(role: :primary) do
      @from_account = Account.find(from_account_id)
      @into_account = Account.find(into_account_id)
    end

    ApplicationRecord.connected_to(role: :read, prevent_writes: true) do
      FeedManager.instance.unmerge_from_home(@from_account, @into_account)
    end
  rescue ActiveRecord::RecordNotFound
    true
  end

M config/application.rb => config/application.rb +6 -4
@@ 1,3 1,5 @@
# frozen_string_literal: true

require_relative 'boot'

require 'rails'


@@ 194,10 196,10 @@ module Mastodon
    config.to_prepare do
      Doorkeeper::AuthorizationsController.layout 'modal'
      Doorkeeper::AuthorizedApplicationsController.layout 'admin'
      Doorkeeper::Application.send :include, ApplicationExtension
      Doorkeeper::AccessToken.send :include, AccessTokenExtension
      Devise::FailureApp.send :include, AbstractController::Callbacks
      Devise::FailureApp.send :include, Localized
      Doorkeeper::Application.include ApplicationExtension
      Doorkeeper::AccessToken.include AccessTokenExtension
      Devise::FailureApp.include AbstractController::Callbacks
      Devise::FailureApp.include Localized
    end
  end
end

M config/boot.rb => config/boot.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

unless ENV.key?('RAILS_ENV')
  STDERR.puts 'ERROR: Missing RAILS_ENV environment variable, please set it to "production", "development", or "test".'
  exit 1

M config/brakeman.ignore => config/brakeman.ignore +56 -91
@@ 18,46 18,9 @@
      },
      "user_input": "id",
      "confidence": "Weak",
      "note": ""
    },
    {
      "warning_type": "SQL Injection",
      "warning_code": 0,
      "fingerprint": "30dfe36e87fe1b8f239df9a33d576e44a9863f73b680198d4713be6540ae61d3",
      "check_name": "SQL",
      "message": "Possible SQL injection",
      "file": "app/models/trends/query.rb",
      "line": 76,
      "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
      "code": "klass.joins(\"join unnest(array[#{ids.join(\",\")}]) with ordinality as x (id, ordering) on #{klass.table_name}.id = x.id\")",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "Trends::Query",
        "method": "to_arel"
      },
      "user_input": "ids.join(\",\")",
      "confidence": "Weak",
      "note": ""
    },
    {
      "warning_type": "Redirect",
      "warning_code": 18,
      "fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7",
      "check_name": "Redirect",
      "message": "Possible unprotected redirect",
      "file": "app/controllers/remote_interaction_controller.rb",
      "line": 24,
      "link": "https://brakemanscanner.org/docs/warning_types/redirect/",
      "code": "redirect_to(RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id])))",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "RemoteInteractionController",
        "method": "create"
      },
      "user_input": "RemoteFollow.new(resource_params).interact_address_for(Status.find(params[:id]))",
      "confidence": "High",
      "cwe_id": [
        89
      ],
      "note": ""
    },
    {


@@ 88,46 51,33 @@
      },
      "user_input": "(Unresolved Model).new.strike",
      "confidence": "Weak",
      "cwe_id": [
        79
      ],
      "note": ""
    },
    {
      "warning_type": "SQL Injection",
      "warning_code": 0,
      "fingerprint": "75fcd147b7611763ab6915faf8c5b0709e612b460f27c05c72d8b9bd0a6a77f8",
      "check_name": "SQL",
      "message": "Possible SQL injection",
      "file": "lib/mastodon/snowflake.rb",
      "line": 87,
      "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
      "code": "connection.execute(\"CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\nRETURNS bigint AS\\n$$\\n  DECLARE\\n    time_part bigint;\\n    sequence_base bigint;\\n    tail bigint;\\n  BEGIN\\n    time_part := (\\n      -- Get the time in milliseconds\\n      ((date_part('epoch', now()) * 1000))::bigint\\n      -- And shift it over two bytes\\n      << 16);\\n\\n    sequence_base := (\\n      'x' ||\\n      -- Take the first two bytes (four hex characters)\\n      substr(\\n        -- Of the MD5 hash of the data we documented\\n        md5(table_name || '#{SecureRandom.hex(16)}' || time_part::text),\\n        1, 4\\n      )\\n    -- And turn it into a bigint\\n    )::bit(16)::bigint;\\n\\n    -- Finally, add our sequence number to our base, and chop\\n    -- it to the last two bytes\\n    tail := (\\n      (sequence_base + nextval(table_name || '_id_seq'))\\n      & 65535);\\n\\n    -- Return the time part and the sequence part. OR appears\\n    -- faster here than addition, but they're equivalent:\\n    -- time_part has no trailing two bytes, and tail is only\\n    -- the last two bytes.\\n    RETURN time_part | tail;\\n  END\\n$$ LANGUAGE plpgsql VOLATILE;\\n\")",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "Mastodon::Snowflake",
        "method": "define_timestamp_id"
      },
      "user_input": "SecureRandom.hex(16)",
      "confidence": "Medium",
      "note": ""
    },
    {
      "warning_type": "Mass Assignment",
      "warning_code": 105,
      "fingerprint": "7631e93d0099506e7c3e5c91ba8d88523b00a41a0834ae30031a5a4e8bb3020a",
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v2/search_controller.rb",
      "line": 28,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id)",
      "warning_type": "Denial of Service",
      "warning_code": 76,
      "fingerprint": "7b6abba5699755348e7ee82a4694bfbf574b41c7cce2d0db0f7c11ae3f983c72",
      "check_name": "RegexDoS",
      "message": "Model attribute used in regular expression",
      "file": "lib/mastodon/cli/domains.rb",
      "line": 128,
      "link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/",
      "code": "/\\.?(#{DomainBlock.where(:severity => 1).pluck(:domain).map do\n Regexp.escape(domain)\n end.join(\"|\")})$/",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "Api::V2::SearchController",
        "method": "search_params"
        "class": "Mastodon::CLI::Domains",
        "method": "crawl"
      },
      "user_input": ":account_id",
      "confidence": "High",
      "user_input": "DomainBlock.where(:severity => 1).pluck(:domain)",
      "confidence": "Weak",
      "cwe_id": [
        20,
        185
      ],
      "note": ""
    },
    {


@@ 137,7 87,7 @@
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v1/admin/reports_controller.rb",
      "line": 90,
      "line": 88,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:resolved, :account_id, :target_account_id)",
      "render_path": null,


@@ 148,6 98,9 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    },
    {


@@ 157,7 110,7 @@
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v1/notifications_controller.rb",
      "line": 81,
      "line": 77,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
      "render_path": null,


@@ 168,26 121,32 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    },
    {
      "warning_type": "Redirect",
      "warning_code": 18,
      "fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50",
      "check_name": "Redirect",
      "message": "Possible unprotected redirect",
      "file": "app/controllers/remote_follow_controller.rb",
      "line": 21,
      "link": "https://brakemanscanner.org/docs/warning_types/redirect/",
      "code": "redirect_to(RemoteFollow.new(resource_params).subscribe_address_for(@account))",
      "warning_type": "Mass Assignment",
      "warning_code": 105,
      "fingerprint": "b0dd0a26d24f5ede9713fe49210e9638be5f5548af9eee0b5a16fe9dbc80ffcd",
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v2/search_controller.rb",
      "line": 42,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following)",
      "render_path": null,
      "location": {
        "type": "method",
        "class": "RemoteFollowController",
        "method": "create"
        "class": "Api::V2::SearchController",
        "method": "search_params"
      },
      "user_input": "RemoteFollow.new(resource_params).subscribe_address_for(@account)",
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    },
    {


@@ 218,18 177,21 @@
      },
      "user_input": "(Unresolved Model).new.url",
      "confidence": "Weak",
      "cwe_id": [
        79
      ],
      "note": ""
    },
    {
      "warning_type": "Mass Assignment",
      "warning_code": 105,
      "fingerprint": "f9de0ca4b04ae4b51b74d98db14dcbb6dae6809e627b58e711019cf9b4a47866",
      "fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427",
      "check_name": "PermitAttributes",
      "message": "Potentially dangerous key allowed for mass assignment",
      "file": "app/controllers/api/v1/reports_controller.rb",
      "line": 26,
      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
      "code": "params.permit(:account_id, :comment, :category, :forward, :status_ids => ([]), :rule_ids => ([]))",
      "code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))",
      "render_path": null,
      "location": {
        "type": "method",


@@ 238,9 200,12 @@
      },
      "user_input": ":account_id",
      "confidence": "High",
      "cwe_id": [
        915
      ],
      "note": ""
    }
  ],
  "updated": "2022-03-22 07:48:32 +0100",
  "brakeman_version": "5.2.1"
  "updated": "2023-07-11 16:08:58 +0200",
  "brakeman_version": "6.0.0"
}

M config/database.yml => config/database.yml +17 -7
@@ 27,10 27,20 @@ test:
  port: <%= ENV['DB_PORT'] %>

production:
  <<: *default
  database: <%= ENV['DB_NAME'] || 'mastodon_production' %>
  username: <%= ENV['DB_USER'] || 'mastodon' %>
  password: <%= (ENV['DB_PASS'] || '').to_json %>
  host: <%= ENV['DB_HOST'] || 'localhost' %>
  port: <%= ENV['DB_PORT'] || 5432 %>
  prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
  primary:
    <<: *default
    database: <%= ENV['DB_NAME'] || 'mastodon_production' %>
    username: <%= ENV['DB_USER'] || 'mastodon' %>
    password: <%= (ENV['DB_PASS'] || '').to_json %>
    host: <%= ENV['DB_HOST'] || 'localhost' %>
    port: <%= ENV['DB_PORT'] || 5432 %>
    prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
  read:
    <<: *default
    database: <%= ENV['DB_REPLICA_NAME'] ||ENV['DB_NAME'] || 'mastodon_production' %>
    username: <%= ENV['DB_REPLICA_USER'] ||ENV['DB_USER'] || 'mastodon' %>
    password: <%= (ENV['DB_REPLICA_PASS'] || ENV['DB_PASS'] || '').to_json %>
    host: <%= ENV['DB_REPLICA_HOST'] ||ENV['DB_HOST'] || 'localhost' %>
    port: <%= ENV['DB_REPLICA_PORT'] ||ENV['DB_PORT'] || 5432 %>
    prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
    replica: true

M config/environment.rb => config/environment.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Load the Rails application.
require_relative 'application'


M config/environments/development.rb => config/environments/development.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.


M config/environments/production.rb => config/environments/production.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.


M config/environments/test.rb => config/environments/test.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.


M config/i18n-tasks.yml => config/i18n-tasks.yml +1 -0
@@ 71,6 71,7 @@ ignore_unused:
  - 'statuses.attached.*'
  - 'themes.*'
  - 'move_handler.carry_{mutes,blocks}_over_text'
  - 'admin_mailer.*.subject'
  - 'notification_mailer.*'
  - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks}_html'
  - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks}_html'

M config/initializers/0_post_deployment_migrations.rb => config/initializers/0_post_deployment_migrations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Post deployment migrations are included by default. This file must be loaded
# before other initializers as Rails may otherwise memoize a list of migrations
# excluding the post deployment migrations.

M config/initializers/active_model_serializers.rb => config/initializers/active_model_serializers.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

ActiveModelSerializers.config.tap do |config|
  config.default_includes = '**'
end

M config/initializers/application_controller_renderer.rb => config/initializers/application_controller_renderer.rb +1 -0
@@ 1,3 1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file.

# ActiveSupport::Reloader.to_prepare do

M config/initializers/assets.rb => config/initializers/assets.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.

M config/initializers/backtrace_silencers.rb => config/initializers/backtrace_silencers.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.

M config/initializers/cache_logging.rb => config/initializers/cache_logging.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Log cache errors with Rail's logger
# This used to be the default in old Rails versions: https://github.com/rails/rails/commit/7fcf8590e788cef8b64cc266f75931c418902ca9#diff-f0748f0be8a653eea13369ebb1cadabcad71ede7cfaf20282447e64329817befL86
Rails.cache.logger = Rails.logger

M config/initializers/chewy.rb => config/initializers/chewy.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

enabled         = ENV['ES_ENABLED'] == 'true'
host            = ENV.fetch('ES_HOST') { 'localhost' }
port            = ENV.fetch('ES_PORT') { 9200 }

M config/initializers/content_security_policy.rb => config/initializers/content_security_policy.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

M config/initializers/cookies_serializer.rb => config/initializers/cookies_serializer.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Specify a serializer for the signed and encrypted cookie jars.

M config/initializers/cors.rb => config/initializers/cors.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.

M config/initializers/devise.rb => config/initializers/devise.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'devise/strategies/authenticatable'

Warden::Manager.after_set_user except: :fetch do |user, warden|

M config/initializers/doorkeeper.rb => config/initializers/doorkeeper.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Doorkeeper.configure do
  # Change the ORM that doorkeeper will use (needs plugins)
  orm :active_record

M config/initializers/fast_blank.rb => config/initializers/fast_blank.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

if String.method_defined?(:blank_as?)
  class String
    alias blank? blank_as?

M config/initializers/ffmpeg.rb => config/initializers/ffmpeg.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

if ENV['FFMPEG_BINARY'].present?
  FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY']
end

M config/initializers/filter_parameter_logging.rb => config/initializers/filter_parameter_logging.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Configure sensitive parameters which will be filtered from the log file.

M config/initializers/http_client_proxy.rb => config/initializers/http_client_proxy.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Rails.application.configure do
  config.x.http_client_proxy = {}


M config/initializers/httplog.rb => config/initializers/httplog.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

HttpLog.configure do |config|
  config.logger = Rails.logger
  config.color = { color: :yellow }

M config/initializers/inflections.rb => config/initializers/inflections.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# Add new inflection rules using the following format. Inflections

M config/initializers/mail_delivery_job.rb => config/initializers/mail_delivery_job.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

ActionMailer::MailDeliveryJob.class_eval do
  discard_on ActiveJob::DeserializationError
end

D config/initializers/makara.rb => config/initializers/makara.rb +0 -2
@@ 1,2 0,0 @@
Makara::Cookie::DEFAULT_OPTIONS[:same_site] = :lax
Makara::Cookie::DEFAULT_OPTIONS[:secure]    = Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'

M config/initializers/mime_types.rb => config/initializers/mime_types.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

Mime::Type.register 'application/json', :json, %w(text/x-json application/jsonrequest application/jrd+json application/activity+json application/ld+json)

M config/initializers/oj.rb => config/initializers/oj.rb +2 -0
@@ 1,1 1,3 @@
# frozen_string_literal: true

Oj.default_options = { mode: :compat, time_format: :ruby, use_to_json: true }

M config/initializers/omniauth.rb => config/initializers/omniauth.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

Rails.application.config.middleware.use OmniAuth::Builder do
  # Vanilla omniauth strategies
end

M config/initializers/open_uri_redirection.rb => config/initializers/open_uri_redirection.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'open-uri'

module OpenURI

M config/initializers/permissions_policy.rb => config/initializers/permissions_policy.rb +1 -0
@@ 1,3 1,4 @@
# frozen_string_literal: true
# Define an application-wide HTTP permissions policy. For further
# information see https://developers.google.com/web/updates/2018/06/feature-policy
#

M config/initializers/pghero.rb => config/initializers/pghero.rb +2 -0
@@ 1,1 1,3 @@
# frozen_string_literal: true

PgHero.show_migrations = Rails.env.development?

M config/initializers/preload_link_headers.rb => config/initializers/preload_link_headers.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Since Rails 6.1, ActionView adds preload links for javascript files
# in the Links header per default.


M config/initializers/premailer_rails.rb => config/initializers/premailer_rails.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require_relative '../../lib/mastodon/premailer_webpack_strategy'

Premailer::Rails.config.merge!(remove_ids: true,

M config/initializers/rack_attack.rb => config/initializers/rack_attack.rb +2 -2
@@ 5,9 5,9 @@ require 'doorkeeper/grape/authorization_decorator'
class Rack::Attack
  class Request
    def authenticated_token
      return @token if defined?(@token)
      return @authenticated_token if defined?(@authenticated_token)

      @token = Doorkeeper::OAuth::Token.authenticate(
      @authenticated_token = Doorkeeper::OAuth::Token.authenticate(
        Doorkeeper::Grape::AuthorizationDecorator.new(self),
        *Doorkeeper.configuration.access_token_methods
      )

M config/initializers/rack_attack_logging.rb => config/initializers/rack_attack_logging.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
  req = payload[:request]


M config/initializers/redis.rb => config/initializers/redis.rb +2 -0
@@ 1,1 1,3 @@
# frozen_string_literal: true

Redis.sadd_returns_boolean = false

M config/initializers/session_store.rb => config/initializers/session_store.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

Rails.application.config.session_store :cookie_store,

M config/initializers/simple_form.rb => config/initializers/simple_form.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Use this setup block to configure all options available in SimpleForm.

module AppendComponent

M config/initializers/stoplight.rb => config/initializers/stoplight.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'stoplight'

Rails.application.reloader.to_prepare do

M config/initializers/trusted_proxies.rb => config/initializers/trusted_proxies.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

module Rack
  class Request
    def trusted_proxy?(ip)

M config/initializers/twitter_regex.rb => config/initializers/twitter_regex.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

module Twitter::TwitterText
  class Configuration
    def emoji_parsing_enabled

M config/initializers/webauthn.rb => config/initializers/webauthn.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

WebAuthn.configure do |config|
  # This value needs to match `window.location.origin` evaluated by
  # the User Agent during registration and authentication ceremonies.

M config/initializers/wrap_parameters.rb => config/initializers/wrap_parameters.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Be sure to restart your server when you modify this file.

# This file contains settings for ActionController::ParamsWrapper which

M config/locales/sr-Latn.rb => config/locales/sr-Latn.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'rails_i18n/common_pluralizations/romanian'

::RailsI18n::Pluralization::Romanian.with_locale(:'sr-Latn')

M config/locales/sr.rb => config/locales/sr.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'rails_i18n/common_pluralizations/romanian'

::RailsI18n::Pluralization::Romanian.with_locale(:sr)

M config/puma.rb => config/puma.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

persistent_timeout ENV.fetch('PERSISTENT_TIMEOUT') { 20 }.to_i

max_threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i

M config/routes/admin.rb => config/routes/admin.rb +1 -1
@@ 68,7 68,7 @@ namespace :admin do
    end
  end

  resources :instances, only: [:index, :show, :destroy], constraints: { id: %r{[^/]+} } do
  resources :instances, only: [:index, :show, :destroy], constraints: { id: %r{[^/]+} }, format: 'html' do
    member do
      post :clear_delivery_errors
      post :restart_delivery

M db/migrate/20160220174730_create_accounts.rb => db/migrate/20160220174730_create_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccounts < ActiveRecord::Migration[4.2]
  def change
    create_table :accounts do |t|

M db/migrate/20160220211917_create_statuses.rb => db/migrate/20160220211917_create_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatuses < ActiveRecord::Migration[4.2]
  def change
    create_table :statuses do |t|

M db/migrate/20160221003140_create_users.rb => db/migrate/20160221003140_create_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateUsers < ActiveRecord::Migration[4.2]
  def change
    create_table :users do |t|

M db/migrate/20160221003621_create_follows.rb => db/migrate/20160221003621_create_follows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFollows < ActiveRecord::Migration[4.2]
  def change
    create_table :follows do |t|

M db/migrate/20160222122600_create_stream_entries.rb => db/migrate/20160222122600_create_stream_entries.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStreamEntries < ActiveRecord::Migration[4.2]
  def change
    create_table :stream_entries do |t|

M db/migrate/20160222143943_add_profile_fields_to_accounts.rb => db/migrate/20160222143943_add_profile_fields_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddProfileFieldsToAccounts < ActiveRecord::Migration[4.2]
  def change
    add_column :accounts, :note, :text, null: false, default: ''

M db/migrate/20160223162837_add_metadata_to_statuses.rb => db/migrate/20160223162837_add_metadata_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddMetadataToStatuses < ActiveRecord::Migration[4.2]
  def change
    add_column :statuses, :in_reply_to_id, :integer, null: true

M db/migrate/20160223164502_make_uris_nullable_in_statuses.rb => db/migrate/20160223164502_make_uris_nullable_in_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MakeUrisNullableInStatuses < ActiveRecord::Migration[4.2]
  def change
    change_column :statuses, :uri, :string, null: true, default: nil

M db/migrate/20160223165723_add_url_to_statuses.rb => db/migrate/20160223165723_add_url_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddURLToStatuses < ActiveRecord::Migration[4.2]
  def change
    add_column :statuses, :url, :string, null: true, default: nil

M db/migrate/20160223165855_add_url_to_accounts.rb => db/migrate/20160223165855_add_url_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddURLToAccounts < ActiveRecord::Migration[4.2]
  def change
    add_column :accounts, :url, :string, null: true, default: nil

M db/migrate/20160223171800_create_favourites.rb => db/migrate/20160223171800_create_favourites.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFavourites < ActiveRecord::Migration[4.2]
  def change
    create_table :favourites do |t|

M db/migrate/20160224223247_create_mentions.rb => db/migrate/20160224223247_create_mentions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateMentions < ActiveRecord::Migration[4.2]
  def change
    create_table :mentions do |t|

M db/migrate/20160227230233_add_attachment_avatar_to_accounts.rb => db/migrate/20160227230233_add_attachment_avatar_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAttachmentAvatarToAccounts < ActiveRecord::Migration[4.2]
  def self.up
    change_table :accounts do |t|

M db/migrate/20160305115639_add_devise_to_users.rb => db/migrate/20160305115639_add_devise_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDeviseToUsers < ActiveRecord::Migration[4.2]
  def self.up
    change_table(:users) do |t|

M db/migrate/20160306172223_create_doorkeeper_tables.rb => db/migrate/20160306172223_create_doorkeeper_tables.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateDoorkeeperTables < ActiveRecord::Migration[4.2]
  def change
    create_table :oauth_applications do |t|

M db/migrate/20160312193225_add_attachment_header_to_accounts.rb => db/migrate/20160312193225_add_attachment_header_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAttachmentHeaderToAccounts < ActiveRecord::Migration[4.2]
  def self.up
    change_table :accounts do |t|

M db/migrate/20160314164231_add_owner_to_application.rb => db/migrate/20160314164231_add_owner_to_application.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddOwnerToApplication < ActiveRecord::Migration[4.2]
  def change
    add_column :oauth_applications, :owner_id, :integer, null: true

M db/migrate/20160316103650_add_missing_indices.rb => db/migrate/20160316103650_add_missing_indices.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddMissingIndices < ActiveRecord::Migration[4.2]
  def change
    add_index :users, :account_id

M db/migrate/20160322193748_add_avatar_remote_url_to_accounts.rb => db/migrate/20160322193748_add_avatar_remote_url_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAvatarRemoteURLToAccounts < ActiveRecord::Migration[4.2]
  def change
    add_column :accounts, :avatar_remote_url, :string, null: true, default: nil

M db/migrate/20160325130944_add_admin_to_users.rb => db/migrate/20160325130944_add_admin_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAdminToUsers < ActiveRecord::Migration[4.2]
  def change
    add_column :users, :admin, :boolean, default: false

M db/migrate/20160826155805_add_superapp_to_oauth_applications.rb => db/migrate/20160826155805_add_superapp_to_oauth_applications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSuperappToOauthApplications < ActiveRecord::Migration[5.0]
  def change
    add_column :oauth_applications, :superapp, :boolean, default: false, null: false

M db/migrate/20160905150353_create_media_attachments.rb => db/migrate/20160905150353_create_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateMediaAttachments < ActiveRecord::Migration[5.0]
  def change
    create_table :media_attachments do |t|

M db/migrate/20160919221059_add_subscription_expires_at_to_accounts.rb => db/migrate/20160919221059_add_subscription_expires_at_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSubscriptionExpiresAtToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :subscription_expires_at, :datetime, null: true, default: nil

M db/migrate/20160920003904_remove_verify_token_from_accounts.rb => db/migrate/20160920003904_remove_verify_token_from_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveVerifyTokenFromAccounts < ActiveRecord::Migration[5.0]
  def change
    remove_column :accounts, :verify_token, :string, null: false, default: ''

M db/migrate/20160926213048_remove_owner_from_application.rb => db/migrate/20160926213048_remove_owner_from_application.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveOwnerFromApplication < ActiveRecord::Migration[5.0]
  def change
    remove_index :oauth_applications, [:owner_id, :owner_type]

M db/migrate/20161003142332_add_confirmable_to_users.rb => db/migrate/20161003142332_add_confirmable_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddConfirmableToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :confirmation_token, :string

M db/migrate/20161003145426_create_blocks.rb => db/migrate/20161003145426_create_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateBlocks < ActiveRecord::Migration[5.0]
  def change
    create_table :blocks do |t|

M db/migrate/20161006213403_rails_settings_migration.rb => db/migrate/20161006213403_rails_settings_migration.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

MIGRATION_BASE_CLASS = if ActiveRecord::VERSION::MAJOR >= 5
                         ActiveRecord::Migration[5.0]
                       else

M db/migrate/20161009120834_create_domain_blocks.rb => db/migrate/20161009120834_create_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateDomainBlocks < ActiveRecord::Migration[5.0]
  def change
    create_table :domain_blocks do |t|

M db/migrate/20161027172456_add_silenced_to_accounts.rb => db/migrate/20161027172456_add_silenced_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSilencedToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :silenced, :boolean, null: false, default: false

M db/migrate/20161104173623_create_tags.rb => db/migrate/20161104173623_create_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateTags < ActiveRecord::Migration[5.0]
  def change
    create_table :tags do |t|

M db/migrate/20161105130633_create_statuses_tags_join_table.rb => db/migrate/20161105130633_create_statuses_tags_join_table.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatusesTagsJoinTable < ActiveRecord::Migration[5.0]
  def change
    create_join_table :statuses, :tags do |t|

M db/migrate/20161116162355_add_locale_to_users.rb => db/migrate/20161116162355_add_locale_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLocaleToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :locale, :string

M db/migrate/20161119211120_create_notifications.rb => db/migrate/20161119211120_create_notifications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateNotifications < ActiveRecord::Migration[5.0]
  def change
    create_table :notifications do |t|

M db/migrate/20161122163057_remove_unneeded_indexes.rb => db/migrate/20161122163057_remove_unneeded_indexes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveUnneededIndexes < ActiveRecord::Migration[5.0]
  def change
    remove_index :notifications, name: 'index_notifications_on_account_id'

M db/migrate/20161123093447_add_sensitive_to_statuses.rb => db/migrate/20161123093447_add_sensitive_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSensitiveToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :sensitive, :boolean, default: false

M db/migrate/20161128103007_create_subscriptions.rb => db/migrate/20161128103007_create_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateSubscriptions < ActiveRecord::Migration[5.0]
  def change
    create_table :subscriptions do |t|

M db/migrate/20161130142058_add_last_successful_delivery_at_to_subscriptions.rb => db/migrate/20161130142058_add_last_successful_delivery_at_to_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastSuccessfulDeliveryAtToSubscriptions < ActiveRecord::Migration[5.0]
  def change
    add_column :subscriptions, :last_successful_delivery_at, :datetime, null: true, default: nil

M db/migrate/20161130185319_add_visibility_to_statuses.rb => db/migrate/20161130185319_add_visibility_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddVisibilityToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :visibility, :integer, null: false, default: 0

M db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb => db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddInReplyToAccountIdToStatuses < ActiveRecord::Migration[5.0]
  def up
    add_column :statuses, :in_reply_to_account_id, :integer, null: true, default: nil

M db/migrate/20161203164520_add_from_account_id_to_notifications.rb => db/migrate/20161203164520_add_from_account_id_to_notifications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddFromAccountIdToNotifications < ActiveRecord::Migration[5.0]
  def up
    add_column :notifications, :from_account_id, :integer

M db/migrate/20161205214545_add_suspended_to_accounts.rb => db/migrate/20161205214545_add_suspended_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSuspendedToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :suspended, :boolean, null: false, default: false

M db/migrate/20161221152630_add_hidden_to_stream_entries.rb => db/migrate/20161221152630_add_hidden_to_stream_entries.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddHiddenToStreamEntries < ActiveRecord::Migration[5.0]
  def change
    add_column :stream_entries, :hidden, :boolean, null: false, default: false

M db/migrate/20161222201034_add_locked_to_accounts.rb => db/migrate/20161222201034_add_locked_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLockedToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :locked, :boolean, null: false, default: false

M db/migrate/20161222204147_create_follow_requests.rb => db/migrate/20161222204147_create_follow_requests.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFollowRequests < ActiveRecord::Migration[5.0]
  def change
    create_table :follow_requests do |t|

M db/migrate/20170105224407_add_shortcode_to_media_attachments.rb => db/migrate/20170105224407_add_shortcode_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddShortcodeToMediaAttachments < ActiveRecord::Migration[5.0]
  def up
    add_column :media_attachments, :shortcode, :string, null: true, default: nil

M db/migrate/20170109120109_create_web_settings.rb => db/migrate/20170109120109_create_web_settings.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateWebSettings < ActiveRecord::Migration[5.0]
  def change
    create_table :web_settings do |t|

M db/migrate/20170112154826_migrate_settings.rb => db/migrate/20170112154826_migrate_settings.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateSettings < ActiveRecord::Migration[4.2]
  def up
    remove_index :settings, [:target_type, :target_id, :var]

M db/migrate/20170114194937_add_application_to_statuses.rb => db/migrate/20170114194937_add_application_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddApplicationToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :application_id, :int

M db/migrate/20170114203041_add_website_to_oauth_application.rb => db/migrate/20170114203041_add_website_to_oauth_application.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddWebsiteToOauthApplication < ActiveRecord::Migration[5.0]
  def change
    add_column :oauth_applications, :website, :string

M db/migrate/20170119214911_create_preview_cards.rb => db/migrate/20170119214911_create_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePreviewCards < ActiveRecord::Migration[5.0]
  def change
    create_table :preview_cards do |t|

M db/migrate/20170123162658_add_severity_to_domain_blocks.rb => db/migrate/20170123162658_add_severity_to_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSeverityToDomainBlocks < ActiveRecord::Migration[5.0]
  def change
    add_column :domain_blocks, :severity, :integer, default: 0

M db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb => db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddRejectMediaToDomainBlocks < ActiveRecord::Migration[5.0]
  def change
    add_column :domain_blocks, :reject_media, :boolean

M db/migrate/20170125145934_add_spoiler_text_to_statuses.rb => db/migrate/20170125145934_add_spoiler_text_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSpoilerTextToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :spoiler_text, :text, default: '', null: false

M db/migrate/20170127165745_add_devise_two_factor_to_users.rb => db/migrate/20170127165745_add_devise_two_factor_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDeviseTwoFactorToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :encrypted_otp_secret, :string

M db/migrate/20170205175257_remove_devices.rb => db/migrate/20170205175257_remove_devices.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveDevices < ActiveRecord::Migration[5.0]
  def change
    drop_table :devices if table_exists?(:devices)

M db/migrate/20170209184350_add_reply_to_statuses.rb => db/migrate/20170209184350_add_reply_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddReplyToStatuses < ActiveRecord::Migration[5.0]
  def up
    add_column :statuses, :reply, :boolean, nil: false, default: false

M db/migrate/20170214110202_create_reports.rb => db/migrate/20170214110202_create_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateReports < ActiveRecord::Migration[5.0]
  def change
    create_table :reports do |t|

M db/migrate/20170217012631_add_reblog_of_id_foreign_key_to_statuses.rb => db/migrate/20170217012631_add_reblog_of_id_foreign_key_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddReblogOfIdForeignKeyToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_foreign_key :statuses, :statuses, column: :reblog_of_id, on_delete: :cascade

M db/migrate/20170301222600_create_mutes.rb => db/migrate/20170301222600_create_mutes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateMutes < ActiveRecord::Migration[5.0]
  def change
    create_table :mutes do |t|

M db/migrate/20170303212857_add_last_emailed_at_to_users.rb => db/migrate/20170303212857_add_last_emailed_at_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastEmailedAtToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :last_emailed_at, :datetime, null: true, default: nil

M db/migrate/20170304202101_add_type_to_media_attachments.rb => db/migrate/20170304202101_add_type_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTypeToMediaAttachments < ActiveRecord::Migration[5.0]
  def up
    add_column :media_attachments, :type, :integer, default: 0, null: false

M db/migrate/20170317193015_add_search_index_to_accounts.rb => db/migrate/20170317193015_add_search_index_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSearchIndexToAccounts < ActiveRecord::Migration[5.0]
  def up
    execute 'CREATE INDEX search_index ON accounts USING gin((setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\')));'

M db/migrate/20170318214217_add_header_remote_url_to_accounts.rb => db/migrate/20170318214217_add_header_remote_url_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddHeaderRemoteURLToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :header_remote_url, :string, null: false, default: ''

M db/migrate/20170322021028_add_lowercase_index_to_accounts.rb => db/migrate/20170322021028_add_lowercase_index_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLowercaseIndexToAccounts < ActiveRecord::Migration[5.0]
  def up
    execute 'CREATE INDEX index_accounts_on_username_and_domain_lower ON accounts (lower(username), lower(domain))'

M db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb => db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangePrimaryKeyToBigintOnStatuses < ActiveRecord::Migration[5.0]
  def change
    change_column :statuses, :id, :bigint

M db/migrate/20170322162804_add_search_index_to_tags.rb => db/migrate/20170322162804_add_search_index_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSearchIndexToTags < ActiveRecord::Migration[5.0]
  def up
    execute 'CREATE INDEX hashtag_search_index ON tags USING gin(to_tsvector(\'simple\', tags.name));'

M db/migrate/20170330021336_add_counter_caches.rb => db/migrate/20170330021336_add_counter_caches.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCounterCaches < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :favourites_count, :integer, null: false, default: 0

M db/migrate/20170330163835_create_imports.rb => db/migrate/20170330163835_create_imports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateImports < ActiveRecord::Migration[5.0]
  def change
    create_table :imports do |t|

M db/migrate/20170330164118_add_attachment_data_to_imports.rb => db/migrate/20170330164118_add_attachment_data_to_imports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAttachmentDataToImports < ActiveRecord::Migration[4.2]
  def self.up
    change_table :imports do |t|

M db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb => db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddActionTakenByAccountIdToReports < ActiveRecord::Migration[5.0]
  def change
    add_column :reports, :action_taken_by_account_id, :integer

M db/migrate/20170405112956_add_index_on_mentions_status_id.rb => db/migrate/20170405112956_add_index_on_mentions_status_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexOnMentionsStatusId < ActiveRecord::Migration[5.0]
  def change
    add_index :mentions, :status_id

M db/migrate/20170406215816_add_notifications_and_favourites_indices.rb => db/migrate/20170406215816_add_notifications_and_favourites_indices.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddNotificationsAndFavouritesIndices < ActiveRecord::Migration[5.0]
  def change
    add_index :notifications, [:activity_id, :activity_type]

M db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb => db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastWebfingeredAtToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_column :accounts, :last_webfingered_at, :datetime

M db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb => db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :otp_backup_codes, :string, array: true

M db/migrate/20170414132105_add_language_to_statuses.rb => db/migrate/20170414132105_add_language_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLanguageToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :language, :string, null: false, default: 'en'

M db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb => db/migrate/20170418160728_add_indexes_to_reports_for_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexesToReportsForAccounts < ActiveRecord::Migration[5.0]
  def change
    add_index :reports, :account_id

M db/migrate/20170423005413_add_allowed_languages_to_user.rb => db/migrate/20170423005413_add_allowed_languages_to_user.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAllowedLanguagesToUser < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :allowed_languages, :string, array: true, default: [], null: false

M db/migrate/20170424003227_create_account_domain_blocks.rb => db/migrate/20170424003227_create_account_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountDomainBlocks < ActiveRecord::Migration[5.0]
  def change
    create_table :account_domain_blocks do |t|

M db/migrate/20170424112722_add_status_id_index_to_statuses_tags.rb => db/migrate/20170424112722_add_status_id_index_to_statuses_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddStatusIdIndexToStatusesTags < ActiveRecord::Migration[5.0]
  def change
    add_index :statuses_tags, :status_id

M db/migrate/20170425131920_add_media_attachment_meta.rb => db/migrate/20170425131920_add_media_attachment_meta.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddMediaAttachmentMeta < ActiveRecord::Migration[5.0]
  def change
    add_column :media_attachments, :file_meta, :json

M db/migrate/20170425202925_add_oembed_to_preview_cards.rb => db/migrate/20170425202925_add_oembed_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddOEmbedToPreviewCards < ActiveRecord::Migration[5.0]
  def change
    add_column :preview_cards, :type, :integer, default: 0, null: false

M db/migrate/20170427011934_re_add_owner_to_application.rb => db/migrate/20170427011934_re_add_owner_to_application.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ReAddOwnerToApplication < ActiveRecord::Migration[5.0]
  def change
    add_column :oauth_applications, :owner_id, :integer, null: true

M db/migrate/20170506235850_create_conversations.rb => db/migrate/20170506235850_create_conversations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateConversations < ActiveRecord::Migration[5.0]
  def change
    create_table :conversations, id: :bigserial do |t|

M db/migrate/20170507000211_add_conversation_id_to_statuses.rb => db/migrate/20170507000211_add_conversation_id_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddConversationIdToStatuses < ActiveRecord::Migration[5.0]
  def change
    add_column :statuses, :conversation_id, :bigint, null: true, default: nil

M db/migrate/20170507141759_optimize_index_subscriptions.rb => db/migrate/20170507141759_optimize_index_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class OptimizeIndexSubscriptions < ActiveRecord::Migration[5.0]
  def up
    add_index :subscriptions, [:account_id, :callback_url], unique: true

M db/migrate/20170508230434_create_conversation_mutes.rb => db/migrate/20170508230434_create_conversation_mutes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateConversationMutes < ActiveRecord::Migration[5.0]
  def change
    create_table :conversation_mutes do |t|

M db/migrate/20170516072309_add_index_accounts_on_uri.rb => db/migrate/20170516072309_add_index_accounts_on_uri.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexAccountsOnUri < ActiveRecord::Migration[5.0]
  def change
    add_index :accounts, :uri

M db/migrate/20170520145338_change_language_filter_to_opt_out.rb => db/migrate/20170520145338_change_language_filter_to_opt_out.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeLanguageFilterToOptOut < ActiveRecord::Migration[5.0]
  def change
    remove_index :users, :allowed_languages

M db/migrate/20170601210557_add_index_on_media_attachments_account_id.rb => db/migrate/20170601210557_add_index_on_media_attachments_account_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexOnMediaAttachmentsAccountId < ActiveRecord::Migration[5.1]
  def change
    add_index :media_attachments, :account_id

M db/migrate/20170604144747_add_foreign_keys_for_accounts.rb => db/migrate/20170604144747_add_foreign_keys_for_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddForeignKeysForAccounts < ActiveRecord::Migration[5.1]
  def change
    add_foreign_key :statuses, :accounts, on_delete: :cascade

M db/migrate/20170606113804_change_tag_search_index_to_btree.rb => db/migrate/20170606113804_change_tag_search_index_to_btree.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeTagSearchIndexToBtree < ActiveRecord::Migration[5.1]
  def up
    remove_index :tags, name: :hashtag_search_index

M db/migrate/20170609145826_remove_default_language_from_statuses.rb => db/migrate/20170609145826_remove_default_language_from_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveDefaultLanguageFromStatuses < ActiveRecord::Migration[5.1]
  def change
    change_column :statuses, :language, :string, default: nil, null: true

M db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb => db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddStatusesIndexOnAccountIdId < ActiveRecord::Migration[5.1]
  disable_ddl_transaction!


M db/migrate/20170623152212_create_session_activations.rb => db/migrate/20170623152212_create_session_activations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateSessionActivations < ActiveRecord::Migration[5.1]
  def change
    create_table :session_activations do |t|

M db/migrate/20170624134742_add_description_to_session_activations.rb => db/migrate/20170624134742_add_description_to_session_activations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDescriptionToSessionActivations < ActiveRecord::Migration[5.1]
  def change
    add_column :session_activations, :user_agent, :string, null: false, default: ''

M db/migrate/20170625140443_add_access_token_id_to_session_activations.rb => db/migrate/20170625140443_add_access_token_id_to_session_activations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAccessTokenIdToSessionActivations < ActiveRecord::Migration[5.1]
  def change
    add_column :session_activations, :access_token_id, :integer

M db/migrate/20170711225116_fix_null_booleans.rb => db/migrate/20170711225116_fix_null_booleans.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixNullBooleans < ActiveRecord::Migration[5.1]
  def change
    safety_assured do

M db/migrate/20170713112503_make_tag_search_case_insensitive.rb => db/migrate/20170713112503_make_tag_search_case_insensitive.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MakeTagSearchCaseInsensitive < ActiveRecord::Migration[5.1]
  def up
    remove_index :tags, name: :hashtag_search_index

M db/migrate/20170713175513_create_web_push_subscriptions.rb => db/migrate/20170713175513_create_web_push_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateWebPushSubscriptions < ActiveRecord::Migration[5.1]
  def change
    create_table :web_push_subscriptions do |t|

M db/migrate/20170713190709_add_web_push_subscription_to_session_activations.rb => db/migrate/20170713190709_add_web_push_subscription_to_session_activations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddWebPushSubscriptionToSessionActivations < ActiveRecord::Migration[5.1]
  def change
    add_column :session_activations, :web_push_subscription_id, :integer

M db/migrate/20170714184731_add_domain_to_subscriptions.rb => db/migrate/20170714184731_add_domain_to_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDomainToSubscriptions < ActiveRecord::Migration[5.1]
  def change
    add_column :subscriptions, :domain, :string

M db/migrate/20170716191202_add_hide_notifications_to_mute.rb => db/migrate/20170716191202_add_hide_notifications_to_mute.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddHideNotificationsToMute < ActiveRecord::Migration[5.1]

M db/migrate/20170718211102_add_activitypub_to_accounts.rb => db/migrate/20170718211102_add_activitypub_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddActivityPubToAccounts < ActiveRecord::Migration[5.1]
  def change
    add_column :accounts, :inbox_url, :string, null: false, default: ''

M db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb => db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexFavouritesOnAccountIdAndId < ActiveRecord::Migration[5.1]
  def change
    # Used to query favourites of an account ordered by id.

M db/migrate/20170823162448_create_status_pins.rb => db/migrate/20170823162448_create_status_pins.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatusPins < ActiveRecord::Migration[5.1]
  def change
    create_table :status_pins do |t|

M db/migrate/20170824103029_add_timestamps_to_status_pins.rb => db/migrate/20170824103029_add_timestamps_to_status_pins.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTimestampsToStatusPins < ActiveRecord::Migration[5.1]
  def change
    add_timestamps :status_pins, null: false, default: -> { 'CURRENT_TIMESTAMP' }

M db/migrate/20170829215220_remove_status_pins_account_index.rb => db/migrate/20170829215220_remove_status_pins_account_index.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveStatusPinsAccountIndex < ActiveRecord::Migration[5.1]
  def change
    remove_index :status_pins, :account_id

M db/migrate/20170901141119_truncate_preview_cards.rb => db/migrate/20170901141119_truncate_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class TruncatePreviewCards < ActiveRecord::Migration[5.1]
  def up
    rename_table :preview_cards, :deprecated_preview_cards

M db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb => db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateJoinTablePreviewCardsStatuses < ActiveRecord::Migration[5.1]
  def change
    create_join_table :preview_cards, :statuses do |t|

M db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb => db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexIdAccountIdActivityTypeOnNotifications < ActiveRecord::Migration[5.1]
  def change
    add_index :notifications, [:id, :account_id, :activity_type], order: { id: :desc }

M db/migrate/20170905165803_add_local_to_statuses.rb => db/migrate/20170905165803_add_local_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLocalToStatuses < ActiveRecord::Migration[5.1]
  def change
    add_column :statuses, :local, :boolean, null: true, default: nil

M db/migrate/20170913000752_create_site_uploads.rb => db/migrate/20170913000752_create_site_uploads.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateSiteUploads < ActiveRecord::Migration[5.1]
  def change
    create_table :site_uploads do |t|

M db/migrate/20170917153509_create_custom_emojis.rb => db/migrate/20170917153509_create_custom_emojis.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateCustomEmojis < ActiveRecord::Migration[5.1]
  def change
    create_table :custom_emojis do |t|

M db/migrate/20170918125918_ids_to_bigints.rb => db/migrate/20170918125918_ids_to_bigints.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require_relative '../../lib/mastodon/migration_helpers'
require_relative '../../lib/mastodon/migration_warning'


M db/migrate/20170920024819_status_ids_to_timestamp_ids.rb => db/migrate/20170920024819_status_ids_to_timestamp_ids.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class StatusIdsToTimestampIds < ActiveRecord::Migration[5.1]
  def up
    # Prepare the function we will use to generate IDs.

M db/migrate/20170920032311_fix_reblogs_in_feeds.rb => db/migrate/20170920032311_fix_reblogs_in_feeds.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixReblogsInFeeds < ActiveRecord::Migration[5.1]
  def up
    redis = RedisConfiguration.pool.checkout

M db/migrate/20170924022025_ids_to_bigints2.rb => db/migrate/20170924022025_ids_to_bigints2.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class IdsToBigints2 < ActiveRecord::Migration[5.1]
  def up
    change_column :statuses_tags, :tag_id, :bigint

M db/migrate/20170927215609_add_description_to_media_attachments.rb => db/migrate/20170927215609_add_description_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDescriptionToMediaAttachments < ActiveRecord::Migration[5.2]
  def change
    add_column :media_attachments, :description, :text

M db/migrate/20170928082043_create_email_domain_blocks.rb => db/migrate/20170928082043_create_email_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateEmailDomainBlocks < ActiveRecord::Migration[5.2]
  def change
    create_table :email_domain_blocks do |t|

M db/migrate/20171005102658_create_account_moderation_notes.rb => db/migrate/20171005102658_create_account_moderation_notes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountModerationNotes < ActiveRecord::Migration[5.2]
  def change
    create_table :account_moderation_notes do |t|

M db/migrate/20171005171936_add_disabled_to_custom_emojis.rb => db/migrate/20171005171936_add_disabled_to_custom_emojis.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddDisabledToCustomEmojis < ActiveRecord::Migration[5.2]

M db/migrate/20171006142024_add_uri_to_custom_emojis.rb => db/migrate/20171006142024_add_uri_to_custom_emojis.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddUriToCustomEmojis < ActiveRecord::Migration[5.2]
  def change
    add_column :custom_emojis, :uri, :string

M db/migrate/20171010023049_add_foreign_key_to_account_moderation_notes.rb => db/migrate/20171010023049_add_foreign_key_to_account_moderation_notes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddForeignKeyToAccountModerationNotes < ActiveRecord::Migration[5.2]
  def change
    safety_assured { add_foreign_key :account_moderation_notes, :accounts }

M db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb => db/migrate/20171010025614_change_accounts_nonnullable_in_account_moderation_notes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20171020084748_add_visible_in_picker_to_custom_emoji.rb => db/migrate/20171020084748_add_visible_in_picker_to_custom_emoji.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddVisibleInPickerToCustomEmoji < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20171028221157_add_reblogs_to_follows.rb => db/migrate/20171028221157_add_reblogs_to_follows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddReblogsToFollows < ActiveRecord::Migration[5.2]

M db/migrate/20171107143332_add_memorial_to_accounts.rb => db/migrate/20171107143332_add_memorial_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddMemorialToAccounts < ActiveRecord::Migration[5.2]

M db/migrate/20171107143624_add_disabled_to_users.rb => db/migrate/20171107143624_add_disabled_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddDisabledToUsers < ActiveRecord::Migration[5.2]

M db/migrate/20171109012327_add_moderator_to_accounts.rb => db/migrate/20171109012327_add_moderator_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddModeratorToAccounts < ActiveRecord::Migration[5.2]

M db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb => db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexDomainToEmailDomainBlocks < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20171114231651_create_lists.rb => db/migrate/20171114231651_create_lists.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateLists < ActiveRecord::Migration[5.2]
  def change
    create_table :lists do |t|

M db/migrate/20171116161857_create_list_accounts.rb => db/migrate/20171116161857_create_list_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateListAccounts < ActiveRecord::Migration[5.2]
  def change
    create_table :list_accounts do |t|

M db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb => db/migrate/20171118012443_add_moved_to_account_id_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddMovedToAccountIdToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :moved_to_account_id, :bigint, null: true, default: nil

M db/migrate/20171119172437_create_admin_action_logs.rb => db/migrate/20171119172437_create_admin_action_logs.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAdminActionLogs < ActiveRecord::Migration[5.2]
  def change
    create_table :admin_action_logs do |t|

M db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb => db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexAccountAndReblogOfIdToStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20171125024930_create_invites.rb => db/migrate/20171125024930_create_invites.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateInvites < ActiveRecord::Migration[5.2]
  def change
    create_table :invites do |t|

M db/migrate/20171125031751_add_invite_id_to_users.rb => db/migrate/20171125031751_add_invite_id_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddInviteIdToUsers < ActiveRecord::Migration[5.2]
  def change
    safety_assured { add_reference :users, :invite, null: true, default: nil, foreign_key: { on_delete: :nullify }, index: false }

M db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb => db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexReblogOfIdAndAccountToStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb => db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveOldReblogIndexOnStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20171129172043_add_index_on_stream_entries.rb => db/migrate/20171129172043_add_index_on_stream_entries.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexOnStreamEntries < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20171130000000_add_embed_url_to_preview_cards.rb => db/migrate/20171130000000_add_embed_url_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddEmbedURLToPreviewCards < ActiveRecord::Migration[5.2]

M db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb => db/migrate/20171201000000_change_account_id_nonnullable_in_lists.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb => db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveDuplicateIndexesInLists < ActiveRecord::Migration[5.2]
  def change
    remove_index :list_accounts, name: 'index_list_accounts_on_account_id'

M db/migrate/20171226094803_more_faster_index_on_notifications.rb => db/migrate/20171226094803_more_faster_index_on_notifications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MoreFasterIndexOnNotifications < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb => db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexOnStatusesForApiV1AccountsAccountIdStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180109143959_add_remember_token_to_users.rb => db/migrate/20180109143959_add_remember_token_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddRememberTokenToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :remember_token, :string, null: true

M db/migrate/20180204034416_create_identities.rb => db/migrate/20180204034416_create_identities.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateIdentities < ActiveRecord::Migration[5.2]
  def change
    create_table :identities, id: :integer do |t|

M db/migrate/20180206000000_change_user_id_nonnullable.rb => db/migrate/20180206000000_change_user_id_nonnullable.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeUserIdNonnullable < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20180211015820_create_backups.rb => db/migrate/20180211015820_create_backups.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateBackups < ActiveRecord::Migration[5.2]
  def change
    create_table :backups do |t|

M db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb => db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddFeaturedCollectionURLToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :featured_collection_url, :string

M db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb => db/migrate/20180310000000_change_columns_in_notifications_nonnullable.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20180402031200_add_assigned_account_id_to_reports.rb => db/migrate/20180402031200_add_assigned_account_id_to_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAssignedAccountIdToReports < ActiveRecord::Migration[5.2]
  def change
    safety_assured { add_reference :reports, :assigned_account, null: true, default: nil, foreign_key: { on_delete: :nullify, to_table: :accounts }, index: false }

M db/migrate/20180402040909_create_report_notes.rb => db/migrate/20180402040909_create_report_notes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateReportNotes < ActiveRecord::Migration[5.2]
  def change
    create_table :report_notes do |t|

M db/migrate/20180410204633_add_fields_to_accounts.rb => db/migrate/20180410204633_add_fields_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddFieldsToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :fields, :jsonb

M db/migrate/20180416210259_add_uri_to_relationships.rb => db/migrate/20180416210259_add_uri_to_relationships.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddUriToRelationships < ActiveRecord::Migration[5.2]
  def change
    add_column :follows, :uri, :string

M db/migrate/20180506221944_add_actor_type_to_accounts.rb => db/migrate/20180506221944_add_actor_type_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddActorTypeToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :actor_type, :string

M db/migrate/20180510214435_add_access_token_id_to_web_push_subscriptions.rb => db/migrate/20180510214435_add_access_token_id_to_web_push_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAccessTokenIdToWebPushSubscriptions < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20180510230049_migrate_web_push_subscriptions.rb => db/migrate/20180510230049_migrate_web_push_subscriptions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateWebPushSubscriptions < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180528141303_fix_accounts_unique_index.rb => db/migrate/20180528141303_fix_accounts_unique_index.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require_relative '../../lib/mastodon/migration_warning'

class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2]

M db/migrate/20180608213548_reject_following_blocked_users.rb => db/migrate/20180608213548_reject_following_blocked_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RejectFollowingBlockedUsers < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180609104432_migrate_web_push_subscriptions2.rb => db/migrate/20180609104432_migrate_web_push_subscriptions2.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateWebPushSubscriptions2 < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180615122121_add_autofollow_to_invites.rb => db/migrate/20180615122121_add_autofollow_to_invites.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddAutofollowToInvites < ActiveRecord::Migration[5.2]

M db/migrate/20180616192031_add_chosen_languages_to_users.rb => db/migrate/20180616192031_add_chosen_languages_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddChosenLanguagesToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :chosen_languages, :string, array: true, null: true, default: nil

M db/migrate/20180617162849_remove_unused_indexes.rb => db/migrate/20180617162849_remove_unused_indexes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveUnusedIndexes < ActiveRecord::Migration[5.2]
  def change
    remove_index :statuses, name: 'index_statuses_on_conversation_id'

M db/migrate/20180628181026_create_custom_filters.rb => db/migrate/20180628181026_create_custom_filters.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateCustomFilters < ActiveRecord::Migration[5.2]
  def change
    create_table :custom_filters do |t|

M db/migrate/20180707154237_add_whole_word_to_custom_filter.rb => db/migrate/20180707154237_add_whole_word_to_custom_filter.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddWholeWordToCustomFilter < ActiveRecord::Migration[5.2]

M db/migrate/20180711152640_create_relays.rb => db/migrate/20180711152640_create_relays.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateRelays < ActiveRecord::Migration[5.2]
  def change
    create_table :relays do |t|

M db/migrate/20180808175627_create_account_pins.rb => db/migrate/20180808175627_create_account_pins.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountPins < ActiveRecord::Migration[5.2]
  def change
    create_table :account_pins do |t|

M db/migrate/20180812123222_change_relays_enabled.rb => db/migrate/20180812123222_change_relays_enabled.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeRelaysEnabled < ActiveRecord::Migration[5.2]
  def up
    # The relays table is supposed to be very small,

M db/migrate/20180812162710_create_status_stats.rb => db/migrate/20180812162710_create_status_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatusStats < ActiveRecord::Migration[5.2]
  def change
    create_table :status_stats do |t|

M db/migrate/20180812173710_copy_status_stats.rb => db/migrate/20180812173710_copy_status_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CopyStatusStats < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20180814171349_add_confidential_to_doorkeeper_application.rb => db/migrate/20180814171349_add_confidential_to_doorkeeper_application.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddConfidentialToDoorkeeperApplication < ActiveRecord::Migration[5.2]

M db/migrate/20180831171112_create_bookmarks.rb => db/migrate/20180831171112_create_bookmarks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# This migration is a duplicate of 20180410220657 and may get ignored, see
# config/initializers/0_duplicate_migrations.rb


M db/migrate/20180929222014_create_account_conversations.rb => db/migrate/20180929222014_create_account_conversations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountConversations < ActiveRecord::Migration[5.2]
  def change
    create_table :account_conversations do |t|

M db/migrate/20181007025445_create_pghero_space_stats.rb => db/migrate/20181007025445_create_pghero_space_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePgheroSpaceStats < ActiveRecord::Migration[5.2]
  def change
    create_table :pghero_space_stats do |t|

M db/migrate/20181010141500_add_silent_to_mentions.rb => db/migrate/20181010141500_add_silent_to_mentions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddSilentToMentions < ActiveRecord::Migration[5.2]

M db/migrate/20181017170937_add_reject_reports_to_domain_blocks.rb => db/migrate/20181017170937_add_reject_reports_to_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddRejectReportsToDomainBlocks < ActiveRecord::Migration[5.2]

M db/migrate/20181018205649_add_unread_to_account_conversations.rb => db/migrate/20181018205649_add_unread_to_account_conversations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddUnreadToAccountConversations < ActiveRecord::Migration[5.2]

M db/migrate/20181024224956_migrate_account_conversations.rb => db/migrate/20181024224956_migrate_account_conversations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require_relative '../../lib/mastodon/migration_warning'

class MigrateAccountConversations < ActiveRecord::Migration[5.2]

M db/migrate/20181026034033_remove_faux_remote_account_duplicates.rb => db/migrate/20181026034033_remove_faux_remote_account_duplicates.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveFauxRemoteAccountDuplicates < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20181116165755_create_account_stats.rb => db/migrate/20181116165755_create_account_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountStats < ActiveRecord::Migration[5.2]
  def change
    create_table :account_stats do |t|

M db/migrate/20181116173541_copy_account_stats.rb => db/migrate/20181116173541_copy_account_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CopyAccountStats < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20181127130500_identity_id_to_bigint.rb => db/migrate/20181127130500_identity_id_to_bigint.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class IdentityIdToBigint < ActiveRecord::Migration[5.2]

M db/migrate/20181127165847_add_show_replies_to_lists.rb => db/migrate/20181127165847_add_show_replies_to_lists.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddShowRepliesToLists < ActiveRecord::Migration[5.2]

M db/migrate/20181203003808_create_accounts_tags_join_table.rb => db/migrate/20181203003808_create_accounts_tags_join_table.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountsTagsJoinTable < ActiveRecord::Migration[5.2]
  def change
    create_join_table :accounts, :tags do |t|

M db/migrate/20181203021853_add_discoverable_to_accounts.rb => db/migrate/20181203021853_add_discoverable_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDiscoverableToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :discoverable, :boolean

M db/migrate/20181204193439_add_last_status_at_to_account_stats.rb => db/migrate/20181204193439_add_last_status_at_to_account_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastStatusAtToAccountStats < ActiveRecord::Migration[5.2]
  def change
    add_column :account_stats, :last_status_at, :datetime

M db/migrate/20181204215309_create_account_tag_stats.rb => db/migrate/20181204215309_create_account_tag_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountTagStats < ActiveRecord::Migration[5.2]
  def change
    create_table :account_tag_stats do |t|

M db/migrate/20181207011115_downcase_custom_emoji_domains.rb => db/migrate/20181207011115_downcase_custom_emoji_domains.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20181213184704_create_account_warnings.rb => db/migrate/20181213184704_create_account_warnings.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountWarnings < ActiveRecord::Migration[5.2]
  def change
    create_table :account_warnings do |t|

M db/migrate/20181213185533_create_account_warning_presets.rb => db/migrate/20181213185533_create_account_warning_presets.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountWarningPresets < ActiveRecord::Migration[5.2]
  def change
    create_table :account_warning_presets do |t|

M db/migrate/20181219235220_add_created_by_application_id_to_users.rb => db/migrate/20181219235220_add_created_by_application_id_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCreatedByApplicationIdToUsers < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20181226021420_add_also_known_as_to_accounts.rb => db/migrate/20181226021420_add_also_known_as_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddAlsoKnownAsToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :also_known_as, :string, array: true

M db/migrate/20190103124649_create_scheduled_statuses.rb => db/migrate/20190103124649_create_scheduled_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateScheduledStatuses < ActiveRecord::Migration[5.2]
  def change
    create_table :scheduled_statuses do |t|

M db/migrate/20190103124754_add_scheduled_status_id_to_media_attachments.rb => db/migrate/20190103124754_add_scheduled_status_id_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddScheduledStatusIdToMediaAttachments < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20190117114553_create_tombstones.rb => db/migrate/20190117114553_create_tombstones.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateTombstones < ActiveRecord::Migration[5.2]
  def change
    create_table :tombstones do |t|

M db/migrate/20190201012802_add_overwrite_to_imports.rb => db/migrate/20190201012802_add_overwrite_to_imports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddOverwriteToImports < ActiveRecord::Migration[5.2]

M db/migrate/20190203180359_create_featured_tags.rb => db/migrate/20190203180359_create_featured_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFeaturedTags < ActiveRecord::Migration[5.2]
  def change
    create_table :featured_tags do |t|

M db/migrate/20190225031541_create_polls.rb => db/migrate/20190225031541_create_polls.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePolls < ActiveRecord::Migration[5.2]
  def change
    create_table :polls do |t|

M db/migrate/20190225031625_create_poll_votes.rb => db/migrate/20190225031625_create_poll_votes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePollVotes < ActiveRecord::Migration[5.2]
  def change
    create_table :poll_votes do |t|

M db/migrate/20190226003449_add_poll_id_to_statuses.rb => db/migrate/20190226003449_add_poll_id_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddPollIdToStatuses < ActiveRecord::Migration[5.2]
  def change
    add_column :statuses, :poll_id, :bigint

M db/migrate/20190304152020_add_uri_to_poll_votes.rb => db/migrate/20190304152020_add_uri_to_poll_votes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddUriToPollVotes < ActiveRecord::Migration[5.2]
  def change
    add_column :poll_votes, :uri, :string

M db/migrate/20190306145741_add_lock_version_to_polls.rb => db/migrate/20190306145741_add_lock_version_to_polls.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddLockVersionToPolls < ActiveRecord::Migration[5.2]

M db/migrate/20190307234537_add_approved_to_users.rb => db/migrate/20190307234537_add_approved_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddApprovedToUsers < ActiveRecord::Migration[5.2]

M db/migrate/20190314181829_migrate_open_registrations_setting.rb => db/migrate/20190314181829_migrate_open_registrations_setting.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateOpenRegistrationsSetting < ActiveRecord::Migration[5.2]
  def up
    open_registrations = Setting.find_by(var: 'open_registrations')

M db/migrate/20190316190352_create_account_identity_proofs.rb => db/migrate/20190316190352_create_account_identity_proofs.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountIdentityProofs < ActiveRecord::Migration[5.2]
  def change
    create_table :account_identity_proofs do |t|

M db/migrate/20190317135723_add_uri_to_reports.rb => db/migrate/20190317135723_add_uri_to_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddUriToReports < ActiveRecord::Migration[5.2]
  def change
    add_column :reports, :uri, :string

M db/migrate/20190403141604_add_comment_to_invites.rb => db/migrate/20190403141604_add_comment_to_invites.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCommentToInvites < ActiveRecord::Migration[5.2]
  def change
    add_column :invites, :comment, :text

M db/migrate/20190409054914_create_user_invite_requests.rb => db/migrate/20190409054914_create_user_invite_requests.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateUserInviteRequests < ActiveRecord::Migration[5.2]
  def change
    create_table :user_invite_requests do |t|

M db/migrate/20190420025523_add_blurhash_to_media_attachments.rb => db/migrate/20190420025523_add_blurhash_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddBlurhashToMediaAttachments < ActiveRecord::Migration[5.2]
  def change
    add_column :media_attachments, :blurhash, :string

M db/migrate/20190509164208_add_by_moderator_to_tombstone.rb => db/migrate/20190509164208_add_by_moderator_to_tombstone.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddByModeratorToTombstone < ActiveRecord::Migration[5.2]
  def change
    add_column :tombstones, :by_moderator, :boolean

M db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb => db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSilencedAtSuspendedAtToAccounts < ActiveRecord::Migration[5.2]
  class Account < ApplicationRecord
    # Dummy class, to make migration possible across version changes

M db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb => db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class PreserveOldLayoutForExistingUsers < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20190627222225_create_custom_emoji_categories.rb => db/migrate/20190627222225_create_custom_emoji_categories.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateCustomEmojiCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :custom_emoji_categories do |t|

M db/migrate/20190627222826_add_category_id_to_custom_emojis.rb => db/migrate/20190627222826_add_category_id_to_custom_emojis.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCategoryIdToCustomEmojis < ActiveRecord::Migration[5.2]
  def change
    add_column :custom_emojis, :category_id, :bigint

M db/migrate/20190701022101_add_trust_level_to_accounts.rb => db/migrate/20190701022101_add_trust_level_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTrustLevelToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :trust_level, :integer

M db/migrate/20190705002136_create_domain_allows.rb => db/migrate/20190705002136_create_domain_allows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateDomainAllows < ActiveRecord::Migration[5.2]
  def change
    create_table :domain_allows do |t|

M db/migrate/20190715164535_add_instance_actor.rb => db/migrate/20190715164535_add_instance_actor.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddInstanceActor < ActiveRecord::Migration[5.2]
  class Account < ApplicationRecord
    # Dummy class, to make migration possible across version changes

M db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb => db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb +3 -1
@@ 1,10 1,12 @@
# frozen_string_literal: true

class AddCaseInsensitiveIndexToTags < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!

  def up
    Tag.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM tags GROUP BY lower(name) HAVING count(*) > 1').to_ary.each do |row|
      canonical_tag_id  = row['ids'].split(',').first
      redundant_tag_ids = row['ids'].split(',')[1..-1]
      redundant_tag_ids = row['ids'].split(',')[1..]

      safety_assured do
        execute "UPDATE accounts_tags AS t0 SET tag_id = #{canonical_tag_id} WHERE tag_id IN (#{redundant_tag_ids.join(', ')}) AND NOT EXISTS (SELECT t1.tag_id FROM accounts_tags AS t1 WHERE t1.tag_id = #{canonical_tag_id} AND t1.account_id = t0.account_id)"

M db/migrate/20190729185330_add_score_to_tags.rb => db/migrate/20190729185330_add_score_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddScoreToTags < ActiveRecord::Migration[5.2]
  def change
    add_column :tags, :score, :int

M db/migrate/20190805123746_add_capabilities_to_tags.rb => db/migrate/20190805123746_add_capabilities_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCapabilitiesToTags < ActiveRecord::Migration[5.2]
  def change
    add_column :tags, :usable, :boolean

M db/migrate/20190807135426_add_comments_to_domain_blocks.rb => db/migrate/20190807135426_add_comments_to_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddCommentsToDomainBlocks < ActiveRecord::Migration[5.2]
  def change
    add_column :domain_blocks, :private_comment, :text

M db/migrate/20190815225426_add_last_status_at_to_tags.rb => db/migrate/20190815225426_add_last_status_at_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastStatusAtToTags < ActiveRecord::Migration[5.2]
  def change
    add_column :tags, :last_status_at, :datetime

M db/migrate/20190819134503_add_deleted_at_to_statuses.rb => db/migrate/20190819134503_add_deleted_at_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDeletedAtToStatuses < ActiveRecord::Migration[5.2]
  def change
    add_column :statuses, :deleted_at, :datetime

M db/migrate/20190820003045_update_statuses_index.rb => db/migrate/20190820003045_update_statuses_index.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class UpdateStatusesIndex < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20190823221802_add_local_index_to_statuses.rb => db/migrate/20190823221802_add_local_index_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLocalIndexToStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20190901035623_add_max_score_to_tags.rb => db/migrate/20190901035623_add_max_score_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddMaxScoreToTags < ActiveRecord::Migration[5.2]
  def change
    add_column :tags, :max_score, :float

M db/migrate/20190904222339_create_markers.rb => db/migrate/20190904222339_create_markers.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateMarkers < ActiveRecord::Migration[5.2]
  def change
    create_table :markers do |t|

M db/migrate/20190914202517_create_account_migrations.rb => db/migrate/20190914202517_create_account_migrations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountMigrations < ActiveRecord::Migration[5.2]
  def change
    create_table :account_migrations do |t|

M db/migrate/20190915194355_create_account_aliases.rb => db/migrate/20190915194355_create_account_aliases.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountAliases < ActiveRecord::Migration[5.2]
  def change
    create_table :account_aliases do |t|

M db/migrate/20190927232842_add_voters_count_to_polls.rb => db/migrate/20190927232842_add_voters_count_to_polls.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddVotersCountToPolls < ActiveRecord::Migration[5.2]
  def change
    add_column :polls, :voters_count, :bigint

M db/migrate/20191001213028_add_lock_version_to_account_stats.rb => db/migrate/20191001213028_add_lock_version_to_account_stats.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddLockVersionToAccountStats < ActiveRecord::Migration[5.2]

M db/migrate/20191007013357_update_pt_locales.rb => db/migrate/20191007013357_update_pt_locales.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class UpdatePtLocales < ActiveRecord::Migration[5.2]
  class User < ApplicationRecord
    # Dummy class, to make migration possible across version changes

M db/migrate/20191031163205_change_list_account_follow_nullable.rb => db/migrate/20191031163205_change_list_account_follow_nullable.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeListAccountFollowNullable < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/migrate/20191212003415_increase_backup_size.rb => db/migrate/20191212003415_increase_backup_size.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class IncreaseBackupSize < ActiveRecord::Migration[5.2]

M db/migrate/20191212163405_add_hide_collections_to_accounts.rb => db/migrate/20191212163405_add_hide_collections_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddHideCollectionsToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :hide_collections, :boolean

M db/migrate/20191218153258_create_announcements.rb => db/migrate/20191218153258_create_announcements.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAnnouncements < ActiveRecord::Migration[5.2]
  def change
    create_table :announcements do |t|

M db/migrate/20200113125135_create_announcement_mutes.rb => db/migrate/20200113125135_create_announcement_mutes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAnnouncementMutes < ActiveRecord::Migration[5.2]
  def change
    create_table :announcement_mutes do |t|

M db/migrate/20200114113335_create_announcement_reactions.rb => db/migrate/20200114113335_create_announcement_reactions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAnnouncementReactions < ActiveRecord::Migration[5.2]
  def change
    create_table :announcement_reactions do |t|

M db/migrate/20200119112504_add_public_index_to_statuses.rb => db/migrate/20200119112504_add_public_index_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddPublicIndexToStatuses < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20200126203551_add_published_at_to_announcements.rb => db/migrate/20200126203551_add_published_at_to_announcements.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddPublishedAtToAnnouncements < ActiveRecord::Migration[5.2]
  def change
    add_column :announcements, :published_at, :datetime

M db/migrate/20200306035625_add_processing_to_media_attachments.rb => db/migrate/20200306035625_add_processing_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddProcessingToMediaAttachments < ActiveRecord::Migration[5.2]
  def change
    add_column :media_attachments, :processing, :integer

M db/migrate/20200309150742_add_forwarded_to_reports.rb => db/migrate/20200309150742_add_forwarded_to_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddForwardedToReports < ActiveRecord::Migration[5.2]
  def change
    add_column :reports, :forwarded, :boolean

M db/migrate/20200312144258_add_title_to_account_warning_presets.rb => db/migrate/20200312144258_add_title_to_account_warning_presets.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddTitleToAccountWarningPresets < ActiveRecord::Migration[5.2]

M db/migrate/20200312162302_add_status_ids_to_announcements.rb => db/migrate/20200312162302_add_status_ids_to_announcements.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddStatusIdsToAnnouncements < ActiveRecord::Migration[5.2]
  def change
    add_column :announcements, :status_ids, :bigint, array: true

M db/migrate/20200312185443_add_parent_id_to_email_domain_blocks.rb => db/migrate/20200312185443_add_parent_id_to_email_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddParentIdToEmailDomainBlocks < ActiveRecord::Migration[5.2]
  def change
    safety_assured { add_reference :email_domain_blocks, :parent, null: true, default: nil, foreign_key: { on_delete: :cascade, to_table: :email_domain_blocks }, index: false }

M db/migrate/20200317021758_add_expires_at_to_mutes.rb => db/migrate/20200317021758_add_expires_at_to_mutes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddExpiresAtToMutes < ActiveRecord::Migration[5.2]
  def change
    add_column :mutes, :expires_at, :datetime

M db/migrate/20200407201300_create_unavailable_domains.rb => db/migrate/20200407201300_create_unavailable_domains.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateUnavailableDomains < ActiveRecord::Migration[5.2]
  def change
    create_table :unavailable_domains do |t|

M db/migrate/20200407202420_migrate_unavailable_inboxes.rb => db/migrate/20200407202420_migrate_unavailable_inboxes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateUnavailableInboxes < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20200417125749_add_storage_schema_version.rb => db/migrate/20200417125749_add_storage_schema_version.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddStorageSchemaVersion < ActiveRecord::Migration[5.2]
  def change
    add_column :preview_cards, :image_storage_schema_version, :integer

M db/migrate/20200508212852_reset_unique_jobs_locks.rb => db/migrate/20200508212852_reset_unique_jobs_locks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ResetUniqueJobsLocks < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20200510110808_reset_web_app_secret.rb => db/migrate/20200510110808_reset_web_app_secret.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ResetWebAppSecret < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20200510181721_remove_duplicated_indexes_pghero.rb => db/migrate/20200510181721_remove_duplicated_indexes_pghero.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveDuplicatedIndexesPghero < ActiveRecord::Migration[5.2]
  def up
    remove_index :account_conversations, name: :index_account_conversations_on_account_id     if index_exists?(:account_conversations, :account_id, name: :index_account_conversations_on_account_id)

M db/migrate/20200516180352_create_devices.rb => db/migrate/20200516180352_create_devices.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateDevices < ActiveRecord::Migration[5.2]
  def change
    create_table :devices do |t|

M db/migrate/20200516183822_create_one_time_keys.rb => db/migrate/20200516183822_create_one_time_keys.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateOneTimeKeys < ActiveRecord::Migration[5.2]
  def change
    create_table :one_time_keys do |t|

M db/migrate/20200518083523_create_encrypted_messages.rb => db/migrate/20200518083523_create_encrypted_messages.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateEncryptedMessages < ActiveRecord::Migration[5.2]
  def change
    create_table :encrypted_messages do |t|

M db/migrate/20200521180606_encrypted_message_ids_to_timestamp_ids.rb => db/migrate/20200521180606_encrypted_message_ids_to_timestamp_ids.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class EncryptedMessageIdsToTimestampIds < ActiveRecord::Migration[5.2]
  def up
    safety_assured do

M db/migrate/20200529214050_add_devices_url_to_accounts.rb => db/migrate/20200529214050_add_devices_url_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDevicesURLToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :devices_url, :string

M db/migrate/20200601222558_create_system_keys.rb => db/migrate/20200601222558_create_system_keys.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateSystemKeys < ActiveRecord::Migration[5.2]
  def change
    create_table :system_keys do |t|

M db/migrate/20200605155027_add_blurhash_to_preview_cards.rb => db/migrate/20200605155027_add_blurhash_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddBlurhashToPreviewCards < ActiveRecord::Migration[5.2]
  def change
    add_column :preview_cards, :blurhash, :string

M db/migrate/20200608113046_add_sign_in_token_to_users.rb => db/migrate/20200608113046_add_sign_in_token_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSignInTokenToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :sign_in_token, :string

M db/migrate/20200614002136_add_sensitized_to_accounts.rb => db/migrate/20200614002136_add_sensitized_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSensitizedToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :sensitized_at, :datetime

M db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb => db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddFixedLowercaseIndexToAccounts < ActiveRecord::Migration[5.2]

M db/migrate/20200622213645_media_attachment_ids_to_timestamp_ids.rb => db/migrate/20200622213645_media_attachment_ids_to_timestamp_ids.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MediaAttachmentIdsToTimestampIds < ActiveRecord::Migration[5.2]
  def up
    # Set up the media_attachments.id column to use our timestamp-based IDs.

M db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb => db/migrate/20200627125810_add_thumbnail_columns_to_media_attachments.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddThumbnailColumnsToMediaAttachments < ActiveRecord::Migration[5.2]
  def up
    add_attachment :media_attachments, :thumbnail

M db/migrate/20200628133322_create_account_notes.rb => db/migrate/20200628133322_create_account_notes.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountNotes < ActiveRecord::Migration[5.2]
  def change
    create_table :account_notes do |t|

M db/migrate/20200630190240_create_webauthn_credentials.rb => db/migrate/20200630190240_create_webauthn_credentials.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateWebauthnCredentials < ActiveRecord::Migration[5.2]
  def change
    create_table :webauthn_credentials do |t|

M db/migrate/20200630190544_add_webauthn_id_to_users.rb => db/migrate/20200630190544_add_webauthn_id_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddWebauthnIdToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :webauthn_id, :string

M db/migrate/20200908193330_create_account_deletion_requests.rb => db/migrate/20200908193330_create_account_deletion_requests.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountDeletionRequests < ActiveRecord::Migration[5.2]
  def change
    create_table :account_deletion_requests do |t|

M db/migrate/20200917192924_add_notify_to_follows.rb => db/migrate/20200917192924_add_notify_to_follows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddNotifyToFollows < ActiveRecord::Migration[5.2]

M db/migrate/20200917193034_add_type_to_notifications.rb => db/migrate/20200917193034_add_type_to_notifications.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTypeToNotifications < ActiveRecord::Migration[5.2]
  def change
    add_column :notifications, :type, :string

M db/migrate/20200917222316_add_index_notifications_on_type.rb => db/migrate/20200917222316_add_index_notifications_on_type.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexNotificationsOnType < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20201008202037_create_ip_blocks.rb => db/migrate/20201008202037_create_ip_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateIpBlocks < ActiveRecord::Migration[5.2]
  def change
    create_table :ip_blocks do |t|

M db/migrate/20201008220312_add_sign_up_ip_to_users.rb => db/migrate/20201008220312_add_sign_up_ip_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSignUpIpToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :sign_up_ip, :inet

M db/migrate/20201017233919_add_suspension_origin_to_accounts.rb => db/migrate/20201017233919_add_suspension_origin_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSuspensionOriginToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :suspension_origin, :integer

M db/migrate/20201206004238_create_instances.rb => db/migrate/20201206004238_create_instances.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateInstances < ActiveRecord::Migration[5.2]
  def change
    create_view :instances, materialized: true

M db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb => db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddObfuscateToDomainBlocks < ActiveRecord::Migration[5.2]

M db/migrate/20210221045109_create_rules.rb => db/migrate/20210221045109_create_rules.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateRules < ActiveRecord::Migration[5.2]
  def change
    create_table :rules do |t|

M db/migrate/20210306164523_account_ids_to_timestamp_ids.rb => db/migrate/20210306164523_account_ids_to_timestamp_ids.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AccountIdsToTimestampIds < ActiveRecord::Migration[5.2]
  def up
    # Set up the accounts.id column to use our timestamp-based IDs.

M db/migrate/20210322164601_create_account_summaries.rb => db/migrate/20210322164601_create_account_summaries.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountSummaries < ActiveRecord::Migration[5.2]
  def change
    create_view :account_summaries, materialized: { no_data: true }

M db/migrate/20210323114347_create_follow_recommendations.rb => db/migrate/20210323114347_create_follow_recommendations.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFollowRecommendations < ActiveRecord::Migration[5.2]
  def change
    create_view :follow_recommendations

M db/migrate/20210324171613_create_follow_recommendation_suppressions.rb => db/migrate/20210324171613_create_follow_recommendation_suppressions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateFollowRecommendationSuppressions < ActiveRecord::Migration[6.1]
  def change
    create_table :follow_recommendation_suppressions do |t|

M db/migrate/20210416200740_create_canonical_email_blocks.rb => db/migrate/20210416200740_create_canonical_email_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateCanonicalEmailBlocks < ActiveRecord::Migration[6.1]
  def change
    create_table :canonical_email_blocks do |t|

M db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb => db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddCaseInsensitiveBtreeIndexToTags < ActiveRecord::Migration[5.2]

M db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb => db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexOnMediaAttachmentsAccountIdStatusId < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/migrate/20210505174616_update_follow_recommendations_to_version_2.rb => db/migrate/20210505174616_update_follow_recommendations_to_version_2.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class UpdateFollowRecommendationsToVersion2 < ActiveRecord::Migration[6.1]
  # We're switching from a normal to a materialized view so we need
  # custom `up` and `down` paths.

M db/migrate/20210609202149_create_login_activities.rb => db/migrate/20210609202149_create_login_activities.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateLoginActivities < ActiveRecord::Migration[6.1]
  def change
    create_table :login_activities do |t|

M db/migrate/20210616214526_create_user_ips.rb => db/migrate/20210616214526_create_user_ips.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateUserIps < ActiveRecord::Migration[6.1]
  def change
    create_view :user_ips

M db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb => db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddSkipSignInTokenToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :skip_sign_in_token, :boolean

M db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb => db/migrate/20210630000137_fix_canonical_email_blocks_foreign_key.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixCanonicalEmailBlocksForeignKey < ActiveRecord::Migration[6.1]
  def up
    safety_assured do

M db/migrate/20210722120340_create_account_statuses_cleanup_policies.rb => db/migrate/20210722120340_create_account_statuses_cleanup_policies.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAccountStatusesCleanupPolicies < ActiveRecord::Migration[6.1]
  def change
    create_table :account_statuses_cleanup_policies do |t|

M db/migrate/20210904215403_add_edited_at_to_statuses.rb => db/migrate/20210904215403_add_edited_at_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddEditedAtToStatuses < ActiveRecord::Migration[6.1]
  def change
    add_column :statuses, :edited_at, :datetime

M db/migrate/20210908220918_create_status_edits.rb => db/migrate/20210908220918_create_status_edits.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatusEdits < ActiveRecord::Migration[6.1]
  def change
    create_table :status_edits do |t|

M db/migrate/20211031031021_create_preview_card_providers.rb => db/migrate/20211031031021_create_preview_card_providers.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePreviewCardProviders < ActiveRecord::Migration[6.1]
  def change
    create_table :preview_card_providers do |t|

M db/migrate/20211112011713_add_language_to_preview_cards.rb => db/migrate/20211112011713_add_language_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLanguageToPreviewCards < ActiveRecord::Migration[6.1]
  def change
    add_column :preview_cards, :language, :string

M db/migrate/20211115032527_add_trendable_to_preview_cards.rb => db/migrate/20211115032527_add_trendable_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTrendableToPreviewCards < ActiveRecord::Migration[6.1]
  def change
    add_column :preview_cards, :trendable, :boolean

M db/migrate/20211123212714_add_link_type_to_preview_cards.rb => db/migrate/20211123212714_add_link_type_to_preview_cards.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLinkTypeToPreviewCards < ActiveRecord::Migration[6.1]
  def change
    add_column :preview_cards, :link_type, :int

M db/migrate/20211213040746_update_account_summaries_to_version_2.rb => db/migrate/20211213040746_update_account_summaries_to_version_2.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class UpdateAccountSummariesToVersion2 < ActiveRecord::Migration[6.1]
  def up
    reapplication_follow_recommendations_v2 do

M db/migrate/20211231080958_add_category_to_reports.rb => db/migrate/20211231080958_add_category_to_reports.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddCategoryToReports < ActiveRecord::Migration[6.1]

M db/migrate/20220105163928_remove_mentions_status_id_index.rb => db/migrate/20220105163928_remove_mentions_status_id_index.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveMentionsStatusIdIndex < ActiveRecord::Migration[6.1]
  def up
    remove_index :mentions, name: :mentions_status_id_index if index_exists?(:mentions, :status_id, name: :mentions_status_id_index)

M db/migrate/20220115125126_add_report_id_to_account_warnings.rb => db/migrate/20220115125126_add_report_id_to_account_warnings.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddReportIdToAccountWarnings < ActiveRecord::Migration[6.1]
  def change
    safety_assured { add_reference :account_warnings, :report, foreign_key: { on_delete: :cascade }, index: false }

M db/migrate/20220115125341_fix_account_warning_actions.rb => db/migrate/20220115125341_fix_account_warning_actions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixAccountWarningActions < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb => db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDeletedAtIndexOnStatuses < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220124141035_create_appeals.rb => db/migrate/20220124141035_create_appeals.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateAppeals < ActiveRecord::Migration[6.1]
  def change
    create_table :appeals do |t|

M db/migrate/20220202200743_add_trendable_to_accounts.rb => db/migrate/20220202200743_add_trendable_to_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTrendableToAccounts < ActiveRecord::Migration[6.1]
  def change
    add_column :accounts, :trendable, :boolean

M db/migrate/20220202200926_add_trendable_to_statuses.rb => db/migrate/20220202200926_add_trendable_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddTrendableToStatuses < ActiveRecord::Migration[6.1]
  def change
    add_column :statuses, :trendable, :boolean

M db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb => db/migrate/20220210153119_add_overruled_at_to_account_warnings.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddOverruledAtToAccountWarnings < ActiveRecord::Migration[6.1]
  def change
    add_column :account_warnings, :overruled_at, :datetime

M db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb => db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIpsToEmailDomainBlocks < ActiveRecord::Migration[6.1]
  def change
    add_column :email_domain_blocks, :ips, :inet, array: true

M db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb => db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLastUsedAtToOauthAccessTokens < ActiveRecord::Migration[6.1]
  def change
    add_column :oauth_access_tokens, :last_used_at, :datetime

M db/migrate/20220302232632_add_ordered_media_attachment_ids_to_statuses.rb => db/migrate/20220302232632_add_ordered_media_attachment_ids_to_statuses.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddOrderedMediaAttachmentIdsToStatuses < ActiveRecord::Migration[6.1]
  def change
    add_column :statuses, :ordered_media_attachment_ids, :bigint, array: true

M db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb => db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddOrderedMediaAttachmentIdsToStatusEdits < ActiveRecord::Migration[6.1]
  def change
    add_column :status_edits, :ordered_media_attachment_ids, :bigint, array: true

M db/migrate/20220304195405_migrate_hide_network_preference.rb => db/migrate/20220304195405_migrate_hide_network_preference.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class MigrateHideNetworkPreference < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220307094650_fix_featured_tags_constraints.rb => db/migrate/20220307094650_fix_featured_tags_constraints.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixFeaturedTagsConstraints < ActiveRecord::Migration[6.1]
  def up
    safety_assured do

M db/migrate/20220309213005_fix_reblog_deleted_at.rb => db/migrate/20220309213005_fix_reblog_deleted_at.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class FixReblogDeletedAt < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220316233212_update_kurdish_locales.rb => db/migrate/20220316233212_update_kurdish_locales.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class UpdateKurdishLocales < ActiveRecord::Migration[6.1]
  class User < ApplicationRecord
    # Dummy class, to make migration possible across version changes

M db/migrate/20220428112511_add_index_statuses_on_account_id.rb => db/migrate/20220428112511_add_index_statuses_on_account_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexStatusesOnAccountId < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220428112727_add_index_statuses_pins_on_status_id.rb => db/migrate/20220428112727_add_index_statuses_pins_on_status_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexStatusesPinsOnStatusId < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220428114454_add_index_reports_on_assigned_account_id.rb => db/migrate/20220428114454_add_index_reports_on_assigned_account_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexReportsOnAssignedAccountId < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220428114902_add_index_reports_on_action_taken_by_account_id.rb => db/migrate/20220428114902_add_index_reports_on_action_taken_by_account_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexReportsOnActionTakenByAccountId < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220606044941_create_webhooks.rb => db/migrate/20220606044941_create_webhooks.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateWebhooks < ActiveRecord::Migration[6.1]
  def change
    create_table :webhooks do |t|

M db/migrate/20220611210335_create_user_roles.rb => db/migrate/20220611210335_create_user_roles.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateUserRoles < ActiveRecord::Migration[6.1]
  def change
    create_table :user_roles do |t|

M db/migrate/20220611212541_add_role_id_to_users.rb => db/migrate/20220611212541_add_role_id_to_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddRoleIdToUsers < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20220710102457_add_display_name_to_tags.rb => db/migrate/20220710102457_add_display_name_to_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddDisplayNameToTags < ActiveRecord::Migration[6.1]
  def change
    add_column :tags, :display_name, :string

M db/migrate/20220714171049_create_tag_follows.rb => db/migrate/20220714171049_create_tag_follows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateTagFollows < ActiveRecord::Migration[6.1]
  def change
    create_table :tag_follows do |t|

M db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb => db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddHumanIdentifierToAdminActionLogs < ActiveRecord::Migration[6.1]
  def change
    add_column :admin_action_logs, :human_identifier, :string

M db/migrate/20220824233535_create_status_trends.rb => db/migrate/20220824233535_create_status_trends.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreateStatusTrends < ActiveRecord::Migration[6.1]
  def change
    create_table :status_trends do |t|

M db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb => db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class ChangeCanonicalEmailBlocksNullable < ActiveRecord::Migration[6.1]
  def change
    safety_assured { change_column :canonical_email_blocks, :reference_account_id, :bigint, null: true, default: nil }

M db/migrate/20220829192633_add_languages_to_follows.rb => db/migrate/20220829192633_add_languages_to_follows.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLanguagesToFollows < ActiveRecord::Migration[6.1]
  def change
    add_column :follows, :languages, :string, array: true

M db/migrate/20220829192658_add_languages_to_follow_requests.rb => db/migrate/20220829192658_add_languages_to_follow_requests.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddLanguagesToFollowRequests < ActiveRecord::Migration[6.1]
  def change
    add_column :follow_requests, :languages, :string, array: true

M db/migrate/20221006061337_create_preview_card_trends.rb => db/migrate/20221006061337_create_preview_card_trends.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class CreatePreviewCardTrends < ActiveRecord::Migration[6.1]
  def change
    create_table :preview_card_trends do |t|

M db/migrate/20221012181003_add_blurhash_to_site_uploads.rb => db/migrate/20221012181003_add_blurhash_to_site_uploads.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddBlurhashToSiteUploads < ActiveRecord::Migration[6.1]
  def change
    add_column :site_uploads, :blurhash, :string

M db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb => db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexFeaturedTagsOnAccountIdAndTagId < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20221025171544_add_index_ip_blocks_on_ip.rb => db/migrate/20221025171544_add_index_ip_blocks_on_ip.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddIndexIpBlocksOnIp < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!


M db/migrate/20221104133904_add_name_to_featured_tags.rb => db/migrate/20221104133904_add_name_to_featured_tags.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class AddNameToFeaturedTags < ActiveRecord::Migration[6.1]
  def change
    add_column :featured_tags, :name, :string

M db/post_migrate/20190519130537_remove_boosts_widening_audience.rb => db/post_migrate/20190519130537_remove_boosts_widening_audience.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveBoostsWideningAudience < ActiveRecord::Migration[5.2]
  disable_ddl_transaction!


M db/post_migrate/20210308133107_remove_subscription_expires_at_from_accounts.rb => db/post_migrate/20210308133107_remove_subscription_expires_at_from_accounts.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveSubscriptionExpiresAtFromAccounts < ActiveRecord::Migration[5.2]
  def change
    safety_assured do

M db/post_migrate/20220118183123_remove_rememberable_from_users.rb => db/post_migrate/20220118183123_remove_rememberable_from_users.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

class RemoveRememberableFromUsers < ActiveRecord::Migration[6.1]
  def change
    safety_assured do

M db/seeds/01_web_app.rb => db/seeds/01_web_app.rb +2 -0
@@ 1,1 1,3 @@
# frozen_string_literal: true

Doorkeeper::Application.create_with(name: 'Web', redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push').find_or_create_by(superapp: true)

M db/seeds/02_instance_actor.rb => db/seeds/02_instance_actor.rb +2 -0
@@ 1,1 1,3 @@
# frozen_string_literal: true

Account.create_with(actor_type: 'Application', locked: true, username: 'mastodon.internal').find_or_create_by(id: -99)

M db/seeds/03_roles.rb => db/seeds/03_roles.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

# Pre-create base role
UserRole.everyone


M db/seeds/04_admin.rb => db/seeds/04_admin.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

if Rails.env.development?
  domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain


M lib/active_record/batches.rb => lib/active_record/batches.rb +1 -1
@@ 29,7 29,7 @@ module ActiveRecord
          if flatten
            yield record[1]
          else
            yield record[1..-1]
            yield record[1..]
          end
        end


M lib/mastodon/premailer_webpack_strategy.rb => lib/mastodon/premailer_webpack_strategy.rb +1 -1
@@ 12,7 12,7 @@ module PremailerWebpackStrategy
    css = if url.start_with?('http')
            HTTP.get(url).to_s
          else
            url = url[1..-1] if url.start_with?('/')
            url = url[1..] if url.start_with?('/')
            Rails.public_path.join(url).read
          end


M lib/mastodon/snowflake.rb => lib/mastodon/snowflake.rb +52 -40
@@ 64,46 64,7 @@ module Mastodon::Snowflake
    def define_timestamp_id
      return if already_defined?

      connection.execute(<<~SQL)
        CREATE OR REPLACE FUNCTION timestamp_id(table_name text)
        RETURNS bigint AS
        $$
          DECLARE
            time_part bigint;
            sequence_base bigint;
            tail bigint;
          BEGIN
            time_part := (
              -- Get the time in milliseconds
              ((date_part('epoch', now()) * 1000))::bigint
              -- And shift it over two bytes
              << 16);

            sequence_base := (
              'x' ||
              -- Take the first two bytes (four hex characters)
              substr(
                -- Of the MD5 hash of the data we documented
                md5(table_name || '#{SecureRandom.hex(16)}' || time_part::text),
                1, 4
              )
            -- And turn it into a bigint
            )::bit(16)::bigint;

            -- Finally, add our sequence number to our base, and chop
            -- it to the last two bytes
            tail := (
              (sequence_base + nextval(table_name || '_id_seq'))
              & 65535);

            -- Return the time part and the sequence part. OR appears
            -- faster here than addition, but they're equivalent:
            -- time_part has no trailing two bytes, and tail is only
            -- the last two bytes.
            RETURN time_part | tail;
          END
        $$ LANGUAGE plpgsql VOLATILE;
      SQL
      connection.execute(sanitized_timestamp_id_sql)
    end

    def ensure_id_sequences_exist


@@ 153,6 114,57 @@ module Mastodon::Snowflake
      SQL
    end

    def sanitized_timestamp_id_sql
      ActiveRecord::Base.sanitize_sql_array(timestamp_id_sql_array)
    end

    def timestamp_id_sql_array
      [timestamp_id_sql_string, { random_string: SecureRandom.hex(16) }]
    end

    def timestamp_id_sql_string
      <<~SQL
        CREATE OR REPLACE FUNCTION timestamp_id(table_name text)
        RETURNS bigint AS
        $$
          DECLARE
            time_part bigint;
            sequence_base bigint;
            tail bigint;
          BEGIN
            time_part := (
              -- Get the time in milliseconds
              ((date_part('epoch', now()) * 1000))::bigint
              -- And shift it over two bytes
              << 16);

            sequence_base := (
              'x' ||
              -- Take the first two bytes (four hex characters)
              substr(
                -- Of the MD5 hash of the data we documented
                md5(table_name || :random_string || time_part::text),
                1, 4
              )
            -- And turn it into a bigint
            )::bit(16)::bigint;

            -- Finally, add our sequence number to our base, and chop
            -- it to the last two bytes
            tail := (
              (sequence_base + nextval(table_name || '_id_seq'))
              & 65535);

            -- Return the time part and the sequence part. OR appears
            -- faster here than addition, but they're equivalent:
            -- time_part has no trailing two bytes, and tail is only
            -- the last two bytes.
            RETURN time_part | tail;
          END
        $$ LANGUAGE plpgsql VOLATILE;
      SQL
    end

    def connection
      ActiveRecord::Base.connection
    end

M lib/rails/engine_extensions.rb => lib/rails/engine_extensions.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

module Rails
  module EngineExtensions
    # Rewrite task loading code to filter digitalocean.rake task

M lib/tasks/branding.rake => lib/tasks/branding.rake +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

namespace :branding do
  desc 'Generate necessary graphic assets for branding from source SVG files'
  task generate: :environment do

M lib/tasks/repo.rake => lib/tasks/repo.rake +1 -1
@@ 50,7 50,7 @@ namespace :repo do
        file.each_line do |line|
          if line.start_with?('-')
            new_line = line.gsub(/#([[:digit:]]+)*/) do |pull_request_reference|
              pull_request_number = pull_request_reference[1..-1]
              pull_request_number = pull_request_reference[1..]
              response = nil

              loop do

M package.json => package.json +2 -2
@@ 204,8 204,8 @@
    "lint-staged": "^13.2.2",
    "prettier": "^2.8.8",
    "react-test-renderer": "^18.2.0",
    "stylelint": "^15.6.2",
    "stylelint-config-standard-scss": "^9.0.0",
    "stylelint": "^15.10.1",
    "stylelint-config-standard-scss": "^10.0.0",
    "typescript": "^5.0.4",
    "webpack-dev-server": "^3.11.3",
    "yargs": "^17.7.2"

M spec/controllers/admin/domain_blocks_controller_spec.rb => spec/controllers/admin/domain_blocks_controller_spec.rb +3 -2
@@ 166,10 166,11 @@ RSpec.describe Admin::DomainBlocksController do
  end

  describe 'PUT #update' do
    let!(:remote_account) { Fabricate(:account, domain: 'example.com') }
    let(:subject) do
    subject do
      post :update, params: { :id => domain_block.id, :domain_block => { domain: 'example.com', severity: new_severity }, 'confirm' => '' }
    end

    let!(:remote_account) { Fabricate(:account, domain: 'example.com') }
    let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) }

    before do

M spec/controllers/api/base_controller_spec.rb => spec/controllers/api/base_controller_spec.rb +2 -1
@@ 88,10 88,11 @@ describe Api::BaseController do
      Mastodon::NotPermittedError => 403,
    }.each do |error, code|
      it "Handles error class of #{error}" do
        expect(FakeService).to receive(:new).and_raise(error)
        allow(FakeService).to receive(:new).and_raise(error)

        get 'error'
        expect(response).to have_http_status(code)
        expect(FakeService).to have_received(:new)
      end
    end
  end

M spec/controllers/api/v1/media_controller_spec.rb => spec/controllers/api/v1/media_controller_spec.rb +2 -2
@@ 16,7 16,7 @@ RSpec.describe Api::V1::MediaController do
    describe 'with paperclip errors' do
      context 'when imagemagick cant identify the file type' do
        it 'returns http 422' do
          expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError)
          allow_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Errors::NotIdentifiedByImageMagickError)
          post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') }

          expect(response).to have_http_status(422)


@@ 25,7 25,7 @@ RSpec.describe Api::V1::MediaController do

      context 'when there is a generic error' do
        it 'returns http 422' do
          expect_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Error)
          allow_any_instance_of(Account).to receive_message_chain(:media_attachments, :create!).and_raise(Paperclip::Error)
          post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') }

          expect(response).to have_http_status(500)

M spec/controllers/api/v1/reports_controller_spec.rb => spec/controllers/api/v1/reports_controller_spec.rb +1 -3
@@ 23,8 23,6 @@ RSpec.describe Api::V1::ReportsController do
    let(:rule_ids) { nil }

    before do
      allow(AdminMailer).to receive(:new_report)
        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
      post :create, params: { status_ids: [status.id], account_id: target_account.id, comment: 'reasons', category: category, rule_ids: rule_ids, forward: forward }
    end



@@ 41,7 39,7 @@ RSpec.describe Api::V1::ReportsController do
    end

    it 'sends e-mails to admins' do
      expect(AdminMailer).to have_received(:new_report).with(admin.account, Report)
      expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email])
    end

    context 'when a status does not belong to the reported account' do

M spec/controllers/auth/registrations_controller_spec.rb => spec/controllers/auth/registrations_controller_spec.rb +4 -2
@@ 15,20 15,22 @@ RSpec.describe Auth::RegistrationsController do
    it 'redirects if it is in single user mode while it is open for registration' do
      Fabricate(:account)
      Setting.registrations_mode = 'open'
      expect(Rails.configuration.x).to receive(:single_user_mode).and_return(true)
      allow(Rails.configuration.x).to receive(:single_user_mode).and_return(true)

      get path

      expect(response).to redirect_to '/'
      expect(Rails.configuration.x).to have_received(:single_user_mode)
    end

    it 'redirects if it is not open for registration while it is not in single user mode' do
      Setting.registrations_mode = 'none'
      expect(Rails.configuration.x).to receive(:single_user_mode).and_return(false)
      allow(Rails.configuration.x).to receive(:single_user_mode).and_return(false)

      get path

      expect(response).to redirect_to '/'
      expect(Rails.configuration.x).to have_received(:single_user_mode)
    end
  end


M spec/controllers/disputes/appeals_controller_spec.rb => spec/controllers/disputes/appeals_controller_spec.rb +1 -3
@@ 14,13 14,11 @@ RSpec.describe Disputes::AppealsController do
    let(:strike) { Fabricate(:account_warning, target_account: current_user.account) }

    before do
      allow(AdminMailer).to receive(:new_appeal)
        .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil))
      post :create, params: { strike_id: strike.id, appeal: { text: 'Foo' } }
    end

    it 'notifies staff about new appeal' do
      expect(AdminMailer).to have_received(:new_appeal).with(admin.account, Appeal.last)
      expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email])
    end

    it 'redirects back to the strike page' do

M spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb => spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +1 -1
@@ 104,7 104,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
              post :create,
                   params: { form_two_factor_confirmation: { otp_attempt: '123456' } },
                   session: { challenge_passed_at: Time.now.utc, new_otp_secret: 'thisisasecretforthespecofnewview' }
            end.to not_change { user.reload.otp_secret }
            end.to(not_change { user.reload.otp_secret })
          end

          it 'renders the new view' do

M spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb => spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb +2 -2
@@ 63,7 63,7 @@ describe Settings::TwoFactorAuthentication::OtpAuthenticationController do
            expect do
              post :create, session: { challenge_passed_at: Time.now.utc }
            end.to not_change { user.reload.otp_secret }
               .and change { session[:new_otp_secret] }
               .and(change { session[:new_otp_secret] })

            expect(response).to redirect_to(new_settings_two_factor_authentication_confirmation_path)
          end


@@ 80,7 80,7 @@ describe Settings::TwoFactorAuthentication::OtpAuthenticationController do
            expect do
              post :create, session: { challenge_passed_at: Time.now.utc }
            end.to not_change { user.reload.otp_secret }
               .and change { session[:new_otp_secret] }
               .and(change { session[:new_otp_secret] })

            expect(response).to redirect_to(new_settings_two_factor_authentication_confirmation_path)
          end

M spec/fabricators_spec.rb => spec/fabricators_spec.rb +2 -0
@@ 1,3 1,5 @@
# frozen_string_literal: true

require 'rails_helper'

Fabrication.manager.load_definitions if Fabrication.manager.empty?

M spec/helpers/application_helper_spec.rb => spec/helpers/application_helper_spec.rb +6 -7
@@ 78,19 78,17 @@ describe ApplicationHelper do

  describe 'open_registrations?' do
    it 'returns true when open for registrations' do
      without_partial_double_verification do
        expect(Setting).to receive(:registrations_mode).and_return('open')
      end
      allow(Setting).to receive(:[]).with('registrations_mode').and_return('open')

      expect(helper.open_registrations?).to be true
      expect(Setting).to have_received(:[]).with('registrations_mode')
    end

    it 'returns false when closed for registrations' do
      without_partial_double_verification do
        expect(Setting).to receive(:registrations_mode).and_return('none')
      end
      allow(Setting).to receive(:[]).with('registrations_mode').and_return('none')

      expect(helper.open_registrations?).to be false
      expect(Setting).to have_received(:[]).with('registrations_mode')
    end
  end



@@ 297,8 295,9 @@ describe ApplicationHelper do

    it 'returns site title on production environment' do
      Setting.site_title = 'site title'
      expect(Rails.env).to receive(:production?).and_return(true)
      allow(Rails.env).to receive(:production?).and_return(true)
      expect(helper.title).to eq 'site title'
      expect(Rails.env).to have_received(:production?)
    end
  end
end

M spec/lib/status_filter_spec.rb => spec/lib/status_filter_spec.rb +2 -2
@@ 23,7 23,7 @@ describe StatusFilter do

      context 'when status policy does not allow show' do
        it 'filters the status' do
          expect_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
          allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)

          expect(filter).to be_filtered
        end


@@ 74,7 74,7 @@ describe StatusFilter do

      context 'when status policy does not allow show' do
        it 'filters the status' do
          expect_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)
          allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false)

          expect(filter).to be_filtered
        end

M spec/lib/status_finder_spec.rb => spec/lib/status_finder_spec.rb +5 -2
@@ 18,10 18,13 @@ describe StatusFinder do

      it 'raises an error if action is not :show' do
        recognized = Rails.application.routes.recognize_path(url)
        expect(recognized).to receive(:[]).with(:action).and_return(:create)
        expect(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized)
        allow(recognized).to receive(:[]).with(:action).and_return(:create)
        allow(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized)

        expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound)

        expect(Rails.application.routes).to have_received(:recognize_path)
        expect(recognized).to have_received(:[])
      end
    end


M spec/lib/webfinger_resource_spec.rb => spec/lib/webfinger_resource_spec.rb +2 -1
@@ 27,13 27,14 @@ describe WebfingerResource do
        recognized = Rails.application.routes.recognize_path(resource)
        allow(recognized).to receive(:[]).with(:controller).and_return('accounts')
        allow(recognized).to receive(:[]).with(:username).and_return('alice')
        expect(recognized).to receive(:[]).with(:action).and_return('create')
        allow(recognized).to receive(:[]).with(:action).and_return('create')

        expect(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized).at_least(:once)

        expect do
          described_class.new(resource).username
        end.to raise_error(ActiveRecord::RecordNotFound)
        expect(recognized).to have_received(:[]).exactly(3).times
      end

      it 'raises with a string that doesnt start with URL' do

M spec/mailers/admin_mailer_spec.rb => spec/mailers/admin_mailer_spec.rb +4 -4
@@ 7,7 7,7 @@ RSpec.describe AdminMailer do
    let(:sender)    { Fabricate(:account, username: 'John') }
    let(:recipient) { Fabricate(:account, username: 'Mike') }
    let(:report)    { Fabricate(:report, account: sender, target_account: recipient) }
    let(:mail)      { described_class.new_report(recipient, report) }
    let(:mail)      { described_class.with(recipient: recipient).new_report(report) }

    before do
      recipient.user.update(locale: :en)


@@ 27,7 27,7 @@ RSpec.describe AdminMailer do
  describe '.new_appeal' do
    let(:appeal) { Fabricate(:appeal) }
    let(:recipient) { Fabricate(:account, username: 'Kurt') }
    let(:mail)      { described_class.new_appeal(recipient, appeal) }
    let(:mail)      { described_class.with(recipient: recipient).new_appeal(appeal) }

    before do
      recipient.user.update(locale: :en)


@@ 47,7 47,7 @@ RSpec.describe AdminMailer do
  describe '.new_pending_account' do
    let(:recipient) { Fabricate(:account, username: 'Barklums') }
    let(:user) { Fabricate(:user) }
    let(:mail) { described_class.new_pending_account(recipient, user) }
    let(:mail) { described_class.with(recipient: recipient).new_pending_account(user) }

    before do
      recipient.user.update(locale: :en)


@@ 69,7 69,7 @@ RSpec.describe AdminMailer do
    let(:links) { [] }
    let(:statuses) { [] }
    let(:tags) { [] }
    let(:mail) { described_class.new_trends(recipient, links, tags, statuses) }
    let(:mail) { described_class.with(recipient: recipient).new_trends(links, tags, statuses) }

    before do
      recipient.user.update(locale: :en)

M spec/mailers/notification_mailer_spec.rb => spec/mailers/notification_mailer_spec.rb +16 -5
@@ 23,7 23,8 @@ RSpec.describe NotificationMailer do

  describe 'mention' do
    let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) }
    let(:mail) { described_class.mention(receiver.account, Notification.create!(account: receiver.account, activity: mention)) }
    let(:notification) { Notification.create!(account: receiver.account, activity: mention) }
    let(:mail) { prepared_mailer_for(receiver.account).mention }

    include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'



@@ 40,7 41,8 @@ RSpec.describe NotificationMailer do

  describe 'follow' do
    let(:follow) { sender.follow!(receiver.account) }
    let(:mail) { described_class.follow(receiver.account, Notification.create!(account: receiver.account, activity: follow)) }
    let(:notification) { Notification.create!(account: receiver.account, activity: follow) }
    let(:mail) { prepared_mailer_for(receiver.account).follow }

    include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'



@@ 56,7 58,8 @@ RSpec.describe NotificationMailer do

  describe 'favourite' do
    let(:favourite) { Favourite.create!(account: sender, status: own_status) }
    let(:mail) { described_class.favourite(own_status.account, Notification.create!(account: receiver.account, activity: favourite)) }
    let(:notification) { Notification.create!(account: receiver.account, activity: favourite) }
    let(:mail) { prepared_mailer_for(own_status.account).favourite }

    include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'



@@ 73,7 76,8 @@ RSpec.describe NotificationMailer do

  describe 'reblog' do
    let(:reblog) { Status.create!(account: sender, reblog: own_status) }
    let(:mail) { described_class.reblog(own_status.account, Notification.create!(account: receiver.account, activity: reblog)) }
    let(:notification) { Notification.create!(account: receiver.account, activity: reblog) }
    let(:mail) { prepared_mailer_for(own_status.account).reblog }

    include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'



@@ 90,7 94,8 @@ RSpec.describe NotificationMailer do

  describe 'follow_request' do
    let(:follow_request) { Fabricate(:follow_request, account: sender, target_account: receiver.account) }
    let(:mail) { described_class.follow_request(receiver.account, Notification.create!(account: receiver.account, activity: follow_request)) }
    let(:notification) { Notification.create!(account: receiver.account, activity: follow_request) }
    let(:mail) { prepared_mailer_for(receiver.account).follow_request }

    include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'



@@ 103,4 108,10 @@ RSpec.describe NotificationMailer do
      expect(mail.body.encoded).to match('bob has requested to follow you')
    end
  end

  private

  def prepared_mailer_for(recipient)
    described_class.with(recipient: recipient, notification: notification)
  end
end

M spec/mailers/previews/admin_mailer_preview.rb => spec/mailers/previews/admin_mailer_preview.rb +3 -3
@@ 5,16 5,16 @@
class AdminMailerPreview < ActionMailer::Preview
  # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_pending_account
  def new_pending_account
    AdminMailer.new_pending_account(Account.first, User.pending.first)
    AdminMailer.with(recipient: Account.first).new_pending_account(User.pending.first)
  end

  # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_trends
  def new_trends
    AdminMailer.new_trends(Account.first, PreviewCard.joins(:trend).limit(3), Tag.limit(3), Status.joins(:trend).where(reblog_of_id: nil).limit(3))
    AdminMailer.with(recipient: Account.first).new_trends(PreviewCard.joins(:trend).limit(3), Tag.limit(3), Status.joins(:trend).where(reblog_of_id: nil).limit(3))
  end

  # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_appeal
  def new_appeal
    AdminMailer.new_appeal(Account.first, Appeal.first)
    AdminMailer.with(recipient: Account.first).new_appeal(Appeal.first)
  end
end

M spec/mailers/previews/notification_mailer_preview.rb => spec/mailers/previews/notification_mailer_preview.rb +17 -13
@@ 5,36 5,40 @@
class NotificationMailerPreview < ActionMailer::Preview
  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/mention
  def mention
    m = Mention.last
    NotificationMailer.mention(m.account, Notification.find_by(activity: m))
    activity = Mention.last
    mailer_for(activity.account, activity).mention
  end

  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/follow
  def follow
    f = Follow.last
    NotificationMailer.follow(f.target_account, Notification.find_by(activity: f))
    activity = Follow.last
    mailer_for(activity.target_account, activity).follow
  end

  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/follow_request
  def follow_request
    f = Follow.last
    NotificationMailer.follow_request(f.target_account, Notification.find_by(activity: f))
    activity = Follow.last
    mailer_for(activity.target_account, activity).follow_request
  end

  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/favourite
  def favourite
    f = Favourite.last
    NotificationMailer.favourite(f.status.account, Notification.find_by(activity: f))
    activity = Favourite.last
    mailer_for(activity.status.account, activity).favourite
  end

  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/reblog
  def reblog
    r = Status.where.not(reblog_of_id: nil).first
    NotificationMailer.reblog(r.reblog.account, Notification.find_by(activity: r))
    activity = Status.where.not(reblog_of_id: nil).first
    mailer_for(activity.reblog.account, activity).reblog
  end

  # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/digest
  def digest
    NotificationMailer.digest(Account.first, since: 90.days.ago)
  private

  def mailer_for(account, activity)
    NotificationMailer.with(
      recipient: account,
      notification: Notification.find_by(activity: activity)
    )
  end
end

M spec/models/account_migration_spec.rb => spec/models/account_migration_spec.rb +2 -2
@@ 4,11 4,11 @@ require 'rails_helper'

RSpec.describe AccountMigration do
  describe 'validations' do
    subject { described_class.new(account: source_account, acct: target_acct) }

    let(:source_account) { Fabricate(:account) }
    let(:target_acct)    { target_account.acct }

    let(:subject) { described_class.new(account: source_account, acct: target_acct) }

    context 'with valid properties' do
      let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') }


M spec/models/account_spec.rb => spec/models/account_spec.rb +3 -1
@@ 20,7 20,9 @@ RSpec.describe Account do
      end

      context 'when the account is of a local user' do
        let!(:subject) { Fabricate(:user, email: 'foo+bar@domain.org').account }
        subject { local_user_account }

        let!(:local_user_account) { Fabricate(:user, email: 'foo+bar@domain.org').account }

        it 'creates a canonical domain block' do
          subject.suspend!

M spec/models/relationship_filter_spec.rb => spec/models/relationship_filter_spec.rb +1 -1
@@ 7,7 7,7 @@ describe RelationshipFilter do

  describe '#results' do
    context 'when default params are used' do
      let(:subject) do
      subject do
        described_class.new(account, 'order' => 'active').results
      end


M spec/models/user_role_spec.rb => spec/models/user_role_spec.rb +2 -2
@@ 93,7 93,7 @@ RSpec.describe UserRole do

  describe '#computed_permissions' do
    context 'when the role is nobody' do
      let(:subject) { described_class.nobody }
      subject { described_class.nobody }

      it 'returns none' do
        expect(subject.computed_permissions).to eq UserRole::Flags::NONE


@@ 101,7 101,7 @@ RSpec.describe UserRole do
    end

    context 'when the role is everyone' do
      let(:subject) { described_class.everyone }
      subject { described_class.everyone }

      it 'returns permissions' do
        expect(subject.computed_permissions).to eq subject.permissions

M spec/policies/account_moderation_note_policy_spec.rb => spec/policies/account_moderation_note_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe AccountModerationNotePolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/account_policy_spec.rb => spec/policies/account_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe AccountPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }
  let(:alice)   { Fabricate(:account) }

M spec/policies/backup_policy_spec.rb => spec/policies/backup_policy_spec.rb +3 -2
@@ 4,8 4,9 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe BackupPolicy do
  let(:subject) { described_class }
  let(:john)    { Fabricate(:account) }
  subject { described_class }

  let(:john) { Fabricate(:account) }

  permissions :create? do
    context 'when not user_signed_in?' do

M spec/policies/custom_emoji_policy_spec.rb => spec/policies/custom_emoji_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe CustomEmojiPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/domain_block_policy_spec.rb => spec/policies/domain_block_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe DomainBlockPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/email_domain_block_policy_spec.rb => spec/policies/email_domain_block_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe EmailDomainBlockPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/instance_policy_spec.rb => spec/policies/instance_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe InstancePolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/invite_policy_spec.rb => spec/policies/invite_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe InvitePolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:user).account }


M spec/policies/relay_policy_spec.rb => spec/policies/relay_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe RelayPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/report_note_policy_spec.rb => spec/policies/report_note_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe ReportNotePolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/report_policy_spec.rb => spec/policies/report_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe ReportPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/settings_policy_spec.rb => spec/policies/settings_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe SettingsPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/tag_policy_spec.rb => spec/policies/tag_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe TagPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/policies/user_policy_spec.rb => spec/policies/user_policy_spec.rb +2 -1
@@ 4,7 4,8 @@ require 'rails_helper'
require 'pundit/rspec'

RSpec.describe UserPolicy do
  let(:subject) { described_class }
  subject { described_class }

  let(:admin)   { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
  let(:john)    { Fabricate(:account) }


M spec/services/activitypub/process_account_service_spec.rb => spec/services/activitypub/process_account_service_spec.rb +5 -5
@@ 113,11 113,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
  end

  context 'when discovering many subdomains in a short timeframe' do
    before do
      stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5
    end

    let(:subject) do
    subject do
      8.times do |i|
        domain = "test#{i}.testdomain.com"
        json = {


@@ 129,6 125,10 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
      end
    end

    before do
      stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5
    end

    it 'creates at least some accounts' do
      expect { subject }.to change { Account.remote.count }.by_at_least(2)
    end

M spec/services/activitypub/process_collection_service_spec.rb => spec/services/activitypub/process_collection_service_spec.rb +3 -3
@@ 70,7 70,7 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
      let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }

      it 'does not process payload if no signature exists' do
        expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
        allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
        expect(ActivityPub::Activity).to_not receive(:factory)

        subject.call(json, forwarder)


@@ 79,7 79,7 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
      it 'processes payload with actor if valid signature exists' do
        payload['signature'] = { 'type' => 'RsaSignature2017' }

        expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor)
        allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor)
        expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))

        subject.call(json, forwarder)


@@ 88,7 88,7 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
      it 'does not process payload if invalid signature exists' do
        payload['signature'] = { 'type' => 'RsaSignature2017' }

        expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
        allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil)
        expect(ActivityPub::Activity).to_not receive(:factory)

        subject.call(json, forwarder)

M spec/services/activitypub/process_status_update_service_spec.rb => spec/services/activitypub/process_status_update_service_spec.rb +2 -2
@@ 214,11 214,11 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
      end

      it 'does not create any edits' do
        expect { subject.call(status, json) }.to_not change { status.reload.edits.pluck(&:id) }
        expect { subject.call(status, json) }.to_not(change { status.reload.edits.pluck(&:id) })
      end

      it 'does not update the text, spoiler_text or edited_at' do
        expect { subject.call(status, json) }.to_not change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] }
        expect { subject.call(status, json) }.to_not(change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] })
      end
    end


M spec/services/post_status_service_spec.rb => spec/services/post_status_service_spec.rb +1 -1
@@ 52,7 52,7 @@ RSpec.describe PostStatusService, type: :service do
    end

    it 'does not change statuses count' do
      expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not change { [account.statuses_count, previous_status.replies_count] }
      expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not(change { [account.statuses_count, previous_status.replies_count] })
    end
  end


M spec/services/report_service_spec.rb => spec/services/report_service_spec.rb +49 -10
@@ 17,24 17,63 @@ RSpec.describe ReportService, type: :service do

  context 'with a remote account' do
    let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
    let(:forward) { false }

    before do
      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
    end

    it 'sends ActivityPub payload when forward is true' do
      subject.call(source_account, remote_account, forward: true)
      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made
    end
    context 'when forward is true' do
      let(:forward) { true }

      it 'sends ActivityPub payload when forward is true' do
        subject.call(source_account, remote_account, forward: forward)
        expect(a_request(:post, 'http://example.com/inbox')).to have_been_made
      end

      it 'has an uri' do
        report = subject.call(source_account, remote_account, forward: forward)
        expect(report.uri).to_not be_nil
      end

      context 'when reporting a reply' do
        let(:remote_thread_account) { Fabricate(:account, domain: 'foo.com', protocol: :activitypub, inbox_url: 'http://foo.com/inbox') }
        let(:reported_status) { Fabricate(:status, account: remote_account, thread: Fabricate(:status, account: remote_thread_account)) }

        before do
          stub_request(:post, 'http://foo.com/inbox').to_return(status: 200)
        end

    it 'does not send anything when forward is false' do
      subject.call(source_account, remote_account, forward: false)
      expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
        context 'when forward_to_domains includes both the replied-to domain and the origin domain' do
          it 'sends ActivityPub payload to both the author of the replied-to post and the reported user' do
            subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_account.domain, remote_thread_account.domain])
            expect(a_request(:post, 'http://foo.com/inbox')).to have_been_made
            expect(a_request(:post, 'http://example.com/inbox')).to have_been_made
          end
        end

        context 'when forward_to_domains includes only the replied-to domain' do
          it 'sends ActivityPub payload only to the author of the replied-to post' do
            subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_thread_account.domain])
            expect(a_request(:post, 'http://foo.com/inbox')).to have_been_made
            expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
          end
        end

        context 'when forward_to_domains does not include the replied-to domain' do
          it 'does not send ActivityPub payload to the author of the replied-to post' do
            subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward)
            expect(a_request(:post, 'http://foo.com/inbox')).to_not have_been_made
          end
        end
      end
    end

    it 'has an uri' do
      report = subject.call(source_account, remote_account, forward: true)
      expect(report.uri).to_not be_nil
    context 'when forward is false' do
      it 'does not send anything' do
        subject.call(source_account, remote_account, forward: forward)
        expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
      end
    end
  end


M spec/services/search_service_spec.rb => spec/services/search_service_spec.rb +1 -10
@@ 68,7 68,7 @@ describe SearchService, type: :service do
          allow(AccountSearchService).to receive(:new).and_return(service)

          results = subject.call(query, nil, 10)
          expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false, use_searchable_text: true, following: false)
          expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false, start_with_hashtag: false, use_searchable_text: true, following: false)
          expect(results).to eq empty_results.merge(accounts: [account])
        end
      end


@@ 92,15 92,6 @@ describe SearchService, type: :service do
          expect(Tag).to_not have_received(:search_for)
          expect(results).to eq empty_results
        end

        it 'does not include account when starts with # character' do
          query = '#tag'
          allow(AccountSearchService).to receive(:new)

          results = subject.call(query, nil, 10)
          expect(AccountSearchService).to_not have_received(:new)
          expect(results).to eq empty_results
        end
      end
    end
  end

M spec/services/unallow_domain_service_spec.rb => spec/services/unallow_domain_service_spec.rb +2 -2
@@ 14,7 14,7 @@ RSpec.describe UnallowDomainService, type: :service do

  context 'with limited federation mode' do
    before do
      allow(subject).to receive(:whitelist_mode?).and_return(true)
      allow(Rails.configuration.x).to receive(:whitelist_mode).and_return(true)
    end

    describe '#call' do


@@ 40,7 40,7 @@ RSpec.describe UnallowDomainService, type: :service do

  context 'without limited federation mode' do
    before do
      allow(subject).to receive(:whitelist_mode?).and_return(false)
      allow(Rails.configuration.x).to receive(:whitelist_mode).and_return(false)
    end

    describe '#call' do

M spec/spec_helper.rb => spec/spec_helper.rb +1 -0
@@ 3,6 3,7 @@
if ENV['DISABLE_SIMPLECOV'] != 'true'
  require 'simplecov'
  SimpleCov.start 'rails' do
    add_filter 'lib/linter'
    add_group 'Policies', 'app/policies'
    add_group 'Presenters', 'app/presenters'
    add_group 'Serializers', 'app/serializers'

M spec/validators/blacklisted_email_validator_spec.rb => spec/validators/blacklisted_email_validator_spec.rb +7 -4
@@ 11,14 11,15 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do

    before do
      allow(user).to receive(:valid_invitation?).and_return(false)
      allow_any_instance_of(described_class).to receive(:blocked_email_provider?) { blocked_email }
      allow(EmailDomainBlock).to receive(:block?) { blocked_email }
    end

    context 'when e-mail provider is blocked' do
      let(:blocked_email) { true }

      it 'adds error' do
        expect(subject).to have_received(:add).with(:email, :blocked)
        described_class.new.validate(user)
        expect(errors).to have_received(:add).with(:email, :blocked).once
      end
    end



@@ 26,7 27,8 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
      let(:blocked_email) { false }

      it 'does not add errors' do
        expect(subject).to_not have_received(:add).with(:email, :blocked)
        described_class.new.validate(user)
        expect(errors).to_not have_received(:add)
      end

      context 'when canonical e-mail is blocked' do


@@ 37,7 39,8 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
        end

        it 'adds error' do
          expect(subject).to have_received(:add).with(:email, :taken)
          described_class.new.validate(user)
          expect(errors).to have_received(:add).with(:email, :taken).once
        end
      end
    end

M yarn.lock => yarn.lock +304 -243
@@ 31,33 31,43 @@
  dependencies:
    "@babel/highlight" "^7.22.5"

"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255"
  integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==
"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544"
  integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==

"@babel/core@^7.10.4", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.1":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89"
  integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==
  version "7.22.8"
  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.8.tgz#386470abe884302db9c82e8e5e87be9e46c86785"
  integrity sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==
  dependencies:
    "@ampproject/remapping" "^2.2.0"
    "@babel/code-frame" "^7.22.5"
    "@babel/generator" "^7.22.5"
    "@babel/helper-compilation-targets" "^7.22.5"
    "@babel/generator" "^7.22.7"
    "@babel/helper-compilation-targets" "^7.22.6"
    "@babel/helper-module-transforms" "^7.22.5"
    "@babel/helpers" "^7.22.5"
    "@babel/parser" "^7.22.5"
    "@babel/helpers" "^7.22.6"
    "@babel/parser" "^7.22.7"
    "@babel/template" "^7.22.5"
    "@babel/traverse" "^7.22.5"
    "@babel/traverse" "^7.22.8"
    "@babel/types" "^7.22.5"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"
    convert-source-map "^1.7.0"
    debug "^4.1.0"
    gensync "^1.0.0-beta.2"
    json5 "^2.2.2"
    semver "^6.3.0"

"@babel/generator@^7.22.5", "@babel/generator@^7.7.2":
"@babel/generator@^7.22.5", "@babel/generator@^7.22.7":
  version "7.22.7"
  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.7.tgz#a6b8152d5a621893f2c9dacf9a4e286d520633d5"
  integrity sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==
  dependencies:
    "@babel/types" "^7.22.5"
    "@jridgewell/gen-mapping" "^0.3.2"
    "@jridgewell/trace-mapping" "^0.3.17"
    jsesc "^2.5.1"

"@babel/generator@^7.7.2":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
  integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==


@@ 89,21 99,21 @@
    "@babel/helper-annotate-as-pure" "^7.22.5"
    "@babel/types" "^7.22.5"

"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02"
  integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==
"@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz#e30d61abe9480aa5a83232eb31c111be922d2e52"
  integrity sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==
  dependencies:
    "@babel/compat-data" "^7.22.5"
    "@babel/compat-data" "^7.22.6"
    "@babel/helper-validator-option" "^7.22.5"
    browserslist "^4.21.3"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"
    browserslist "^4.21.9"
    lru-cache "^5.1.1"
    semver "^6.3.0"

"@babel/helper-create-class-features-plugin@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c"
  integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz#58564873c889a6fea05a538e23f9f6d201f10950"
  integrity sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.22.5"
    "@babel/helper-environment-visitor" "^7.22.5"


@@ 112,29 122,28 @@
    "@babel/helper-optimise-call-expression" "^7.22.5"
    "@babel/helper-replace-supers" "^7.22.5"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
    "@babel/helper-split-export-declaration" "^7.22.5"
    semver "^6.3.0"
    "@babel/helper-split-export-declaration" "^7.22.6"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"

"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4"
  integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz#87afd63012688ad792de430ceb3b6dc28e4e7a40"
  integrity sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.22.5"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"
    regexpu-core "^5.3.1"
    semver "^6.3.0"

"@babel/helper-define-polyfill-provider@^0.4.0":
  version "0.4.0"
  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8"
  integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==
"@babel/helper-define-polyfill-provider@^0.4.1":
  version "0.4.1"
  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz#af1429c4a83ac316a6a8c2cc8ff45cb5d2998d3a"
  integrity sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==
  dependencies:
    "@babel/helper-compilation-targets" "^7.17.7"
    "@babel/helper-plugin-utils" "^7.16.7"
    "@babel/helper-compilation-targets" "^7.22.6"
    "@babel/helper-plugin-utils" "^7.22.5"
    debug "^4.1.1"
    lodash.debounce "^4.0.8"
    resolve "^1.14.2"
    semver "^6.1.2"

"@babel/helper-environment-visitor@^7.22.5":
  version "7.22.5"


@@ 191,7 200,7 @@
  dependencies:
    "@babel/types" "^7.22.5"

"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
  integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==


@@ 232,10 241,10 @@
  dependencies:
    "@babel/types" "^7.22.5"

"@babel/helper-split-export-declaration@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08"
  integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==
"@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
  integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
  dependencies:
    "@babel/types" "^7.22.5"



@@ 264,13 273,13 @@
    "@babel/traverse" "^7.22.5"
    "@babel/types" "^7.22.5"

"@babel/helpers@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820"
  integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==
"@babel/helpers@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd"
  integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==
  dependencies:
    "@babel/template" "^7.22.5"
    "@babel/traverse" "^7.22.5"
    "@babel/traverse" "^7.22.6"
    "@babel/types" "^7.22.5"

"@babel/highlight@^7.22.5":


@@ 282,11 291,16 @@
    chalk "^2.0.0"
    js-tokens "^4.0.0"

"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5":
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea"
  integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==

"@babel/parser@^7.22.5", "@babel/parser@^7.22.7":
  version "7.22.7"
  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae"
  integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==

"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e"


@@ 471,10 485,10 @@
  dependencies:
    "@babel/helper-plugin-utils" "^7.22.5"

"@babel/plugin-transform-async-generator-functions@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz#7336356d23380eda9a56314974f053a020dab0c3"
  integrity sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==
"@babel/plugin-transform-async-generator-functions@^7.22.7":
  version "7.22.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b"
  integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==
  dependencies:
    "@babel/helper-environment-visitor" "^7.22.5"
    "@babel/helper-plugin-utils" "^7.22.5"


@@ 521,19 535,19 @@
    "@babel/helper-plugin-utils" "^7.22.5"
    "@babel/plugin-syntax-class-static-block" "^7.14.5"

"@babel/plugin-transform-classes@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1"
  integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==
"@babel/plugin-transform-classes@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363"
  integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==
  dependencies:
    "@babel/helper-annotate-as-pure" "^7.22.5"
    "@babel/helper-compilation-targets" "^7.22.5"
    "@babel/helper-compilation-targets" "^7.22.6"
    "@babel/helper-environment-visitor" "^7.22.5"
    "@babel/helper-function-name" "^7.22.5"
    "@babel/helper-optimise-call-expression" "^7.22.5"
    "@babel/helper-plugin-utils" "^7.22.5"
    "@babel/helper-replace-supers" "^7.22.5"
    "@babel/helper-split-export-declaration" "^7.22.5"
    "@babel/helper-split-export-declaration" "^7.22.6"
    globals "^11.1.0"

"@babel/plugin-transform-computed-properties@^7.22.5":


@@ 729,10 743,10 @@
    "@babel/helper-plugin-utils" "^7.22.5"
    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"

"@babel/plugin-transform-optional-chaining@^7.22.5":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0"
  integrity sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==
"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6":
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564"
  integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==
  dependencies:
    "@babel/helper-plugin-utils" "^7.22.5"
    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"


@@ 827,16 841,16 @@
    "@babel/helper-plugin-utils" "^7.22.5"

"@babel/plugin-transform-runtime@^7.22.4":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.5.tgz#ca975fb5e260044473c8142e1b18b567d33c2a3b"
  integrity sha512-bg4Wxd1FWeFx3daHFTWk1pkSWK/AyQuiyAoeZAOkAOUBjnZPH6KT7eMxouV47tQ6hl6ax2zyAWBdWZXbrvXlaw==
  version "7.22.7"
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.7.tgz#eb9094b5fb756cc2d98d398b2c88aeefa9205de9"
  integrity sha512-o02xM7iY7mSPI+TvaYDH0aYl+lg3+KT7qrD705JlsB/GrZSNaYO/4i+aDFKPiJ7ubq3hgv8NNLCdyB5MFxT8mg==
  dependencies:
    "@babel/helper-module-imports" "^7.22.5"
    "@babel/helper-plugin-utils" "^7.22.5"
    babel-plugin-polyfill-corejs2 "^0.4.3"
    babel-plugin-polyfill-corejs3 "^0.8.1"
    babel-plugin-polyfill-regenerator "^0.5.0"
    semver "^6.3.0"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"
    babel-plugin-polyfill-corejs2 "^0.4.4"
    babel-plugin-polyfill-corejs3 "^0.8.2"
    babel-plugin-polyfill-regenerator "^0.5.1"

"@babel/plugin-transform-shorthand-properties@^7.22.5":
  version "7.22.5"


@@ 916,12 930,12 @@
    "@babel/helper-plugin-utils" "^7.22.5"

"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.22.4":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.5.tgz#3da66078b181f3d62512c51cf7014392c511504e"
  integrity sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==
  version "7.22.7"
  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.7.tgz#a1ef34b64a80653c22ce4d9c25603cfa76fc168a"
  integrity sha512-1whfDtW+CzhETuzYXfcgZAh8/GFMeEbz0V5dVgya8YeJyCU6Y/P2Gnx4Qb3MylK68Zu9UiwUvbPMPTpFAOJ+sQ==
  dependencies:
    "@babel/compat-data" "^7.22.5"
    "@babel/helper-compilation-targets" "^7.22.5"
    "@babel/compat-data" "^7.22.6"
    "@babel/helper-compilation-targets" "^7.22.6"
    "@babel/helper-plugin-utils" "^7.22.5"
    "@babel/helper-validator-option" "^7.22.5"
    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5"


@@ 946,13 960,13 @@
    "@babel/plugin-syntax-top-level-await" "^7.14.5"
    "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
    "@babel/plugin-transform-arrow-functions" "^7.22.5"
    "@babel/plugin-transform-async-generator-functions" "^7.22.5"
    "@babel/plugin-transform-async-generator-functions" "^7.22.7"
    "@babel/plugin-transform-async-to-generator" "^7.22.5"
    "@babel/plugin-transform-block-scoped-functions" "^7.22.5"
    "@babel/plugin-transform-block-scoping" "^7.22.5"
    "@babel/plugin-transform-class-properties" "^7.22.5"
    "@babel/plugin-transform-class-static-block" "^7.22.5"
    "@babel/plugin-transform-classes" "^7.22.5"
    "@babel/plugin-transform-classes" "^7.22.6"
    "@babel/plugin-transform-computed-properties" "^7.22.5"
    "@babel/plugin-transform-destructuring" "^7.22.5"
    "@babel/plugin-transform-dotall-regex" "^7.22.5"


@@ 977,7 991,7 @@
    "@babel/plugin-transform-object-rest-spread" "^7.22.5"
    "@babel/plugin-transform-object-super" "^7.22.5"
    "@babel/plugin-transform-optional-catch-binding" "^7.22.5"
    "@babel/plugin-transform-optional-chaining" "^7.22.5"
    "@babel/plugin-transform-optional-chaining" "^7.22.6"
    "@babel/plugin-transform-parameters" "^7.22.5"
    "@babel/plugin-transform-private-methods" "^7.22.5"
    "@babel/plugin-transform-private-property-in-object" "^7.22.5"


@@ 995,11 1009,11 @@
    "@babel/plugin-transform-unicode-sets-regex" "^7.22.5"
    "@babel/preset-modules" "^0.1.5"
    "@babel/types" "^7.22.5"
    babel-plugin-polyfill-corejs2 "^0.4.3"
    babel-plugin-polyfill-corejs3 "^0.8.1"
    babel-plugin-polyfill-regenerator "^0.5.0"
    core-js-compat "^3.30.2"
    semver "^6.3.0"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"
    babel-plugin-polyfill-corejs2 "^0.4.4"
    babel-plugin-polyfill-corejs3 "^0.8.2"
    babel-plugin-polyfill-regenerator "^0.5.1"
    core-js-compat "^3.31.0"

"@babel/preset-modules@^0.1.5":
  version "0.1.5"


@@ 1048,9 1062,9 @@
    regenerator-runtime "^0.12.0"

"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
  integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
  version "7.22.6"
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
  integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
  dependencies:
    regenerator-runtime "^0.13.11"



@@ 1063,7 1077,7 @@
    "@babel/parser" "^7.22.5"
    "@babel/types" "^7.22.5"

"@babel/traverse@7", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.2":
"@babel/traverse@7", "@babel/traverse@^7.7.2":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1"
  integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==


@@ 1079,6 1093,22 @@
    debug "^4.1.0"
    globals "^11.1.0"

"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8":
  version "7.22.8"
  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e"
  integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==
  dependencies:
    "@babel/code-frame" "^7.22.5"
    "@babel/generator" "^7.22.7"
    "@babel/helper-environment-visitor" "^7.22.5"
    "@babel/helper-function-name" "^7.22.5"
    "@babel/helper-hoist-variables" "^7.22.5"
    "@babel/helper-split-export-declaration" "^7.22.6"
    "@babel/parser" "^7.22.7"
    "@babel/types" "^7.22.5"
    debug "^4.1.0"
    globals "^11.1.0"

"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.11", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
  version "7.22.5"
  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"


@@ 1093,25 1123,25 @@
  resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
  integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==

"@csstools/css-parser-algorithms@^2.2.0":
  version "2.2.0"
  resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.2.0.tgz#1268b07196d1118296443aeff41bca27d94b0981"
  integrity sha512-9BoQ/jSrPq4vv3b9jjLW+PNNv56KlDH5JMx5yASSNrCtvq70FCNZUjXRvbCeR9hYj9ZyhURtqpU/RFIgg6kiOw==
"@csstools/css-parser-algorithms@^2.3.0":
  version "2.3.0"
  resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz#0cc3a656dc2d638370ecf6f98358973bfbd00141"
  integrity sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==

"@csstools/css-tokenizer@^2.1.1":
  version "2.1.1"
  resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz#07ae11a0a06365d7ec686549db7b729bc036528e"
  integrity sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==

"@csstools/media-query-list-parser@^2.1.0":
  version "2.1.0"
  resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.0.tgz#6e1a5e12e0d103cd13b94bddb88b878bd6866103"
  integrity sha512-MXkR+TeaS2q9IkpyO6jVCdtA/bfpABJxIrfkLswThFN8EZZgI2RfAHhm6sDNDuYV25d5+b8Lj1fpTccIcSLPsQ==
"@csstools/media-query-list-parser@^2.1.2":
  version "2.1.2"
  resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz#6ef642b728d30c1009bfbba3211c7e4c11302728"
  integrity sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==

"@csstools/selector-specificity@^2.2.0":
  version "2.2.0"
  resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
  integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==
"@csstools/selector-specificity@^3.0.0":
  version "3.0.0"
  resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247"
  integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==

"@discoveryjs/json-ext@0.5.7":
  version "0.5.7"


@@ 1655,6 1685,11 @@
    "@jridgewell/resolve-uri" "3.1.0"
    "@jridgewell/sourcemap-codec" "1.4.14"

"@nicolo-ribaudo/semver-v6@^6.3.3":
  version "6.3.3"
  resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
  integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==

"@nodelib/fs.scandir@2.1.5":
  version "2.1.5"
  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"


@@ 2115,7 2150,7 @@
  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
  integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==

"@types/minimist@^1.2.0":
"@types/minimist@^1.2.2":
  version "1.2.2"
  resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
  integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==


@@ 3225,29 3260,29 @@ babel-plugin-macros@^3.0.1, babel-plugin-macros@^3.1.0:
    cosmiconfig "^7.0.0"
    resolve "^1.19.0"

babel-plugin-polyfill-corejs2@^0.4.3:
  version "0.4.3"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd"
  integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==
babel-plugin-polyfill-corejs2@^0.4.4:
  version "0.4.4"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz#9f9a0e1cd9d645cc246a5e094db5c3aa913ccd2b"
  integrity sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==
  dependencies:
    "@babel/compat-data" "^7.17.7"
    "@babel/helper-define-polyfill-provider" "^0.4.0"
    semver "^6.1.1"
    "@babel/compat-data" "^7.22.6"
    "@babel/helper-define-polyfill-provider" "^0.4.1"
    "@nicolo-ribaudo/semver-v6" "^6.3.3"

babel-plugin-polyfill-corejs3@^0.8.1:
  version "0.8.1"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a"
  integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==
babel-plugin-polyfill-corejs3@^0.8.2:
  version "0.8.2"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz#d406c5738d298cd9c66f64a94cf8d5904ce4cc5e"
  integrity sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==
  dependencies:
    "@babel/helper-define-polyfill-provider" "^0.4.0"
    core-js-compat "^3.30.1"
    "@babel/helper-define-polyfill-provider" "^0.4.1"
    core-js-compat "^3.31.0"

babel-plugin-polyfill-regenerator@^0.5.0:
  version "0.5.0"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380"
  integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==
babel-plugin-polyfill-regenerator@^0.5.1:
  version "0.5.1"
  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz#ace7a5eced6dff7d5060c335c52064778216afd3"
  integrity sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==
  dependencies:
    "@babel/helper-define-polyfill-provider" "^0.4.0"
    "@babel/helper-define-polyfill-provider" "^0.4.1"

babel-plugin-preval@^5.1.0:
  version "5.1.0"


@@ 3521,7 3556,7 @@ browserify-zlib@^0.2.0:
  dependencies:
    pako "~1.0.5"

browserslist@^4.0.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5:
browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.21.5:
  version "4.21.8"
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.8.tgz#db2498e1f4b80ed199c076248a094935860b6017"
  integrity sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw==


@@ 3531,6 3566,16 @@ browserslist@^4.0.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4
    node-releases "^2.0.12"
    update-browserslist-db "^1.0.11"

browserslist@^4.21.9:
  version "4.21.9"
  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635"
  integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==
  dependencies:
    caniuse-lite "^1.0.30001503"
    electron-to-chromium "^1.4.431"
    node-releases "^2.0.12"
    update-browserslist-db "^1.0.11"

bser@2.1.1:
  version "2.1.1"
  resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"


@@ 3661,21 3706,22 @@ callsites@^3.0.0:
  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
  integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==

camelcase-keys@^6.2.2:
  version "6.2.2"
  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
  integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==
camelcase-keys@^7.0.0:
  version "7.0.2"
  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252"
  integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==
  dependencies:
    camelcase "^5.3.1"
    map-obj "^4.0.0"
    quick-lru "^4.0.1"
    camelcase "^6.3.0"
    map-obj "^4.1.0"
    quick-lru "^5.1.1"
    type-fest "^1.2.1"

camelcase@^5.0.0, camelcase@^5.3.1:
  version "5.3.1"
  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==

camelcase@^6.2.0:
camelcase@^6.2.0, camelcase@^6.3.0:
  version "6.3.0"
  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
  integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==


@@ 3690,11 3736,16 @@ caniuse-api@^3.0.0:
    lodash.memoize "^4.1.2"
    lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001502:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001464:
  version "1.0.30001503"
  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz#88b6ff1b2cf735f1f3361dc1a15b59f0561aa398"
  integrity sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==

caniuse-lite@^1.0.30001502, caniuse-lite@^1.0.30001503:
  version "1.0.30001515"
  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b"
  integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==

chalk@5.2.0:
  version "5.2.0"
  resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3"


@@ 4074,12 4125,12 @@ copy-descriptor@^0.1.0:
  resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
  integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==

core-js-compat@^3.30.1, core-js-compat@^3.30.2:
  version "3.31.0"
  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1"
  integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==
core-js-compat@^3.31.0:
  version "3.31.1"
  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.1.tgz#5084ad1a46858df50ff89ace152441a63ba7aae0"
  integrity sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==
  dependencies:
    browserslist "^4.21.5"
    browserslist "^4.21.9"

core-js@^2.5.0:
  version "2.6.12"


@@ 4406,6 4457,11 @@ decamelize@^1.1.0, decamelize@^1.2.0:
  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
  integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==

decamelize@^5.0.0:
  version "5.0.1"
  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9"
  integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==

decimal.js@^10.4.2, decimal.js@^10.4.3:
  version "10.4.3"
  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"


@@ 4750,10 4806,10 @@ ejs@^3.1.6:
  dependencies:
    jake "^10.8.5"

electron-to-chromium@^1.4.428:
  version "1.4.430"
  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.430.tgz#52693c812a81800fafb5b312c1a850142e2fc9eb"
  integrity sha512-FytjTbGwz///F+ToZ5XSeXbbSaXalsVRXsz2mHityI5gfxft7ieW3HqFLkU5V1aIrY42aflICqbmFoDxW10etg==
electron-to-chromium@^1.4.428, electron-to-chromium@^1.4.431:
  version "1.4.457"
  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.457.tgz#3fdc7b4f97d628ac6b51e8b4b385befb362fe343"
  integrity sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA==

elliptic@^6.5.3:
  version "6.5.4"


@@ 5453,6 5509,17 @@ fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
    merge2 "^1.3.0"
    micromatch "^4.0.4"

fast-glob@^3.3.0:
  version "3.3.0"
  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0"
  integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==
  dependencies:
    "@nodelib/fs.stat" "^2.0.2"
    "@nodelib/fs.walk" "^1.2.3"
    glob-parent "^5.1.2"
    merge2 "^1.3.0"
    micromatch "^4.0.4"

fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
  version "2.1.0"
  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"


@@ 6135,11 6202,6 @@ homedir-polyfill@^1.0.1:
  dependencies:
    parse-passwd "^1.0.0"

hosted-git-info@^2.1.4:
  version "2.8.9"
  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==

hosted-git-info@^4.0.1:
  version "4.1.0"
  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"


@@ 6374,6 6436,11 @@ indent-string@^4.0.0:
  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
  integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==

indent-string@^5.0.0:
  version "5.0.0"
  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
  integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==

infer-owner@^1.0.4:
  version "1.0.4"
  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"


@@ 7826,7 7893,7 @@ map-obj@^1.0.0:
  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
  integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==

map-obj@^4.0.0:
map-obj@^4.1.0:
  version "4.3.0"
  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a"
  integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==


@@ 7898,23 7965,23 @@ memory-fs@^0.5.0:
    errno "^0.1.3"
    readable-stream "^2.0.1"

meow@^9.0.0:
  version "9.0.0"
  resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
  integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==
meow@^10.1.5:
  version "10.1.5"
  resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f"
  integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==
  dependencies:
    "@types/minimist" "^1.2.0"
    camelcase-keys "^6.2.2"
    decamelize "^1.2.0"
    "@types/minimist" "^1.2.2"
    camelcase-keys "^7.0.0"
    decamelize "^5.0.0"
    decamelize-keys "^1.1.0"
    hard-rejection "^2.1.0"
    minimist-options "4.1.0"
    normalize-package-data "^3.0.0"
    read-pkg-up "^7.0.1"
    redent "^3.0.0"
    trim-newlines "^3.0.0"
    type-fest "^0.18.0"
    yargs-parser "^20.2.3"
    normalize-package-data "^3.0.2"
    read-pkg-up "^8.0.0"
    redent "^4.0.0"
    trim-newlines "^4.0.2"
    type-fest "^1.2.2"
    yargs-parser "^20.2.9"

merge-descriptors@1.0.1:
  version "1.0.1"


@@ 8003,7 8070,7 @@ mimic-fn@^4.0.0:
  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
  integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==

min-indent@^1.0.0:
min-indent@^1.0.0, min-indent@^1.0.1:
  version "1.0.1"
  resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
  integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==


@@ 8275,21 8342,11 @@ node-libs-browser@^2.2.1:
    vm-browserify "^1.0.1"

node-releases@^2.0.12:
  version "2.0.12"
  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039"
  integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==

normalize-package-data@^2.5.0:
  version "2.5.0"
  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
  dependencies:
    hosted-git-info "^2.1.4"
    resolve "^1.10.0"
    semver "2 || 3 || 4 || 5"
    validate-npm-package-license "^3.0.1"
  version "2.0.13"
  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
  integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==

normalize-package-data@^3.0.0:
normalize-package-data@^3.0.2:
  version "3.0.3"
  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
  integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==


@@ 8774,12 8831,7 @@ pg-cloudflare@^1.1.1:
  resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
  integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==

pg-connection-string@^2.6.0:
  version "2.6.1"
  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
  integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==

pg-connection-string@^2.6.1:
pg-connection-string@^2.6.0, pg-connection-string@^2.6.1:
  version "2.6.1"
  resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
  integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==


@@ 9415,10 9467,10 @@ queue-microtask@^1.2.2:
  resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
  integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==

quick-lru@^4.0.1:
  version "4.0.1"
  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
  integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
quick-lru@^5.1.1:
  version "5.1.1"
  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
  integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==

raf@^3.1.0:
  version "3.4.1"


@@ 9739,24 9791,24 @@ react@^18.2.0:
  dependencies:
    loose-envify "^1.1.0"

read-pkg-up@^7.0.1:
  version "7.0.1"
  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507"
  integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==
read-pkg-up@^8.0.0:
  version "8.0.0"
  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670"
  integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==
  dependencies:
    find-up "^4.1.0"
    read-pkg "^5.2.0"
    type-fest "^0.8.1"
    find-up "^5.0.0"
    read-pkg "^6.0.0"
    type-fest "^1.0.1"

read-pkg@^5.2.0:
  version "5.2.0"
  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
  integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
read-pkg@^6.0.0:
  version "6.0.0"
  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c"
  integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==
  dependencies:
    "@types/normalize-package-data" "^2.4.0"
    normalize-package-data "^2.5.0"
    parse-json "^5.0.0"
    type-fest "^0.6.0"
    normalize-package-data "^3.0.2"
    parse-json "^5.2.0"
    type-fest "^1.0.1"

readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6:
  version "2.3.8"


@@ 9814,6 9866,14 @@ redent@^3.0.0:
    indent-string "^4.0.0"
    strip-indent "^3.0.0"

redent@^4.0.0:
  version "4.0.0"
  resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9"
  integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==
  dependencies:
    indent-string "^5.0.0"
    strip-indent "^4.0.0"

redis@^4.6.5:
  version "4.6.7"
  resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.7.tgz#c73123ad0b572776223f172ec78185adb72a6b57"


@@ 10015,7 10075,7 @@ resolve.exports@^2.0.0:
  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
  integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==

resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
  version "1.22.2"
  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
  integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==


@@ 10245,16 10305,21 @@ selfsigned@^1.10.8:
  dependencies:
    node-forge "^0.10.0"

"semver@2 || 3 || 4 || 5", semver@^5.5.0:
semver@^5.5.0:
  version "5.7.1"
  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==

semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
semver@^6.0.0:
  version "6.3.0"
  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==

semver@^6.3.0:
  version "6.3.1"
  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
  integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==

semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.1:
  version "7.5.1"
  resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"


@@ 10851,6 10916,7 @@ stringz@^2.1.0:
    char-regex "^1.0.2"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
  name strip-ansi-cjs
  version "6.0.1"
  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==


@@ 10915,6 10981,13 @@ strip-indent@^3.0.0:
  dependencies:
    min-indent "^1.0.0"

strip-indent@^4.0.0:
  version "4.0.0"
  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853"
  integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==
  dependencies:
    min-indent "^1.0.1"

strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
  version "3.1.1"
  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"


@@ 10933,26 11006,26 @@ stylehacks@^6.0.0:
    browserslist "^4.21.4"
    postcss-selector-parser "^6.0.4"

stylelint-config-recommended-scss@^11.0.0:
  version "11.0.0"
  resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-11.0.0.tgz#7b933ecac99cd3b52d14d1746e3ecd36f421b4b6"
  integrity sha512-EDghTDU7aOv2LTsRZvcT1w8mcjUaMhuy+t38iV5I/0Qiu6ixdkRwhLEMul3K/fnB2v9Nwqvb3xpvJfPH+HduDw==
stylelint-config-recommended-scss@^12.0.0:
  version "12.0.0"
  resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-12.0.0.tgz#9d9e82c46012649f11bfebcbc788f58e61860f33"
  integrity sha512-5Bb2mlGy6WLa30oNeKpZvavv2lowJUsUJO25+OA68GFTemlwd1zbFsL7q0bReKipOSU3sG47hKneZ6Nd+ctrFA==
  dependencies:
    postcss-scss "^4.0.6"
    stylelint-config-recommended "^12.0.0"
    stylelint-scss "^4.6.0"
    stylelint-scss "^5.0.0"

stylelint-config-recommended@^12.0.0:
  version "12.0.0"
  resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-12.0.0.tgz#d0993232fca017065fd5acfcb52dd8a188784ef4"
  integrity sha512-x6x8QNARrGO2sG6iURkzqL+Dp+4bJorPMMRNPScdvaUK8PsynriOcMW7AFDKqkWAS5wbue/u8fUT/4ynzcmqdQ==

stylelint-config-standard-scss@^9.0.0:
  version "9.0.0"
  resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-9.0.0.tgz#70c66e1179612519fdf6ca1dbff23c804def1b6b"
  integrity sha512-yPKpJsrZn4ybuQZx/DkEHuCjw7pJginErE/47dFhCnrvD48IJ4UYec8tSiCuJWMA3HRjbIa3nh5ZeSauDGuVAg==
stylelint-config-standard-scss@^10.0.0:
  version "10.0.0"
  resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-10.0.0.tgz#159a54a01b80649bf0143fa7ba086b676a1a749e"
  integrity sha512-bChBEo1p3xUVWh/wenJI+josoMk21f2yuLDGzGjmKYcALfl2u3DFltY+n4UHswYiXghqXaA8mRh+bFy/q1hQlg==
  dependencies:
    stylelint-config-recommended-scss "^11.0.0"
    stylelint-config-recommended-scss "^12.0.0"
    stylelint-config-standard "^33.0.0"

stylelint-config-standard@^33.0.0:


@@ 10962,32 11035,32 @@ stylelint-config-standard@^33.0.0:
  dependencies:
    stylelint-config-recommended "^12.0.0"

stylelint-scss@^4.6.0:
  version "4.7.0"
  resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.7.0.tgz#f986bf8c5a4b93eae2b67d3a3562eef822657908"
  integrity sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==
stylelint-scss@^5.0.0:
  version "5.0.1"
  resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-5.0.1.tgz#b33a6580b5734eace083cfc2cc3021225e28547f"
  integrity sha512-n87iCRZrr2J7//I/QFsDXxFLnHKw633U4qvWZ+mOW6KDAp/HLj06H+6+f9zOuTYy+MdGdTuCSDROCpQIhw5fvQ==
  dependencies:
    postcss-media-query-parser "^0.2.3"
    postcss-resolve-nested-selector "^0.1.1"
    postcss-selector-parser "^6.0.11"
    postcss-selector-parser "^6.0.13"
    postcss-value-parser "^4.2.0"

stylelint@^15.6.2:
  version "15.7.0"
  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.7.0.tgz#945939a2ce9516998a198580e69b1ceef8a7c5f3"
  integrity sha512-fQRwHwWuZsDn4ENyE9AsKkOkV9WlD2CmYiVDbdZPdS3iZh0ceypOn1EuwTNuZ8xTrHF+jVeIEzLtFFSlD/nJHg==
stylelint@^15.10.1:
  version "15.10.1"
  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.10.1.tgz#93f189958687e330c106b010cbec0c41dcae506d"
  integrity sha512-CYkzYrCFfA/gnOR+u9kJ1PpzwG10WLVnoxHDuBA/JiwGqdM9+yx9+ou6SE/y9YHtfv1mcLo06fdadHTOx4gBZQ==
  dependencies:
    "@csstools/css-parser-algorithms" "^2.2.0"
    "@csstools/css-parser-algorithms" "^2.3.0"
    "@csstools/css-tokenizer" "^2.1.1"
    "@csstools/media-query-list-parser" "^2.1.0"
    "@csstools/selector-specificity" "^2.2.0"
    "@csstools/media-query-list-parser" "^2.1.2"
    "@csstools/selector-specificity" "^3.0.0"
    balanced-match "^2.0.0"
    colord "^2.9.3"
    cosmiconfig "^8.2.0"
    css-functions-list "^3.1.0"
    css-tree "^2.3.1"
    debug "^4.3.4"
    fast-glob "^3.2.12"
    fast-glob "^3.3.0"
    fastest-levenshtein "^1.0.16"
    file-entry-cache "^6.0.1"
    global-modules "^2.0.0"


@@ 11000,12 11073,11 @@ stylelint@^15.6.2:
    is-plain-object "^5.0.0"
    known-css-properties "^0.27.0"
    mathml-tag-names "^2.1.3"
    meow "^9.0.0"
    meow "^10.1.5"
    micromatch "^4.0.5"
    normalize-path "^3.0.0"
    picocolors "^1.0.0"
    postcss "^8.4.24"
    postcss-media-query-parser "^0.2.3"
    postcss-resolve-nested-selector "^0.1.1"
    postcss-safe-parser "^6.0.0"
    postcss-selector-parser "^6.0.13"


@@ 11017,7 11089,6 @@ stylelint@^15.6.2:
    supports-hyperlinks "^3.0.0"
    svg-tags "^1.0.0"
    table "^6.8.1"
    v8-compile-cache "^2.3.0"
    write-file-atomic "^5.0.1"

stylis@4.2.0:


@@ 11342,10 11413,10 @@ tr46@~0.0.3:
  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
  integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==

trim-newlines@^3.0.0:
  version "3.0.1"
  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
  integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
trim-newlines@^4.0.2:
  version "4.1.1"
  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125"
  integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==

tsconfig-paths@^3.14.1:
  version "3.14.2"


@@ 11423,11 11494,6 @@ type-fest@^0.16.0:
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
  integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==

type-fest@^0.18.0:
  version "0.18.1"
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
  integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==

type-fest@^0.20.2:
  version "0.20.2"
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"


@@ 11438,15 11504,10 @@ type-fest@^0.21.3:
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
  integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==

type-fest@^0.6.0:
  version "0.6.0"
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
  integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==

type-fest@^0.8.1:
  version "0.8.1"
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
  integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2:
  version "1.4.0"
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
  integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==

type-is@~1.6.18:
  version "1.6.18"


@@ 11693,7 11754,7 @@ uuid@^9.0.0:
  resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
  integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==

v8-compile-cache@^2.1.1, v8-compile-cache@^2.3.0:
v8-compile-cache@^2.1.1:
  version "2.3.0"
  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
  integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==


@@ 12379,7 12440,7 @@ yargs-parser@^13.1.2:
    camelcase "^5.0.0"
    decamelize "^1.2.0"

yargs-parser@^20.2.1, yargs-parser@^20.2.3:
yargs-parser@^20.2.1, yargs-parser@^20.2.9:
  version "20.2.9"
  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==