diff --git a/.eslintrc.js b/.eslintrc.js index 2b0907b1d5..24961cdd9d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -293,6 +293,7 @@ module.exports = { '.*rc.js', 'ide-helper.js', 'config/webpack/**/*', + 'config/formatjs-formatter.js', ], env: { @@ -323,7 +324,7 @@ module.exports = { 'plugin:import/recommended', 'plugin:import/typescript', 'plugin:promise/recommended', - 'plugin:jsdoc/recommended', + 'plugin:jsdoc/recommended-typescript', 'plugin:prettier/recommended', ], diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 39ae6bc080..5e6556cb53 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,18 +25,6 @@ updates: - dependency-name: 'react-hotkeys' versions: - '>= 2' - # TODO: This version has breaking changes - - dependency-name: 'intl-messageformat' - versions: - - '>= 3' - # TODO: This version has breaking changes - - dependency-name: 'react-intl' - versions: - - '>= 3' - # TODO: This version has breaking changes - - dependency-name: 'babel-plugin-react-intl' - versions: - - '>= 7' # TODO: This version requires code changes - dependency-name: 'webpack-dev-server' versions: diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index e282e2ab72..b67c503e95 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -41,8 +41,7 @@ jobs: - name: Check for missing strings in English JSON run: | - yarn build:development - yarn manage:translations en + yarn i18n:extract --throws git diff --exit-code - name: Check locale file normalization diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index e13d227bdb..51bde39bc1 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -48,4 +48,4 @@ jobs: - run: echo "::add-matcher::.github/stylelint-matcher.json" - name: Stylelint - run: yarn test:lint:sass + run: yarn lint:sass diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 7700e48512..547035ab3f 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -48,7 +48,7 @@ jobs: run: yarn --frozen-lockfile - name: ESLint - run: yarn test:lint:js --max-warnings 0 + run: yarn lint:js --max-warnings 0 - name: Typecheck - run: yarn test:typecheck + run: yarn typecheck diff --git a/.github/workflows/lint-json.yml b/.github/workflows/lint-json.yml index 98f101ad95..7dfc0e0588 100644 --- a/.github/workflows/lint-json.yml +++ b/.github/workflows/lint-json.yml @@ -40,4 +40,4 @@ jobs: run: yarn --frozen-lockfile - name: Prettier - run: yarn prettier --check "**/*.json" + run: yarn lint:json diff --git a/.github/workflows/lint-md.yml b/.github/workflows/lint-md.yml index 6f76dd60c2..b489ce9684 100644 --- a/.github/workflows/lint-md.yml +++ b/.github/workflows/lint-md.yml @@ -5,6 +5,7 @@ on: - 'dependabot/**' paths: - '.github/workflows/lint-md.yml' + - '.nvmrc' - '.prettier*' - '**/*.md' - '!AUTHORS.md' @@ -14,6 +15,7 @@ on: pull_request: paths: - '.github/workflows/lint-md.yml' + - '.nvmrc' - '.prettier*' - '**/*.md' - '!AUTHORS.md' @@ -32,9 +34,10 @@ jobs: uses: actions/setup-node@v3 with: cache: yarn + node-version-file: '.nvmrc' - name: Install all yarn packages run: yarn --frozen-lockfile - name: Prettier - run: yarn prettier --check "**/*.md" + run: yarn lint:md diff --git a/.github/workflows/lint-yml.yml b/.github/workflows/lint-yml.yml index 6f79babcfd..d77451ee62 100644 --- a/.github/workflows/lint-yml.yml +++ b/.github/workflows/lint-yml.yml @@ -42,4 +42,4 @@ jobs: run: yarn --frozen-lockfile - name: Prettier - run: yarn prettier --check "**/*.{yml,yaml}" + run: yarn lint:yml diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 1c4958550e..32e21d23ce 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -44,4 +44,4 @@ jobs: run: yarn --frozen-lockfile - name: Jest testing - run: yarn test:jest --reporters github-actions summary + run: yarn jest --reporters github-actions summary diff --git a/.rubocop.yml b/.rubocop.yml index 2e9dd5518e..a97fa1da33 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -53,6 +53,28 @@ Lint/UselessAccessModifier: ContextCreatingMethods: - class_methods +## Disable most Metrics/*Length cops +# Reason: those are often triggered and force significant refactors when this happend +# but the team feel they are not really improving the code quality. + +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength +Metrics/BlockLength: + Enabled: false + +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength +Metrics/ClassLength: + Enabled: false + +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength +Metrics/MethodLength: + Enabled: false + +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength +Metrics/ModuleLength: + Enabled: false + +## End Disable Metrics/*Length cops + # Reason: Currently disabled in .rubocop_todo.yml # https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize Metrics/AbcSize: @@ -61,95 +83,12 @@ Metrics/AbcSize: - 'lib/mastodon/cli/*.rb' - db/*migrate/**/* -# Reason: Some functions cannot be broken up, but others may be refactor candidates -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength -Metrics/BlockLength: - CountAsOne: ['array', 'hash', 'heredoc', 'method_call'] - Exclude: - - 'config/routes.rb' - - 'lib/mastodon/cli/*.rb' - - 'lib/tasks/*.rake' - - 'app/models/concerns/account_associations.rb' - - 'app/models/concerns/account_interactions.rb' - - 'app/models/concerns/ldap_authenticable.rb' - - 'app/models/concerns/omniauthable.rb' - - 'app/models/concerns/pam_authenticable.rb' - - 'app/models/concerns/remotable.rb' - - 'app/services/suspend_account_service.rb' - - 'app/services/unsuspend_account_service.rb' - - 'app/views/accounts/show.rss.ruby' - - 'app/views/tags/show.rss.ruby' - - 'config/environments/development.rb' - - 'config/environments/production.rb' - - 'config/initializers/devise.rb' - - 'config/initializers/doorkeeper.rb' - - 'config/initializers/omniauth.rb' - - 'config/initializers/simple_form.rb' - - 'config/navigation.rb' - - 'config/routes.rb' - - 'config/routes/*.rb' - - 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb' - - 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb' - - 'lib/paperclip/gif_transcoder.rb' - # Reason: # https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocknesting Metrics/BlockNesting: Exclude: - 'lib/mastodon/cli/*.rb' -# Reason: Some Excluded files would be candidates for refactoring but not currently addressed -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength -Metrics/ClassLength: - CountAsOne: ['array', 'hash', 'heredoc', 'method_call'] - Exclude: - - 'lib/mastodon/cli/*.rb' - - 'app/controllers/admin/accounts_controller.rb' - - 'app/controllers/api/base_controller.rb' - - 'app/controllers/api/v1/admin/accounts_controller.rb' - - 'app/controllers/application_controller.rb' - - 'app/controllers/auth/registrations_controller.rb' - - 'app/controllers/auth/sessions_controller.rb' - - 'app/lib/activitypub/activity.rb' - - 'app/lib/activitypub/activity/create.rb' - - 'app/lib/activitypub/activity/undo.rb' - - 'app/lib/activitypub/tag_manager.rb' - - 'app/lib/feed_manager.rb' - - 'app/lib/link_details_extractor.rb' - - 'app/lib/request.rb' - - 'app/lib/status_reach_finder.rb' - - 'app/lib/text_formatter.rb' - - 'app/lib/user_settings_decorator.rb' - - 'app/mailers/user_mailer.rb' - - 'app/models/account.rb' - - 'app/models/account_statuses_filter.rb' - - 'app/models/admin/account_action.rb' - - 'app/models/antenna.rb' - - 'app/models/form/account_batch.rb' - - 'app/models/media_attachment.rb' - - 'app/models/status.rb' - - 'app/models/tag.rb' - - 'app/models/user.rb' - - 'app/policies/status_policy.rb' - - 'app/serializers/activitypub/actor_serializer.rb' - - 'app/serializers/activitypub/note_serializer.rb' - - 'app/serializers/rest/account_serializer.rb' - - 'app/serializers/rest/status_serializer.rb' - - 'app/services/account_search_service.rb' - - 'app/services/activitypub/process_account_service.rb' - - 'app/services/activitypub/process_status_update_service.rb' - - 'app/services/backup_service.rb' - - 'app/services/bulk_import_service.rb' - - 'app/services/delete_account_service.rb' - - 'app/services/fan_out_on_write_service.rb' - - 'app/services/fetch_link_card_service.rb' - - 'app/services/import_service.rb' - - 'app/services/notify_service.rb' - - 'app/services/post_status_service.rb' - - 'app/services/search_service.rb' - - 'app/services/update_status_service.rb' - - 'lib/paperclip/color_extractor.rb' - # Reason: Currently disabled in .rubocop_todo.yml # https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity Metrics/CyclomaticComplexity: @@ -160,17 +99,10 @@ Metrics/CyclomaticComplexity: - lib/mastodon/cli/*.rb - db/*migrate/**/* -# Reason: Currently disabled in .rubocop_todo.yml -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength -Metrics/MethodLength: - CountAsOne: [array, heredoc] - Exclude: - - 'lib/mastodon/cli/*.rb' - # Reason: -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength -Metrics/ModuleLength: - CountAsOne: [array, heredoc] +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists +Metrics/ParameterLists: + CountKeywordArgs: false Metrics/PerceivedComplexity: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6c09cddd51..e68d725dd9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -154,12 +154,6 @@ Lint/Void: Metrics/AbcSize: Max: 150 -# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. -# AllowedMethods: refine -Metrics/BlockLength: - Exclude: - - 'app/models/concerns/status_safe_reblog_insert.rb' - # Configuration parameters: CountBlocks, Max. Metrics/BlockNesting: Exclude: @@ -169,27 +163,6 @@ Metrics/BlockNesting: Metrics/CyclomaticComplexity: Max: 25 -# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. -Metrics/MethodLength: - Max: 58 - -# Configuration parameters: CountComments, Max, CountAsOne. -Metrics/ModuleLength: - Exclude: - - 'app/controllers/concerns/signature_verification.rb' - - 'app/helpers/application_helper.rb' - - 'app/helpers/jsonld_helper.rb' - - 'app/models/concerns/account_interactions.rb' - - 'app/models/concerns/has_user_settings.rb' - -# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. -Metrics/ParameterLists: - Exclude: - - 'app/models/concerns/account_interactions.rb' - - 'app/services/activitypub/fetch_remote_account_service.rb' - - 'app/services/activitypub/fetch_remote_actor_service.rb' - - 'app/services/activitypub/fetch_remote_status_service.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 28 diff --git a/Gemfile b/Gemfile index 400afef4af..5aad3f615b 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'makara', '~> 0.5' gem 'pghero' gem 'dotenv-rails', '~> 2.8' -gem 'aws-sdk-s3', '~> 1.122', require: false +gem 'aws-sdk-s3', '~> 1.123', require: false gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 0.3', require: false gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' diff --git a/Gemfile.lock b/Gemfile.lock index 5f61144d89..58ae5301b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,17 +109,17 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.761.0) - aws-sdk-core (3.172.0) + aws-partitions (1.772.0) + aws-sdk-core (3.174.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.64.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.65.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.122.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.123.0) + aws-sdk-core (~> 3, >= 3.174.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) @@ -544,8 +544,9 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) @@ -588,7 +589,7 @@ GEM rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.0.2) + rspec-rails (6.0.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -770,7 +771,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotate (~> 3.2) - aws-sdk-s3 (~> 1.122) + aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 88139d59d3..96bf2048c0 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -31,31 +31,41 @@ module Admin @domain_block = DomainBlock.new(resource_params) existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil + # Disallow accidentally downgrading a domain block if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block) @domain_block.save flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe @domain_block.errors.delete(:domain) - render :new - else - if existing_domain_block.present? - @domain_block = existing_domain_block - @domain_block.update(resource_params) - end + return render :new + end - if @domain_block.save - DomainBlockWorker.perform_async(@domain_block.id) - log_action :create, @domain_block - redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') - else - render :new - end + # Allow transparently upgrading a domain block + if existing_domain_block.present? + @domain_block = existing_domain_block + @domain_block.assign_attributes(resource_params) + end + + # Require explicit confirmation when suspending + return render :confirm_suspension if requires_confirmation? + + if @domain_block.save + DomainBlockWorker.perform_async(@domain_block.id) + log_action :create, @domain_block + redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') + else + render :new end end def update authorize :domain_block, :update? - if @domain_block.update(update_params) + @domain_block.assign_attributes(update_params) + + # Require explicit confirmation when suspending + return render :confirm_suspension if requires_confirmation? + + if @domain_block.save DomainBlockWorker.perform_async(@domain_block.id, @domain_block.severity_previously_changed?) log_action :update, @domain_block redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') @@ -92,5 +102,9 @@ module Admin def action_from_button 'save' if params[:save] end + + def requires_confirmation? + @domain_block.valid? && (@domain_block.new_record? || @domain_block.severity_changed?) && @domain_block.severity.to_s == 'suspend' && !params[:confirm] + end end end diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index 9034e8a2f4..c55500f761 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -11,7 +11,7 @@ class Api::V1::ConversationsController < Api::BaseController def index @conversations = paginated_conversations - render json: @conversations, each_serializer: REST::ConversationSerializer + render json: @conversations, each_serializer: REST::ConversationSerializer, relationships: StatusRelationshipsPresenter.new(@conversations.map(&:last_status), current_user&.account_id) end def read @@ -32,7 +32,20 @@ class Api::V1::ConversationsController < Api::BaseController def paginated_conversations AccountConversation.where(account: current_account) - .to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + .includes( + account: :account_stat, + last_status: [ + :media_attachments, + :preview_cards, + :status_stat, + :tags, + { + active_mentions: [account: :account_stat], + account: :account_stat, + }, + ] + ) + .to_a_paginated_by_id(limit_param(LIMIT), **params_slice(:max_id, :since_id, :min_id)) end def insert_pagination_headers diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5520c53b97..d6bb4fbffd 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -173,11 +173,11 @@ module ApplicationHelper end def storage_host - URI::HTTPS.build(host: storage_host_name).to_s + "https://#{storage_host_var}" end def storage_host? - storage_host_name.present? + storage_host_var.present? end def quote_wrap(text, line_width: 80, break_sequence: "\n") @@ -248,7 +248,7 @@ module ApplicationHelper private - def storage_host_name + def storage_host_var ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) end end diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index 00e1e5178b..840a18d3e6 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# rubocop:disable Metrics/ModuleLength - module LanguagesHelper ISO_639_1 = { aa: ['Afar', 'Afaraf'].freeze, diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 3d5592867c..ae89cec780 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -5,10 +5,6 @@ module SettingsHelper LanguagesHelper::SUPPORTED_LOCALES.keys end - def hash_to_object(hash) - HashObject.new(hash) - end - def session_device_icon(session) device = session.detection.device diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index bb26d8485e..8b3e887d83 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -6,7 +6,7 @@ import { unescapeHTML } from '../../utils/html'; const domParser = new DOMParser(); -const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => { +const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => { obj[`:${emoji.shortcode}:`] = emoji; return obj; }, {}); @@ -20,7 +20,7 @@ export function searchTextFromRawStatus (status) { export function normalizeAccount(account) { account = { ...account }; - const emojiMap = makeEmojiMap(account); + const emojiMap = makeEmojiMap(account.emojis); const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name; account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap); @@ -98,7 +98,7 @@ export function normalizeStatus(status, normalOldStatus) { const spoilerText = normalStatus.spoiler_text || ''; const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); - const emojiMap = makeEmojiMap(normalStatus); + const emojiMap = makeEmojiMap(normalStatus.emojis); normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); @@ -120,22 +120,48 @@ export function normalizeEmojiReactions(emoji_reactions) { return converted; } +export function normalizeStatusTranslation(translation, status) { + const emojiMap = makeEmojiMap(status.get('emojis').toJS()); + + const normalTranslation = { + detected_source_language: translation.detected_source_language, + language: translation.language, + provider: translation.provider, + contentHtml: emojify(translation.content, emojiMap), + spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap), + spoiler_text: translation.spoiler_text, + }; + + return normalTranslation; +} + export function normalizePoll(poll) { const normalPoll = { ...poll }; - const emojiMap = makeEmojiMap(normalPoll); + const emojiMap = makeEmojiMap(poll.emojis); normalPoll.options = poll.options.map((option, index) => ({ ...option, voted: poll.own_votes && poll.own_votes.includes(index), - title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap), + titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap), })); return normalPoll; } +export function normalizePollOptionTranslation(translation, poll) { + const emojiMap = makeEmojiMap(poll.get('emojis').toJS()); + + const normalTranslation = { + ...translation, + titleHtml: emojify(escapeTextContentForBrowser(translation.title), emojiMap), + }; + + return normalTranslation; +} + export function normalizeAnnouncement(announcement) { const normalAnnouncement = { ...announcement }; - const emojiMap = makeEmojiMap(normalAnnouncement); + const emojiMap = makeEmojiMap.emojis(normalAnnouncement); normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 401e714ad7..c9def4962d 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -1,4 +1,4 @@ -import IntlMessageFormat from 'intl-messageformat'; +import { IntlMessageFormat } from 'intl-messageformat'; import { defineMessages } from 'react-intl'; import { List as ImmutableList } from 'immutable'; diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 7d34e124c1..2d987aef5c 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -345,9 +345,10 @@ export const translateStatusFail = (id, error) => ({ error, }); -export const undoStatusTranslation = id => ({ +export const undoStatusTranslation = (id, pollId) => ({ type: STATUS_TRANSLATE_UNDO, id, + pollId, }); export const updateEmojiReaction = (emoji_reaction) => ({ diff --git a/app/javascript/mastodon/components/admin/ImpactReport.jsx b/app/javascript/mastodon/components/admin/ImpactReport.jsx new file mode 100644 index 0000000000..c27ee0ab08 --- /dev/null +++ b/app/javascript/mastodon/components/admin/ImpactReport.jsx @@ -0,0 +1,91 @@ +import PropTypes from 'prop-types'; +import { PureComponent } from 'react'; + +import { FormattedNumber, FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import api from 'mastodon/api'; +import { Skeleton } from 'mastodon/components/skeleton'; + +export default class ImpactReport extends PureComponent { + + static propTypes = { + domain: PropTypes.string.isRequired, + }; + + state = { + loading: true, + data: null, + }; + + componentDidMount () { + const { domain } = this.props; + + const params = { + domain: domain, + include_subdomains: true, + }; + + api().post('/api/v1/admin/measures', { + keys: ['instance_accounts', 'instance_follows', 'instance_followers'], + start_at: null, + end_at: null, + instance_accounts: params, + instance_follows: params, + instance_followers: params, + }).then(res => { + this.setState({ + loading: false, + data: res.data, + }); + }).catch(err => { + console.error(err); + }); + } + + render () { + const { loading, data } = this.state; + + return ( +

+

+ + + + + + + + + + 0 })}> + + + + + + 0 })}> + + + + + +
+ + + {loading ? : } +
+ + + {loading ? : } +
+ + + {loading ? : } +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/components/domain.tsx b/app/javascript/mastodon/components/domain.tsx index db18635be1..f4a3b9d4b6 100644 --- a/app/javascript/mastodon/components/domain.tsx +++ b/app/javascript/mastodon/components/domain.tsx @@ -1,7 +1,6 @@ import { useCallback } from 'react'; -import type { InjectedIntl } from 'react-intl'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; import { IconButton } from './icon_button'; @@ -15,9 +14,11 @@ const messages = defineMessages({ interface Props { domain: string; onUnblockDomain: (domain: string) => void; - intl: InjectedIntl; } -const _Domain: React.FC = ({ domain, onUnblockDomain, intl }) => { + +export const Domain: React.FC = ({ domain, onUnblockDomain }) => { + const intl = useIntl(); + const handleDomainUnblock = useCallback(() => { onUnblockDomain(domain); }, [domain, onUnblockDomain]); @@ -41,5 +42,3 @@ const _Domain: React.FC = ({ domain, onUnblockDomain, intl }) => { ); }; - -export const Domain = injectIntl(_Domain); diff --git a/app/javascript/mastodon/components/load_gap.tsx b/app/javascript/mastodon/components/load_gap.tsx index e6d3060eb3..7e2cd447b9 100644 --- a/app/javascript/mastodon/components/load_gap.tsx +++ b/app/javascript/mastodon/components/load_gap.tsx @@ -1,7 +1,6 @@ import { useCallback } from 'react'; -import type { InjectedIntl } from 'react-intl'; -import { injectIntl, defineMessages } from 'react-intl'; +import { useIntl, defineMessages } from 'react-intl'; import { Icon } from 'mastodon/components/icon'; @@ -13,10 +12,11 @@ interface Props { disabled: boolean; maxId: string; onClick: (maxId: string) => void; - intl: InjectedIntl; } -const _LoadGap: React.FC = ({ disabled, maxId, onClick, intl }) => { +export const LoadGap: React.FC = ({ disabled, maxId, onClick }) => { + const intl = useIntl(); + const handleClick = useCallback(() => { onClick(maxId); }, [maxId, onClick]); @@ -32,5 +32,3 @@ const _LoadGap: React.FC = ({ disabled, maxId, onClick, intl }) => { ); }; - -export const LoadGap = injectIntl(_LoadGap); diff --git a/app/javascript/mastodon/components/media_attachments.jsx b/app/javascript/mastodon/components/media_attachments.jsx index d2f1712437..7b945a0ea2 100644 --- a/app/javascript/mastodon/components/media_attachments.jsx +++ b/app/javascript/mastodon/components/media_attachments.jsx @@ -51,8 +51,9 @@ export default class MediaAttachments extends ImmutablePureComponent { }; render () { - const { status, lang, width, height } = this.props; + const { status, width, height } = this.props; const mediaAttachments = status.get('media_attachments'); + const language = status.getIn(['language', 'translation']) || status.get('language') || this.props.lang; if (mediaAttachments.size === 0) { return null; @@ -60,14 +61,15 @@ export default class MediaAttachments extends ImmutablePureComponent { if (mediaAttachments.getIn([0, 'type']) === 'audio') { const audio = mediaAttachments.get(0); + const description = audio.getIn(['translation', 'description']) || audio.get('description'); return ( {Component => ( @@ -90,8 +93,8 @@ export default class MediaAttachments extends ImmutablePureComponent { frameRate={video.getIn(['meta', 'original', 'frame_rate'])} blurhash={video.get('blurhash')} src={video.get('url')} - alt={video.get('description')} - lang={lang || status.get('language')} + alt={description} + lang={language} width={width} height={height} inline @@ -107,7 +110,7 @@ export default class MediaAttachments extends ImmutablePureComponent { {Component => ( ALT); } + const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); + if (attachment.get('type') === 'unknown') { return (
- +