diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 97a363d1e..da4203e35 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -43,9 +43,16 @@ jobs: type=edge,branch=main type=sha,prefix=,format=long + - name: Generate version suffix + id: version_vars + if: github.repository == 'mastodon/mastodon' && github.event_name == 'push' && github.ref_name == 'main' + run: | + echo mastodon_version_suffix=+edge-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT + - uses: docker/build-push-action@v4 with: context: . + build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }} platforms: linux/amd64,linux/arm64 provenance: false builder: ${{ steps.buildx.outputs.name }} diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 501db6e9c..f07f7447c 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -41,9 +41,15 @@ jobs: labels: | org.opencontainers.image.description=Nightly build image used for testing purposes + - name: Generate version suffix + id: version_vars + run: | + echo mastodon_version_suffix=+nightly-$(date +'%Y%m%d') >> $GITHUB_OUTPUT + - uses: docker/build-push-action@v4 with: context: . + build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }} platforms: linux/amd64,linux/arm64 provenance: false builder: ${{ steps.buildx.outputs.name }} diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index e0c309c73..7700e4851 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 + run: yarn test:lint:js --max-warnings 0 - name: Typecheck run: yarn test:typecheck diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 6b8d6fdfc..f284745ea 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -9,7 +9,6 @@ on: env: BUNDLE_CLEAN: true BUNDLE_FROZEN: true - BUNDLE_WITHOUT: 'development production' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,8 +18,17 @@ jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + mode: + - production + - test env: - RAILS_ENV: test + RAILS_ENV: ${{ matrix.mode }} + BUNDLE_WITH: ${{ matrix.mode }} + OTP_SECRET: precompile_placeholder + SECRET_KEY_BASE: precompile_placeholder steps: - uses: actions/checkout@v3 @@ -50,6 +58,7 @@ jobs: ./bin/rails assets:precompile - uses: actions/upload-artifact@v3 + if: matrix.mode == 'test' with: path: |- ./public/assets @@ -97,7 +106,7 @@ jobs: PAM_ENABLED: true PAM_DEFAULT_SERVICE: pam_test PAM_CONTROLLED_SERVICE: pam_test_controlled - BUNDLE_WITH: 'pam_authentication' + BUNDLE_WITH: 'pam_authentication test' CI_JOBS: ${{ matrix.ci_job }}/4 strategy: diff --git a/.profile b/.profile index c6d57b609..f4826ea30 100644 --- a/.profile +++ b/.profile @@ -1 +1 @@ -LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio +LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio:/app/.apt/usr/lib/x86_64-linux-gnu/openblas-pthread diff --git a/.rubocop.yml b/.rubocop.yml index b510c4303..966a2a43d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -65,6 +65,7 @@ Metrics/AbcSize: 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' @@ -85,6 +86,7 @@ Metrics/BlockLength: - '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' @@ -130,6 +132,7 @@ Metrics/ClassLength: - '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' @@ -158,6 +161,11 @@ Metrics/MethodLength: Metrics/ModuleLength: CountAsOne: [array, heredoc] +# Reason: Prevailing style is argument file paths +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath +Rails/FilePath: + EnforcedStyle: arguments + # Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus # https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus Rails/HttpStatus: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 58e82a90c..c2dc6e4bf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -21,13 +21,6 @@ Layout/ArgumentAlignment: - 'config/initializers/cors.rb' - 'config/initializers/session_store.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, no_empty_lines -Layout/EmptyLinesAroundBlockBody: - Exclude: - - 'config/routes.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. Layout/ExtraSpacing: @@ -106,28 +99,6 @@ Lint/AmbiguousOperatorPrecedence: Exclude: - 'config/initializers/rack_attack.rb' -# Configuration parameters: AllowedMethods. -# AllowedMethods: enums -Lint/ConstantDefinitionInBlock: - Exclude: - - 'spec/controllers/api/base_controller_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/controllers/concerns/accountable_concern_spec.rb' - - 'spec/controllers/concerns/signature_verification_spec.rb' - - 'spec/lib/activitypub/adapter_spec.rb' - - 'spec/lib/connection_pool/shared_connection_pool_spec.rb' - - 'spec/lib/connection_pool/shared_timed_stack_spec.rb' - - 'spec/models/concerns/remotable_spec.rb' - -# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. -Lint/DuplicateBranch: - Exclude: - - 'app/lib/permalink_redirector.rb' - - 'app/models/account_statuses_filter.rb' - - 'app/validators/email_mx_validator.rb' - - 'app/validators/vote_validator.rb' - - 'lib/mastodon/maintenance_cli.rb' - # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: @@ -168,11 +139,6 @@ Lint/EmptyBlock: - 'spec/models/user_role_spec.rb' - 'spec/models/web/setting_spec.rb' -# Configuration parameters: AllowComments. -Lint/EmptyClass: - Exclude: - - 'spec/controllers/api/base_controller_spec.rb' - Lint/NonLocalExitFromIterator: Exclude: - 'app/helpers/jsonld_helper.rb' @@ -228,6 +194,12 @@ Metrics/AbcSize: Exclude: - 'app/serializers/initial_state_serializer.rb' +# 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: @@ -305,42 +277,6 @@ Naming/VariableNumber: - 'spec/models/user_spec.rb' - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb' -# Configuration parameters: MinSize. -Performance/CollectionLiteralInLoop: - Exclude: - - 'app/models/admin/appeal_filter.rb' - - 'app/models/admin/status_filter.rb' - - 'app/models/relationship_filter.rb' - - 'app/models/trends/preview_card_filter.rb' - - 'app/models/trends/status_filter.rb' - - 'app/presenters/status_relationships_presenter.rb' - - 'app/services/fetch_resource_service.rb' - - 'app/services/suspend_account_service.rb' - - 'app/services/unsuspend_account_service.rb' - - 'config/deploy.rb' - - 'lib/mastodon/media_cli.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Performance/Count: - Exclude: - - 'app/lib/importer/accounts_index_importer.rb' - - 'app/lib/importer/tags_index_importer.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SafeMultiline. -Performance/DeletePrefix: - Exclude: - - 'app/controllers/authorize_interactions_controller.rb' - - 'app/controllers/concerns/signature_verification.rb' - - 'app/controllers/intents_controller.rb' - - 'app/lib/activitypub/case_transform.rb' - - 'app/lib/permalink_redirector.rb' - - 'app/lib/webfinger_resource.rb' - - 'app/services/activitypub/fetch_remote_actor_service.rb' - - 'app/services/backup_service.rb' - - 'app/services/resolve_account_service.rb' - - 'app/services/tag_search_service.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Performance/MapCompact: Exclude: @@ -360,46 +296,12 @@ Performance/MapCompact: - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb' - 'spec/presenters/status_relationships_presenter_spec.rb' -Performance/MethodObjectAsBlock: - Exclude: - - 'app/models/account_suggestions/source.rb' - - 'spec/models/export_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowRegexpMatch. -Performance/RedundantEqualityComparisonBlock: - Exclude: - - 'spec/requests/link_headers_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: MaxKeyValuePairs. -Performance/RedundantMerge: - Exclude: - - 'config/initializers/paperclip.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SafeMultiline. Performance/StartWith: Exclude: - 'app/lib/extractor.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: OnlySumOrWithInitialValue. -Performance/Sum: - Exclude: - - 'app/lib/activity_tracker.rb' - - 'app/models/trends/history.rb' - - 'lib/paperclip/color_extractor.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Performance/TimesMap: - Exclude: - - 'spec/controllers/api/v1/blocks_controller_spec.rb' - - 'spec/controllers/api/v1/mutes_controller_spec.rb' - - 'spec/lib/feed_manager_spec.rb' - - 'spec/lib/request_pool_spec.rb' - - 'spec/models/account_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Performance/UnfreezeString: Exclude: @@ -428,116 +330,6 @@ RSpec/AnyInstance: - 'spec/workers/activitypub/delivery_worker_spec.rb' - 'spec/workers/web/push_notification_worker_spec.rb' -# Configuration parameters: Prefixes, AllowedPatterns. -# Prefixes: when, with, without -RSpec/ContextWording: - Exclude: - - 'spec/config/initializers/rack_attack_spec.rb' - - 'spec/controllers/accounts_controller_spec.rb' - - 'spec/controllers/activitypub/collections_controller_spec.rb' - - 'spec/controllers/activitypub/inboxes_controller_spec.rb' - - 'spec/controllers/admin/domain_blocks_controller_spec.rb' - - 'spec/controllers/admin/reports/actions_controller_spec.rb' - - 'spec/controllers/admin/statuses_controller_spec.rb' - - 'spec/controllers/api/v1/accounts/relationships_controller_spec.rb' - - 'spec/controllers/api/v1/accounts_controller_spec.rb' - - 'spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb' - - 'spec/controllers/api/v1/emails/confirmations_controller_spec.rb' - - 'spec/controllers/api/v1/instances/activity_controller_spec.rb' - - 'spec/controllers/api/v1/instances/peers_controller_spec.rb' - - 'spec/controllers/api/v1/media_controller_spec.rb' - - 'spec/controllers/api/v2/filters_controller_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/controllers/auth/registrations_controller_spec.rb' - - 'spec/controllers/auth/sessions_controller_spec.rb' - - 'spec/controllers/concerns/cache_concern_spec.rb' - - 'spec/controllers/concerns/challengable_concern_spec.rb' - - 'spec/controllers/concerns/localized_spec.rb' - - 'spec/controllers/concerns/rate_limit_headers_spec.rb' - - 'spec/controllers/instance_actors_controller_spec.rb' - - 'spec/controllers/settings/applications_controller_spec.rb' - - 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb' - - 'spec/controllers/statuses_controller_spec.rb' - - 'spec/helpers/admin/account_moderation_notes_helper_spec.rb' - - 'spec/helpers/jsonld_helper_spec.rb' - - 'spec/helpers/routing_helper_spec.rb' - - 'spec/lib/activitypub/activity/accept_spec.rb' - - 'spec/lib/activitypub/activity/announce_spec.rb' - - 'spec/lib/activitypub/activity/create_spec.rb' - - 'spec/lib/activitypub/activity/follow_spec.rb' - - 'spec/lib/activitypub/activity/reject_spec.rb' - - 'spec/lib/advanced_text_formatter_spec.rb' - - 'spec/lib/emoji_formatter_spec.rb' - - 'spec/lib/entity_cache_spec.rb' - - 'spec/lib/feed_manager_spec.rb' - - 'spec/lib/html_aware_formatter_spec.rb' - - 'spec/lib/link_details_extractor_spec.rb' - - 'spec/lib/ostatus/tag_manager_spec.rb' - - 'spec/lib/scope_transformer_spec.rb' - - 'spec/lib/status_cache_hydrator_spec.rb' - - 'spec/lib/status_reach_finder_spec.rb' - - 'spec/lib/text_formatter_spec.rb' - - 'spec/models/account/field_spec.rb' - - 'spec/models/account_spec.rb' - - 'spec/models/admin/account_action_spec.rb' - - 'spec/models/concerns/account_interactions_spec.rb' - - 'spec/models/concerns/remotable_spec.rb' - - 'spec/models/custom_emoji_filter_spec.rb' - - 'spec/models/custom_emoji_spec.rb' - - 'spec/models/email_domain_block_spec.rb' - - 'spec/models/media_attachment_spec.rb' - - 'spec/models/notification_spec.rb' - - 'spec/models/remote_follow_spec.rb' - - 'spec/models/report_spec.rb' - - 'spec/models/session_activation_spec.rb' - - 'spec/models/setting_spec.rb' - - 'spec/models/status_spec.rb' - - 'spec/models/web/push_subscription_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/presenters/account_relationships_presenter_spec.rb' - - 'spec/presenters/status_relationships_presenter_spec.rb' - - 'spec/services/account_search_service_spec.rb' - - 'spec/services/account_statuses_cleanup_service_spec.rb' - - 'spec/services/activitypub/fetch_remote_status_service_spec.rb' - - 'spec/services/activitypub/process_account_service_spec.rb' - - 'spec/services/activitypub/process_status_update_service_spec.rb' - - 'spec/services/fetch_link_card_service_spec.rb' - - 'spec/services/fetch_oembed_service_spec.rb' - - 'spec/services/fetch_remote_status_service_spec.rb' - - 'spec/services/follow_service_spec.rb' - - 'spec/services/import_service_spec.rb' - - 'spec/services/notify_service_spec.rb' - - 'spec/services/process_mentions_service_spec.rb' - - 'spec/services/reblog_service_spec.rb' - - 'spec/services/report_service_spec.rb' - - 'spec/services/resolve_account_service_spec.rb' - - 'spec/services/resolve_url_service_spec.rb' - - 'spec/services/search_service_spec.rb' - - 'spec/services/unallow_domain_service_spec.rb' - - 'spec/services/verify_link_service_spec.rb' - - 'spec/validators/disallowed_hashtags_validator_spec.rb' - - 'spec/validators/email_mx_validator_spec.rb' - - 'spec/validators/follow_limit_validator_spec.rb' - - 'spec/validators/poll_validator_spec.rb' - - 'spec/validators/status_pin_validator_spec.rb' - - 'spec/validators/unreserved_username_validator_spec.rb' - - 'spec/validators/url_validator_spec.rb' - - 'spec/workers/move_worker_spec.rb' - - 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: SkipBlocks, EnforcedStyle. # SupportedStyles: described_class, explicit @@ -701,7 +493,6 @@ RSpec/InstanceVariable: - 'spec/controllers/statuses_cleanup_controller_spec.rb' - 'spec/models/concerns/account_finder_concern_spec.rb' - 'spec/models/concerns/account_interactions_spec.rb' - - 'spec/models/concerns/remotable_spec.rb' - 'spec/models/public_feed_spec.rb' - 'spec/serializers/activitypub/note_serializer_spec.rb' - 'spec/serializers/activitypub/update_poll_serializer_spec.rb' @@ -709,17 +500,6 @@ RSpec/InstanceVariable: - 'spec/services/search_service_spec.rb' - 'spec/services/unblock_domain_service_spec.rb' -RSpec/LeakyConstantDeclaration: - Exclude: - - 'spec/controllers/api/base_controller_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/controllers/concerns/accountable_concern_spec.rb' - - 'spec/controllers/concerns/signature_verification_spec.rb' - - 'spec/lib/activitypub/adapter_spec.rb' - - 'spec/lib/connection_pool/shared_connection_pool_spec.rb' - - 'spec/lib/connection_pool/shared_timed_stack_spec.rb' - - 'spec/models/concerns/remotable_spec.rb' - RSpec/LetSetup: Exclude: - 'spec/controllers/admin/accounts_controller_spec.rb' @@ -745,6 +525,7 @@ RSpec/LetSetup: - 'spec/controllers/following_accounts_controller_spec.rb' - 'spec/controllers/oauth/authorized_applications_controller_spec.rb' - 'spec/controllers/oauth/tokens_controller_spec.rb' + - 'spec/controllers/settings/imports_controller_spec.rb' - 'spec/lib/activitypub/activity/delete_spec.rb' - 'spec/lib/vacuum/preview_cards_vacuum_spec.rb' - 'spec/models/account_spec.rb' @@ -759,6 +540,7 @@ RSpec/LetSetup: - 'spec/services/activitypub/process_collection_service_spec.rb' - 'spec/services/batched_remove_status_service_spec.rb' - 'spec/services/block_domain_service_spec.rb' + - 'spec/services/bulk_import_service_spec.rb' - 'spec/services/delete_account_service_spec.rb' - 'spec/services/import_service_spec.rb' - 'spec/services/notify_service_spec.rb' @@ -831,17 +613,6 @@ RSpec/MultipleExpectations: RSpec/MultipleMemoizedHelpers: Max: 21 -# This cop supports safe autocorrection (--autocorrect). -RSpec/MultipleSubjects: - Exclude: - - 'spec/controllers/activitypub/collections_controller_spec.rb' - - 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb' - - 'spec/controllers/activitypub/outboxes_controller_spec.rb' - - 'spec/controllers/api/web/embeds_controller_spec.rb' - - 'spec/controllers/emojis_controller_spec.rb' - - 'spec/controllers/follower_accounts_controller_spec.rb' - - 'spec/controllers/following_accounts_controller_spec.rb' - # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 6 @@ -867,181 +638,6 @@ RSpec/PredicateMatcher: - 'spec/models/user_spec.rb' - 'spec/services/post_status_service_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Inferences. -RSpec/Rails/InferredSpecType: - Exclude: - - 'spec/controllers/about_controller_spec.rb' - - 'spec/controllers/accounts_controller_spec.rb' - - 'spec/controllers/activitypub/collections_controller_spec.rb' - - 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb' - - 'spec/controllers/activitypub/inboxes_controller_spec.rb' - - 'spec/controllers/activitypub/outboxes_controller_spec.rb' - - 'spec/controllers/activitypub/replies_controller_spec.rb' - - 'spec/controllers/admin/account_moderation_notes_controller_spec.rb' - - 'spec/controllers/admin/accounts_controller_spec.rb' - - 'spec/controllers/admin/action_logs_controller_spec.rb' - - 'spec/controllers/admin/base_controller_spec.rb' - - 'spec/controllers/admin/change_emails_controller_spec.rb' - - 'spec/controllers/admin/confirmations_controller_spec.rb' - - 'spec/controllers/admin/dashboard_controller_spec.rb' - - 'spec/controllers/admin/disputes/appeals_controller_spec.rb' - - 'spec/controllers/admin/domain_allows_controller_spec.rb' - - 'spec/controllers/admin/domain_blocks_controller_spec.rb' - - 'spec/controllers/admin/email_domain_blocks_controller_spec.rb' - - 'spec/controllers/admin/export_domain_allows_controller_spec.rb' - - 'spec/controllers/admin/export_domain_blocks_controller_spec.rb' - - 'spec/controllers/admin/instances_controller_spec.rb' - - 'spec/controllers/admin/settings/branding_controller_spec.rb' - - 'spec/controllers/admin/tags_controller_spec.rb' - - 'spec/controllers/api/oembed_controller_spec.rb' - - 'spec/controllers/api/v1/accounts/pins_controller_spec.rb' - - 'spec/controllers/api/v1/accounts/search_controller_spec.rb' - - 'spec/controllers/api/v1/accounts_controller_spec.rb' - - 'spec/controllers/api/v1/admin/account_actions_controller_spec.rb' - - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' - - 'spec/controllers/api/v1/admin/domain_allows_controller_spec.rb' - - 'spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb' - - 'spec/controllers/api/v1/admin/reports_controller_spec.rb' - - 'spec/controllers/api/v1/announcements/reactions_controller_spec.rb' - - 'spec/controllers/api/v1/announcements_controller_spec.rb' - - 'spec/controllers/api/v1/apps_controller_spec.rb' - - 'spec/controllers/api/v1/blocks_controller_spec.rb' - - 'spec/controllers/api/v1/bookmarks_controller_spec.rb' - - 'spec/controllers/api/v1/conversations_controller_spec.rb' - - 'spec/controllers/api/v1/custom_emojis_controller_spec.rb' - - 'spec/controllers/api/v1/domain_blocks_controller_spec.rb' - - 'spec/controllers/api/v1/emails/confirmations_controller_spec.rb' - - 'spec/controllers/api/v1/endorsements_controller_spec.rb' - - 'spec/controllers/api/v1/favourites_controller_spec.rb' - - 'spec/controllers/api/v1/filters_controller_spec.rb' - - 'spec/controllers/api/v1/follow_requests_controller_spec.rb' - - 'spec/controllers/api/v1/followed_tags_controller_spec.rb' - - 'spec/controllers/api/v1/instances/activity_controller_spec.rb' - - 'spec/controllers/api/v1/instances/peers_controller_spec.rb' - - 'spec/controllers/api/v1/instances_controller_spec.rb' - - 'spec/controllers/api/v1/lists_controller_spec.rb' - - 'spec/controllers/api/v1/markers_controller_spec.rb' - - 'spec/controllers/api/v1/media_controller_spec.rb' - - 'spec/controllers/api/v1/mutes_controller_spec.rb' - - 'spec/controllers/api/v1/notifications_controller_spec.rb' - - 'spec/controllers/api/v1/polls/votes_controller_spec.rb' - - 'spec/controllers/api/v1/polls_controller_spec.rb' - - 'spec/controllers/api/v1/reports_controller_spec.rb' - - 'spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb' - - 'spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb' - - 'spec/controllers/api/v1/statuses_controller_spec.rb' - - 'spec/controllers/api/v1/suggestions_controller_spec.rb' - - 'spec/controllers/api/v1/tags_controller_spec.rb' - - 'spec/controllers/api/v1/trends/tags_controller_spec.rb' - - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb' - - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb' - - 'spec/controllers/api/v2/filters/statuses_controller_spec.rb' - - 'spec/controllers/api/v2/filters_controller_spec.rb' - - 'spec/controllers/api/v2/search_controller_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/controllers/auth/challenges_controller_spec.rb' - - 'spec/controllers/auth/confirmations_controller_spec.rb' - - 'spec/controllers/auth/passwords_controller_spec.rb' - - 'spec/controllers/auth/registrations_controller_spec.rb' - - 'spec/controllers/auth/sessions_controller_spec.rb' - - 'spec/controllers/concerns/account_controller_concern_spec.rb' - - 'spec/controllers/concerns/cache_concern_spec.rb' - - 'spec/controllers/concerns/challengable_concern_spec.rb' - - 'spec/controllers/concerns/export_controller_concern_spec.rb' - - 'spec/controllers/concerns/localized_spec.rb' - - 'spec/controllers/concerns/signature_verification_spec.rb' - - 'spec/controllers/concerns/user_tracking_concern_spec.rb' - - 'spec/controllers/disputes/appeals_controller_spec.rb' - - 'spec/controllers/disputes/strikes_controller_spec.rb' - - 'spec/controllers/home_controller_spec.rb' - - 'spec/controllers/instance_actors_controller_spec.rb' - - 'spec/controllers/intents_controller_spec.rb' - - 'spec/controllers/oauth/authorizations_controller_spec.rb' - - 'spec/controllers/oauth/tokens_controller_spec.rb' - - 'spec/controllers/settings/imports_controller_spec.rb' - - 'spec/controllers/settings/profiles_controller_spec.rb' - - 'spec/controllers/statuses_cleanup_controller_spec.rb' - - 'spec/controllers/tags_controller_spec.rb' - - 'spec/controllers/well_known/host_meta_controller_spec.rb' - - 'spec/controllers/well_known/nodeinfo_controller_spec.rb' - - 'spec/controllers/well_known/webfinger_controller_spec.rb' - - 'spec/helpers/accounts_helper_spec.rb' - - 'spec/helpers/admin/account_moderation_notes_helper_spec.rb' - - 'spec/helpers/admin/action_logs_helper_spec.rb' - - 'spec/helpers/flashes_helper_spec.rb' - - 'spec/helpers/formatting_helper_spec.rb' - - 'spec/helpers/home_helper_spec.rb' - - 'spec/helpers/routing_helper_spec.rb' - - 'spec/mailers/admin_mailer_spec.rb' - - 'spec/mailers/notification_mailer_spec.rb' - - 'spec/mailers/user_mailer_spec.rb' - - 'spec/models/account/field_spec.rb' - - 'spec/models/account_alias_spec.rb' - - 'spec/models/account_conversation_spec.rb' - - 'spec/models/account_deletion_request_spec.rb' - - 'spec/models/account_domain_block_spec.rb' - - 'spec/models/account_migration_spec.rb' - - 'spec/models/account_moderation_note_spec.rb' - - 'spec/models/account_spec.rb' - - 'spec/models/account_statuses_cleanup_policy_spec.rb' - - 'spec/models/admin/account_action_spec.rb' - - 'spec/models/admin/action_log_spec.rb' - - 'spec/models/announcement_mute_spec.rb' - - 'spec/models/announcement_reaction_spec.rb' - - 'spec/models/announcement_spec.rb' - - 'spec/models/backup_spec.rb' - - 'spec/models/block_spec.rb' - - 'spec/models/canonical_email_block_spec.rb' - - 'spec/models/conversation_mute_spec.rb' - - 'spec/models/conversation_spec.rb' - - 'spec/models/custom_emoji_spec.rb' - - 'spec/models/custom_filter_keyword_spec.rb' - - 'spec/models/custom_filter_spec.rb' - - 'spec/models/device_spec.rb' - - 'spec/models/domain_block_spec.rb' - - 'spec/models/email_domain_block_spec.rb' - - 'spec/models/encrypted_message_spec.rb' - - 'spec/models/favourite_spec.rb' - - 'spec/models/featured_tag_spec.rb' - - 'spec/models/follow_recommendation_suppression_spec.rb' - - 'spec/models/follow_request_spec.rb' - - 'spec/models/follow_spec.rb' - - 'spec/models/home_feed_spec.rb' - - 'spec/models/identity_spec.rb' - - 'spec/models/import_spec.rb' - - 'spec/models/invite_spec.rb' - - 'spec/models/list_account_spec.rb' - - 'spec/models/list_spec.rb' - - 'spec/models/login_activity_spec.rb' - - 'spec/models/media_attachment_spec.rb' - - 'spec/models/mention_spec.rb' - - 'spec/models/mute_spec.rb' - - 'spec/models/notification_spec.rb' - - 'spec/models/poll_vote_spec.rb' - - 'spec/models/preview_card_spec.rb' - - 'spec/models/preview_card_trend_spec.rb' - - 'spec/models/public_feed_spec.rb' - - 'spec/models/relay_spec.rb' - - 'spec/models/scheduled_status_spec.rb' - - 'spec/models/session_activation_spec.rb' - - 'spec/models/setting_spec.rb' - - 'spec/models/site_upload_spec.rb' - - 'spec/models/status_pin_spec.rb' - - 'spec/models/status_spec.rb' - - 'spec/models/status_stat_spec.rb' - - 'spec/models/status_trend_spec.rb' - - 'spec/models/system_key_spec.rb' - - 'spec/models/tag_follow_spec.rb' - - 'spec/models/unavailable_domain_spec.rb' - - 'spec/models/user_invite_request_spec.rb' - - 'spec/models/user_role_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/models/web/push_subscription_spec.rb' - - 'spec/models/web/setting_spec.rb' - - 'spec/models/webauthn_credentials_spec.rb' - - 'spec/models/webhook_spec.rb' - RSpec/RepeatedExample: Exclude: - 'spec/policies/status_policy_spec.rb' @@ -1120,7 +716,6 @@ RSpec/VerifiedDoubles: - 'spec/controllers/api/web/embeds_controller_spec.rb' - 'spec/controllers/auth/sessions_controller_spec.rb' - 'spec/controllers/disputes/appeals_controller_spec.rb' - - 'spec/controllers/settings/imports_controller_spec.rb' - 'spec/helpers/statuses_helper_spec.rb' - 'spec/lib/suspicious_sign_in_detector_spec.rb' - 'spec/models/account/field_spec.rb' @@ -1148,19 +743,6 @@ RSpec/VerifiedDoubles: - 'spec/workers/feed_insert_worker_spec.rb' - 'spec/workers/regeneration_worker_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/ActiveRecordCallbacksOrder: - Exclude: - - 'app/models/account.rb' - - 'app/models/account_conversation.rb' - - 'app/models/announcement_reaction.rb' - - 'app/models/block.rb' - - 'app/models/media_attachment.rb' - - 'app/models/session_activation.rb' - - 'app/models/status.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Rails/ApplicationController: Exclude: @@ -1216,12 +798,6 @@ Rails/CreateTableWithTimestamps: - 'db/migrate/20220824233535_create_status_trends.rb' - 'db/migrate/20221006061337_create_preview_card_trends.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Severity. -Rails/DeprecatedActiveModelErrorsMethods: - Exclude: - - 'lib/mastodon/accounts_cli.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Severity. Rails/DuplicateAssociation: @@ -1235,74 +811,6 @@ Rails/Exit: Exclude: - 'config/boot.rb' -# Configuration parameters: EnforcedStyle. -# SupportedStyles: slashes, arguments -Rails/FilePath: - Exclude: - - 'app/lib/themes.rb' - - 'app/models/setting.rb' - - 'app/validators/reaction_validator.rb' - - 'config/environments/test.rb' - - 'config/initializers/locale.rb' - - 'db/migrate/20170716191202_add_hide_notifications_to_mute.rb' - - 'db/migrate/20171005171936_add_disabled_to_custom_emojis.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/20171130000000_add_embed_url_to_preview_cards.rb' - - 'db/migrate/20180615122121_add_autofollow_to_invites.rb' - - 'db/migrate/20180707154237_add_whole_word_to_custom_filter.rb' - - 'db/migrate/20180814171349_add_confidential_to_doorkeeper_application.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/20181127130500_identity_id_to_bigint.rb' - - 'db/migrate/20181127165847_add_show_replies_to_lists.rb' - - 'db/migrate/20190201012802_add_overwrite_to_imports.rb' - - 'db/migrate/20190306145741_add_lock_version_to_polls.rb' - - 'db/migrate/20190307234537_add_approved_to_users.rb' - - 'db/migrate/20191001213028_add_lock_version_to_account_stats.rb' - - 'db/migrate/20191212003415_increase_backup_size.rb' - - 'db/migrate/20200312144258_add_title_to_account_warning_presets.rb' - - 'db/migrate/20200620164023_add_fixed_lowercase_index_to_accounts.rb' - - 'db/migrate/20200917192924_add_notify_to_follows.rb' - - 'db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb' - - 'db/migrate/20210421121431_add_case_insensitive_btree_index_to_tags.rb' - - 'db/migrate/20211231080958_add_category_to_reports.rb' - - 'db/migrate/20220613110834_add_action_to_custom_filters.rb' - - 'db/post_migrate/20220307083603_optimize_null_index_conversations_uri.rb' - - 'db/post_migrate/20220310060545_optimize_null_index_statuses_in_reply_to_account_id.rb' - - 'db/post_migrate/20220310060556_optimize_null_index_statuses_in_reply_to_id.rb' - - 'db/post_migrate/20220310060614_optimize_null_index_media_attachments_scheduled_status_id.rb' - - 'db/post_migrate/20220310060626_optimize_null_index_media_attachments_shortcode.rb' - - 'db/post_migrate/20220310060641_optimize_null_index_users_reset_password_token.rb' - - 'db/post_migrate/20220310060653_optimize_null_index_users_created_by_application_id.rb' - - 'db/post_migrate/20220310060706_optimize_null_index_statuses_uri.rb' - - 'db/post_migrate/20220310060722_optimize_null_index_accounts_moved_to_account_id.rb' - - 'db/post_migrate/20220310060740_optimize_null_index_oauth_access_tokens_refresh_token.rb' - - 'db/post_migrate/20220310060750_optimize_null_index_accounts_url.rb' - - 'db/post_migrate/20220310060809_optimize_null_index_oauth_access_tokens_resource_owner_id.rb' - - 'db/post_migrate/20220310060833_optimize_null_index_announcement_reactions_custom_emoji_id.rb' - - 'db/post_migrate/20220310060854_optimize_null_index_appeals_approved_by_account_id.rb' - - 'db/post_migrate/20220310060913_optimize_null_index_account_migrations_target_account_id.rb' - - 'db/post_migrate/20220310060926_optimize_null_index_appeals_rejected_by_account_id.rb' - - 'db/post_migrate/20220310060939_optimize_null_index_list_accounts_follow_id.rb' - - 'db/post_migrate/20220310060959_optimize_null_index_web_push_subscriptions_access_token_id.rb' - - 'db/post_migrate/20220613110802_remove_whole_word_from_custom_filters.rb' - - 'db/post_migrate/20220613110903_remove_irreversible_from_custom_filters.rb' - - 'db/post_migrate/20220617202502_migrate_roles.rb' - - 'db/seeds.rb' - - 'db/seeds/03_roles.rb' - - 'lib/tasks/branding.rake' - - 'lib/tasks/emojis.rake' - - 'lib/tasks/repo.rake' - - 'spec/controllers/admin/custom_emojis_controller_spec.rb' - - 'spec/fabricators/custom_emoji_fabricator.rb' - - 'spec/fabricators/site_upload_fabricator.rb' - - 'spec/rails_helper.rb' - - 'spec/spec_helper.rb' - # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/HasAndBelongsToMany: @@ -1445,12 +953,29 @@ Rails/SkipsModelValidations: - 'spec/services/follow_service_spec.rb' - 'spec/services/update_account_service_spec.rb' -Rails/TransactionExitStatement: +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/ThreeStateBooleanColumn: Exclude: - - 'app/lib/activitypub/activity/announce.rb' - - 'app/lib/activitypub/activity/create.rb' - - 'app/lib/activitypub/activity/delete.rb' - - 'app/services/activitypub/process_account_service.rb' + - 'db/migrate/20160325130944_add_admin_to_users.rb' + - 'db/migrate/20161123093447_add_sensitive_to_statuses.rb' + - 'db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb' + - 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb' + - 'db/migrate/20170209184350_add_reply_to_statuses.rb' + - 'db/migrate/20170330163835_create_imports.rb' + - 'db/migrate/20170905165803_add_local_to_statuses.rb' + - 'db/migrate/20181203021853_add_discoverable_to_accounts.rb' + - 'db/migrate/20190509164208_add_by_moderator_to_tombstone.rb' + - 'db/migrate/20190805123746_add_capabilities_to_tags.rb' + - 'db/migrate/20191212163405_add_hide_collections_to_accounts.rb' + - 'db/migrate/20200309150742_add_forwarded_to_reports.rb' + - 'db/migrate/20210609202149_create_login_activities.rb' + - 'db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb' + - 'db/migrate/20211031031021_create_preview_card_providers.rb' + - 'db/migrate/20211115032527_add_trendable_to_preview_cards.rb' + - 'db/migrate/20220202200743_add_trendable_to_accounts.rb' + - 'db/migrate/20220202200926_add_trendable_to_statuses.rb' + - 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb' # Configuration parameters: Include. # Include: app/models/**/*.rb @@ -1519,12 +1044,6 @@ Style/CaseEquality: Exclude: - 'config/initializers/trusted_proxies.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: MinBranchesCount. -Style/CaseLikeIf: - Exclude: - - 'app/controllers/concerns/signature_verification.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. # AllowedMethods: ==, equal?, eql? @@ -1542,16 +1061,10 @@ Style/CombinableLoops: - 'app/models/form/custom_emoji_batch.rb' - 'app/models/form/ip_block_batch.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ConcatArrayLiterals: - Exclude: - - 'app/lib/feed_manager.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedVars. Style/FetchEnvVar: Exclude: - - 'app/helpers/application_helper.rb' - 'app/lib/redis_configuration.rb' - 'app/lib/translation_service.rb' - 'config/environments/development.rb' @@ -2001,7 +1514,6 @@ Style/GuardClause: - 'app/controllers/auth/passwords_controller.rb' - 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb' - 'app/lib/activitypub/activity/block.rb' - - 'app/lib/connection_pool/shared_connection_pool.rb' - 'app/lib/request.rb' - 'app/lib/request_pool.rb' - 'app/lib/webfinger.rb' @@ -2036,7 +1548,6 @@ Style/HashAsLastArrayItem: Exclude: - 'app/controllers/admin/statuses_controller.rb' - 'app/controllers/api/v1/statuses_controller.rb' - - 'app/models/account.rb' - 'app/models/concerns/account_counters.rb' - 'app/models/concerns/status_threading_concern.rb' - 'app/models/status.rb' @@ -2044,19 +1555,6 @@ Style/HashAsLastArrayItem: - 'app/services/notify_service.rb' - 'db/migrate/20181024224956_migrate_account_conversations.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent -Style/HashSyntax: - Exclude: - - 'app/helpers/application_helper.rb' - - 'app/models/media_attachment.rb' - - 'lib/terrapin/multi_pipe_extensions.rb' - - 'spec/controllers/admin/reports/actions_controller_spec.rb' - - 'spec/controllers/admin/statuses_controller_spec.rb' - - 'spec/controllers/concerns/signature_verification_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/HashTransformValues: Exclude: @@ -2074,22 +1572,8 @@ Style/IfUnlessModifier: # Configuration parameters: InverseMethods, InverseBlocks. Style/InverseMethods: Exclude: - - 'app/controllers/concerns/signature_verification.rb' - - 'app/helpers/jsonld_helper.rb' - - 'app/lib/activitypub/activity/create.rb' - - 'app/lib/activitypub/activity/move.rb' - - 'app/lib/feed_manager.rb' - - 'app/lib/link_details_extractor.rb' - - 'app/models/concerns/attachmentable.rb' - - 'app/models/concerns/remotable.rb' - 'app/models/custom_filter.rb' - - 'app/models/webhook.rb' - - 'app/services/activitypub/process_status_update_service.rb' - - 'app/services/fetch_link_card_service.rb' - - 'app/services/search_service.rb' - 'app/services/update_account_service.rb' - - 'app/workers/web/push_notification_worker.rb' - - 'lib/paperclip/color_extractor.rb' - 'spec/controllers/activitypub/replies_controller_spec.rb' # This cop supports safe autocorrection (--autocorrect). @@ -2110,12 +1594,10 @@ Style/MapToHash: # SupportedStyles: literals, strict Style/MutableConstant: Exclude: - - 'app/models/account.rb' - 'app/models/tag.rb' - 'app/services/delete_account_service.rb' - 'config/initializers/twitter_regex.rb' - 'lib/mastodon/migration_warning.rb' - - 'spec/controllers/api/base_controller_spec.rb' # This cop supports safe autocorrection (--autocorrect). Style/NilLambda: @@ -2199,7 +1681,6 @@ Style/RedundantRegexpEscape: Style/RegexpLiteral: Exclude: - 'app/lib/link_details_extractor.rb' - - 'app/lib/permalink_redirector.rb' - 'app/lib/plain_text_formatter.rb' - 'app/lib/tag_manager.rb' - 'app/lib/text_formatter.rb' @@ -2321,11 +1802,14 @@ Style/TrailingCommaInHashLiteral: - 'config/environments/test.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: WordRegex. +# Configuration parameters: EnforcedStyle, MinSize, WordRegex. # SupportedStyles: percent, brackets Style/WordArray: - EnforcedStyle: percent - MinSize: 6 + Exclude: + - 'app/helpers/languages_helper.rb' + - 'config/initializers/cors.rb' + - 'spec/controllers/settings/imports_controller_spec.rb' + - 'spec/models/form/import_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. diff --git a/Aptfile b/Aptfile index 8f5bb72a2..5e033f136 100644 --- a/Aptfile +++ b/Aptfile @@ -1,4 +1,5 @@ ffmpeg +libopenblas0-pthread libpq-dev libxdamage1 libxfixes3 diff --git a/Dockerfile b/Dockerfile index 978933421..91c26d2ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,10 @@ RUN apt-get update && \ FROM node:${NODE_VERSION} +# Use those args to specify your own version flags & suffixes +ARG MASTODON_VERSION_FLAGS="" +ARG MASTODON_VERSION_SUFFIX="" + ARG UID="991" ARG GID="991" @@ -84,7 +88,9 @@ COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon ENV RAILS_ENV="production" \ NODE_ENV="production" \ RAILS_SERVE_STATIC_FILES="true" \ - BIND="0.0.0.0" + BIND="0.0.0.0" \ + MASTODON_VERSION_FLAGS="${MASTODON_VERSION_FLAGS}" \ + MASTODON_VERSION_SUFFIX="${MASTODON_VERSION_SUFFIX}" # Set the run user USER mastodon diff --git a/Gemfile b/Gemfile index 3301b83cc..e55b21c9e 100644 --- a/Gemfile +++ b/Gemfile @@ -30,10 +30,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' gem 'devise', '~> 4.9' -# The below `v4.x` branch allows attr_encrypted 4.x, which is required for Rails 7. -# Once a new gem version is pushed, we can go back to released gem and off of github branch. -gem 'devise-two-factor', github: 'tinfoil/devise-two-factor', branch: 'v4.x' -gem 'attr_encrypted', '~> 4.0' +gem 'devise-two-factor', '~> 4.1' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.2' @@ -164,3 +161,4 @@ gem 'hcaptcha', '~> 7.1' gem 'cocoon', '~> 1.2' gem 'net-http', '~> 0.3.2' +gem 'rubyzip', '~> 2.3' diff --git a/Gemfile.lock b/Gemfile.lock index 7cf23180e..f22d5b372 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,18 +27,6 @@ GIT rails-settings-cached (0.6.6) rails (>= 4.2.0) -GIT - remote: https://github.com/tinfoil/devise-two-factor.git - revision: e685f91ce62d036259885fbe31fcb4fa930bcfcb - branch: v4.x - specs: - devise-two-factor (4.0.2) - activesupport (< 7.1) - attr_encrypted (>= 1.3, < 5, != 2) - devise (~> 4.0) - railties (< 7.1) - rotp (~> 6.0) - GEM remote: https://rubygems.org/ specs: @@ -218,6 +206,12 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise-two-factor (4.1.0) + activesupport (< 7.1) + attr_encrypted (>= 1.3, < 5, != 2) + devise (~> 4.0) + railties (< 7.1) + rotp (~> 6.0) devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) rpam2 (~> 4.0) @@ -354,15 +348,15 @@ GEM ipaddress (0.8.3) jmespath (1.6.2) json (2.6.3) - json-canonicalization (0.3.1) + json-canonicalization (0.3.2) json-jwt (1.15.3) activesupport (>= 4.2) aes_key_wrap bindata httpclient - json-ld (3.2.4) + json-ld (3.2.5) htmlentities (~> 4.3) - json-canonicalization (~> 0.3) + json-canonicalization (~> 0.3, >= 0.3.2) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) rack (>= 2.2, < 4) @@ -492,7 +486,7 @@ GEM parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.5.2) + pg (1.5.3) pghero (3.3.3) activerecord (>= 6) pkg-config (1.5.1) @@ -626,7 +620,7 @@ GEM rubocop-performance (1.17.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.18.0) + rubocop-rails (2.19.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) @@ -638,6 +632,7 @@ GEM nokogiri (>= 1.10.5) rexml ruby2_keywords (0.0.5) + rubyzip (2.3.2) rufus-scheduler (3.8.2) fugit (~> 1.1, >= 1.1.6) safety_net_attestation (0.4.0) @@ -777,7 +772,6 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotate (~> 3.2) - attr_encrypted (~> 4.0) aws-sdk-s3 (~> 1.120) better_errors (~> 2.9) binding_of_caller (~> 1.0) @@ -799,7 +793,7 @@ DEPENDENCIES concurrent-ruby connection_pool devise (~> 4.9) - devise-two-factor! + devise-two-factor (~> 4.1) devise_pam_authenticatable2 (~> 9.2) discard (~> 1.2) doorkeeper (~> 5.6) @@ -879,6 +873,7 @@ DEPENDENCIES rubocop-rails rubocop-rspec ruby-progressbar (~> 1.13) + rubyzip (~> 2.3) sanitize (~> 6.0) scenic (~> 1.7) sidekiq (~> 6.5) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 750f5c995..081550b76 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -33,7 +33,7 @@ module Admin 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 # rubocop:disable Rails/OutputSafety + 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 diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index 7485438db..5ea26d55b 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -15,7 +15,8 @@ class Api::V1::MediaController < Api::BaseController render json: @media_attachment, serializer: REST::MediaAttachmentSerializer rescue Paperclip::Errors::NotIdentifiedByImageMagickError render json: file_type_error, status: 422 - rescue Paperclip::Error + rescue Paperclip::Error => e + Rails.logger.error "#{e.class}: #{e.message}" render json: processing_error, status: 500 end diff --git a/app/controllers/api/v2/media_controller.rb b/app/controllers/api/v2/media_controller.rb index 288f847f1..72bc69442 100644 --- a/app/controllers/api/v2/media_controller.rb +++ b/app/controllers/api/v2/media_controller.rb @@ -6,7 +6,8 @@ class Api::V2::MediaController < Api::V1::MediaController render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200 rescue Paperclip::Errors::NotIdentifiedByImageMagickError render json: file_type_error, status: 422 - rescue Paperclip::Error + rescue Paperclip::Error => e + Rails.logger.error "#{e.class}: #{e.message}" render json: processing_error, status: 500 end end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index 97fe4a9ab..73f0f2b88 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -60,7 +60,7 @@ class AuthorizeInteractionsController < ApplicationController end def uri_param - params[:uri] || params.fetch(:acct, '').gsub(/\Aacct:/, '') + params[:uri] || params.fetch(:acct, '').delete_prefix('acct:') end def set_body_classes diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 931725943..1d27c92c8 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -180,14 +180,15 @@ module SignatureVerification def build_signed_string signed_headers.map do |signed_header| - if signed_header == Request::REQUEST_TARGET + case signed_header + when Request::REQUEST_TARGET "#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}" - elsif signed_header == '(created)' + when '(created)' raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank? "(created): #{signature_params['created']}" - elsif signed_header == '(expires)' + when '(expires)' raise SignatureVerificationError, 'Invalid pseudo-header (expires) for rsa-sha256' unless signature_algorithm == 'hs2019' raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank? @@ -244,7 +245,7 @@ module SignatureVerification end if key_id.start_with?('acct:') - stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) } + stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) account = ActivityPub::TagManager.instance.uri_to_actor(key_id) account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) } diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb index ca89fc7fe..ea024e30e 100644 --- a/app/controllers/intents_controller.rb +++ b/app/controllers/intents_controller.rb @@ -9,7 +9,7 @@ class IntentsController < ApplicationController if uri.scheme == 'web+mastodon' case uri.host when 'follow' - return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, '')) + return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].delete_prefix('acct:')) when 'share' return redirect_to share_path(text: uri.query_values['text']) end diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb index 1b5486c12..8d480d704 100644 --- a/app/controllers/media_proxy_controller.rb +++ b/app/controllers/media_proxy_controller.rb @@ -16,7 +16,7 @@ class MediaProxyController < ApplicationController rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error def show - with_lock("media_download:#{params[:id]}") do + with_redis_lock("media_download:#{params[:id]}") do @media_attachment = MediaAttachment.remote.attached.find(params[:id]) authorize @media_attachment.status, :show? redownload! if @media_attachment.needs_redownload? && !reject_media? diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb index deaa7940e..46a340aeb 100644 --- a/app/controllers/settings/exports_controller.rb +++ b/app/controllers/settings/exports_controller.rb @@ -15,7 +15,7 @@ class Settings::ExportsController < Settings::BaseController def create backup = nil - with_lock("backup:#{current_user.id}") do + with_redis_lock("backup:#{current_user.id}") do authorize :backup, :create? backup = current_user.backups.create! end diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index d4516526e..bdbf8796f 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -1,31 +1,97 @@ # frozen_string_literal: true -class Settings::ImportsController < Settings::BaseController - before_action :set_account +require 'csv' - def show - @import = Import.new +class Settings::ImportsController < Settings::BaseController + before_action :set_bulk_import, only: [:show, :confirm, :destroy] + before_action :set_recent_imports, only: [:index] + + TYPE_TO_FILENAME_MAP = { + following: 'following_accounts_failures.csv', + blocking: 'blocked_accounts_failures.csv', + muting: 'muted_accounts_failures.csv', + domain_blocking: 'blocked_domains_failures.csv', + bookmarks: 'bookmarks_failures.csv', + }.freeze + + TYPE_TO_HEADERS_MAP = { + following: ['Account address', 'Show boosts', 'Notify on new posts', 'Languages'], + blocking: false, + muting: ['Account address', 'Hide notifications'], + domain_blocking: false, + bookmarks: false, + }.freeze + + def index + @import = Form::Import.new(current_account: current_account) + end + + def show; end + + def failures + @bulk_import = current_account.bulk_imports.where(state: :finished).find(params[:id]) + + respond_to do |format| + format.csv do + filename = TYPE_TO_FILENAME_MAP[@bulk_import.type.to_sym] + headers = TYPE_TO_HEADERS_MAP[@bulk_import.type.to_sym] + + export_data = CSV.generate(headers: headers, write_headers: true) do |csv| + @bulk_import.rows.find_each do |row| + case @bulk_import.type.to_sym + when :following + csv << [row.data['acct'], row.data.fetch('show_reblogs', true), row.data.fetch('notify', false), row.data['languages']&.join(', ')] + when :blocking + csv << [row.data['acct']] + when :muting + csv << [row.data['acct'], row.data.fetch('hide_notifications', true)] + when :domain_blocking + csv << [row.data['domain']] + when :bookmarks + csv << [row.data['uri']] + end + end + end + + send_data export_data, filename: filename + end + end + end + + def confirm + @bulk_import.update!(state: :scheduled) + BulkImportWorker.perform_async(@bulk_import.id) + redirect_to settings_imports_path, notice: I18n.t('imports.success') end def create - @import = Import.new(import_params) - @import.account = @account + @import = Form::Import.new(import_params.merge(current_account: current_account)) if @import.save - ImportWorker.perform_async(@import.id) - redirect_to settings_import_path, notice: I18n.t('imports.success') + redirect_to settings_import_path(@import.bulk_import.id) else - render :show + # We need to set recent imports as we are displaying the index again + set_recent_imports + render :index end end + def destroy + @bulk_import.destroy! + redirect_to settings_imports_path + end + private - def set_account - @account = current_user.account + def import_params + params.require(:form_import).permit(:data, :type, :mode) end - def import_params - params.require(:import).permit(:data, :type, :mode) + def set_bulk_import + @bulk_import = current_account.bulk_imports.where(state: :unconfirmed).find(params[:id]) + end + + def set_recent_imports + @recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(10) end end diff --git a/app/controllers/settings/preferences/appearance_controller.rb b/app/controllers/settings/preferences/appearance_controller.rb index 80ea57bd2..4d7d12bb7 100644 --- a/app/controllers/settings/preferences/appearance_controller.rb +++ b/app/controllers/settings/preferences/appearance_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Settings::Preferences::AppearanceController < Settings::PreferencesController +class Settings::Preferences::AppearanceController < Settings::Preferences::BaseController private def after_update_redirect_path diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences/base_controller.rb similarity index 81% rename from app/controllers/settings/preferences_controller.rb rename to app/controllers/settings/preferences/base_controller.rb index 281deb64d..faf778a7e 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences/base_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Settings::PreferencesController < Settings::BaseController +class Settings::Preferences::BaseController < Settings::BaseController def show; end def update @@ -15,7 +15,7 @@ class Settings::PreferencesController < Settings::BaseController private def after_update_redirect_path - settings_preferences_path + raise 'Override in controller' end def user_params diff --git a/app/controllers/settings/preferences/notifications_controller.rb b/app/controllers/settings/preferences/notifications_controller.rb index a16ae6a67..66d6c9a2f 100644 --- a/app/controllers/settings/preferences/notifications_controller.rb +++ b/app/controllers/settings/preferences/notifications_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Settings::Preferences::NotificationsController < Settings::PreferencesController +class Settings::Preferences::NotificationsController < Settings::Preferences::BaseController private def after_update_redirect_path diff --git a/app/controllers/settings/preferences/other_controller.rb b/app/controllers/settings/preferences/other_controller.rb index 07eb89a76..a19fbf5c4 100644 --- a/app/controllers/settings/preferences/other_controller.rb +++ b/app/controllers/settings/preferences/other_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Settings::Preferences::OtherController < Settings::PreferencesController +class Settings::Preferences::OtherController < Settings::Preferences::BaseController private def after_update_redirect_path diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index a06253f45..0d897e8e2 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -18,7 +18,14 @@ module WellKnown private def set_account - @account = Account.find_local!(username_from_resource) + username = username_from_resource + @account = begin + if username == Rails.configuration.x.local_domain + Account.representative + else + Account.find_local!(username) + end + end end def username_from_resource diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 879752cf7..3192c7ab5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,10 +32,6 @@ module ApplicationHelper paths.any? { |path| current_page?(path) } ? 'active' : '' end - def active_link_to(label, path, **options) - link_to label, path, options.merge(class: active_nav_class(path)) - end - def show_landing_strip? !user_signed_in? && !single_user_mode? end @@ -147,7 +143,7 @@ module ApplicationHelper if prefers_autoplay? image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:") else - image_tag(custom_emoji.image.url(:static), class: 'emojione custom-emoji', alt: ":#{custom_emoji.shortcode}", 'data-original' => full_asset_url(custom_emoji.image.url), 'data-static' => full_asset_url(custom_emoji.image.url(:static))) + image_tag(custom_emoji.image.url(:static), :class => 'emojione custom-emoji', :alt => ":#{custom_emoji.shortcode}", 'data-original' => full_asset_url(custom_emoji.image.url), 'data-static' => full_asset_url(custom_emoji.image.url(:static))) end end @@ -174,11 +170,11 @@ module ApplicationHelper end def storage_host - "https://#{ENV['S3_ALIAS_HOST'].presence || ENV['S3_CLOUDFRONT_HOST']}" + URI::HTTPS.build(host: storage_host_name).to_s end def storage_host? - ENV['S3_ALIAS_HOST'].present? || ENV['S3_CLOUDFRONT_HOST'].present? + storage_host_name.present? end def quote_wrap(text, line_width: 80, break_sequence: "\n") @@ -236,4 +232,10 @@ module ApplicationHelper def prerender_custom_emojis(html, custom_emojis, other_options = {}) EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end + + private + + def storage_host_name + ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) + end end diff --git a/app/javascript/mastodon/actions/app.js b/app/javascript/mastodon/actions/app.js deleted file mode 100644 index c817c8708..000000000 --- a/app/javascript/mastodon/actions/app.js +++ /dev/null @@ -1,17 +0,0 @@ -export const APP_FOCUS = 'APP_FOCUS'; -export const APP_UNFOCUS = 'APP_UNFOCUS'; - -export const focusApp = () => ({ - type: APP_FOCUS, -}); - -export const unfocusApp = () => ({ - type: APP_UNFOCUS, -}); - -export const APP_LAYOUT_CHANGE = 'APP_LAYOUT_CHANGE'; - -export const changeLayout = layout => ({ - type: APP_LAYOUT_CHANGE, - layout, -}); diff --git a/app/javascript/mastodon/actions/app.ts b/app/javascript/mastodon/actions/app.ts new file mode 100644 index 000000000..0acfbfae7 --- /dev/null +++ b/app/javascript/mastodon/actions/app.ts @@ -0,0 +1,10 @@ +import { createAction } from '@reduxjs/toolkit'; + +export const focusApp = createAction('APP_FOCUS'); +export const unfocusApp = createAction('APP_UNFOCUS'); + +type ChangeLayoutPayload = { + layout: 'mobile' | 'single-column' | 'multi-column'; +}; +export const changeLayout = + createAction('APP_LAYOUT_CHANGE'); diff --git a/app/javascript/mastodon/blurhash.js b/app/javascript/mastodon/blurhash.ts similarity index 87% rename from app/javascript/mastodon/blurhash.js rename to app/javascript/mastodon/blurhash.ts index 5adcc3e77..cb1c3b2c8 100644 --- a/app/javascript/mastodon/blurhash.js +++ b/app/javascript/mastodon/blurhash.ts @@ -84,7 +84,7 @@ const DIGIT_CHARACTERS = [ '~', ]; -export const decode83 = (str) => { +export const decode83 = (str: string) => { let value = 0; let c, digit; @@ -97,13 +97,13 @@ export const decode83 = (str) => { return value; }; -export const intToRGB = int => ({ +export const intToRGB = (int: number) => ({ r: Math.max(0, (int >> 16)), g: Math.max(0, (int >> 8) & 255), b: Math.max(0, (int & 255)), }); -export const getAverageFromBlurhash = blurhash => { +export const getAverageFromBlurhash = (blurhash: string) => { if (!blurhash) { return null; } diff --git a/app/javascript/mastodon/compare_id.js b/app/javascript/mastodon/compare_id.ts similarity index 72% rename from app/javascript/mastodon/compare_id.js rename to app/javascript/mastodon/compare_id.ts index d2bd74f44..ae4ac6f89 100644 --- a/app/javascript/mastodon/compare_id.js +++ b/app/javascript/mastodon/compare_id.ts @@ -1,4 +1,4 @@ -export default function compareId (id1, id2) { +export default function compareId (id1: string, id2: string) { if (id1 === id2) { return 0; } diff --git a/app/javascript/mastodon/components/blurhash.jsx b/app/javascript/mastodon/components/blurhash.jsx deleted file mode 100644 index f5c58e04e..000000000 --- a/app/javascript/mastodon/components/blurhash.jsx +++ /dev/null @@ -1,65 +0,0 @@ -// @ts-check - -import { decode } from 'blurhash'; -import React, { useRef, useEffect } from 'react'; -import PropTypes from 'prop-types'; - -/** - * @typedef BlurhashPropsBase - * @property {string?} hash Hash to render - * @property {number} width - * Width of the blurred region in pixels. Defaults to 32 - * @property {number} [height] - * Height of the blurred region in pixels. Defaults to width - * @property {boolean} [dummy] - * Whether dummy mode is enabled. If enabled, nothing is rendered - * and canvas left untouched - */ - -/** @typedef {JSX.IntrinsicElements['canvas'] & BlurhashPropsBase} BlurhashProps */ - -/** - * Component that is used to render blurred of blurhash string - * @param {BlurhashProps} param1 Props of the component - * @returns {JSX.Element} Canvas which will render blurred region element to embed - */ -function Blurhash({ - hash, - width = 32, - height = width, - dummy = false, - ...canvasProps -}) { - const canvasRef = /** @type {import('react').MutableRefObject} */ (useRef()); - - useEffect(() => { - const { current: canvas } = canvasRef; - canvas.width = canvas.width; // resets canvas - - if (dummy || !hash) return; - - try { - const pixels = decode(hash, width, height); - const ctx = canvas.getContext('2d'); - const imageData = new ImageData(pixels, width, height); - - // @ts-expect-error - ctx.putImageData(imageData, 0, 0); - } catch (err) { - console.error('Blurhash decoding failure', { err, hash }); - } - }, [dummy, hash, width, height]); - - return ( - - ); -} - -Blurhash.propTypes = { - hash: PropTypes.string.isRequired, - width: PropTypes.number, - height: PropTypes.number, - dummy: PropTypes.bool, -}; - -export default React.memo(Blurhash); diff --git a/app/javascript/mastodon/components/blurhash.tsx b/app/javascript/mastodon/components/blurhash.tsx new file mode 100644 index 000000000..6fec6e1ef --- /dev/null +++ b/app/javascript/mastodon/components/blurhash.tsx @@ -0,0 +1,45 @@ +import { decode } from 'blurhash'; +import React, { useRef, useEffect } from 'react'; + +type Props = { + hash: string; + width?: number; + height?: number; + dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched + children?: never; + [key: string]: any; +} +function Blurhash({ + hash, + width = 32, + height = width, + dummy = false, + ...canvasProps +}: Props) { + const canvasRef = useRef(null); + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const canvas = canvasRef.current!; + // eslint-disable-next-line no-self-assign + canvas.width = canvas.width; // resets canvas + + if (dummy || !hash) return; + + try { + const pixels = decode(hash, width, height); + const ctx = canvas.getContext('2d'); + const imageData = new ImageData(pixels, width, height); + + ctx?.putImageData(imageData, 0, 0); + } catch (err) { + console.error('Blurhash decoding failure', { err, hash }); + } + }, [dummy, hash, width, height]); + + return ( + + ); +} + +export default React.memo(Blurhash); diff --git a/app/javascript/mastodon/components/column_back_button.jsx b/app/javascript/mastodon/components/column_back_button.jsx index 12926bb25..faa800b2a 100644 --- a/app/javascript/mastodon/components/column_back_button.jsx +++ b/app/javascript/mastodon/components/column_back_button.jsx @@ -21,7 +21,9 @@ export default class ColumnBackButton extends React.PureComponent { if (onClick) { onClick(); - } else if (window.history && window.history.state) { + // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 + // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location + } else if (router.route.location.key) { router.history.goBack(); } else { router.history.push('/'); diff --git a/app/javascript/mastodon/components/icon_button.jsx b/app/javascript/mastodon/components/icon_button.tsx similarity index 68% rename from app/javascript/mastodon/components/icon_button.jsx rename to app/javascript/mastodon/components/icon_button.tsx index 989cae440..ec11ab701 100644 --- a/app/javascript/mastodon/components/icon_button.jsx +++ b/app/javascript/mastodon/components/icon_button.tsx @@ -1,34 +1,36 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import Icon from 'mastodon/components/icon'; -import AnimatedNumber from 'mastodon/components/animated_number'; +import { Icon } from './icon'; +import { AnimatedNumber } from './animated_number'; -export default class IconButton extends React.PureComponent { - - static propTypes = { - className: PropTypes.string, - title: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - onClick: PropTypes.func, - onMouseDown: PropTypes.func, - onKeyDown: PropTypes.func, - onKeyPress: PropTypes.func, - size: PropTypes.number, - active: PropTypes.bool, - expanded: PropTypes.bool, - style: PropTypes.object, - activeStyle: PropTypes.object, - disabled: PropTypes.bool, - inverted: PropTypes.bool, - animate: PropTypes.bool, - overlay: PropTypes.bool, - tabIndex: PropTypes.number, - counter: PropTypes.number, - obfuscateCount: PropTypes.bool, - href: PropTypes.string, - ariaHidden: PropTypes.bool, - }; +type Props = { + className?: string; + title: string; + icon: string; + onClick?: React.MouseEventHandler; + onMouseDown?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; + onKeyPress?: React.KeyboardEventHandler; + size: number; + active: boolean; + expanded?: boolean; + style?: React.CSSProperties; + activeStyle?: React.CSSProperties; + disabled: boolean; + inverted?: boolean; + animate: boolean; + overlay: boolean; + tabIndex: number; + counter?: number; + obfuscateCount?: boolean; + href?: string; + ariaHidden: boolean; +} +type States = { + activate: boolean, + deactivate: boolean, +} +export default class IconButton extends React.PureComponent { static defaultProps = { size: 18, @@ -45,7 +47,7 @@ export default class IconButton extends React.PureComponent { deactivate: false, }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps: Props) { if (!nextProps.animate) return; if (this.props.active && !nextProps.active) { @@ -55,27 +57,27 @@ export default class IconButton extends React.PureComponent { } } - handleClick = (e) => { + handleClick: React.MouseEventHandler = (e) => { e.preventDefault(); - if (!this.props.disabled) { + if (!this.props.disabled && this.props.onClick != null) { this.props.onClick(e); } }; - handleKeyPress = (e) => { + handleKeyPress: React.KeyboardEventHandler = (e) => { if (this.props.onKeyPress && !this.props.disabled) { this.props.onKeyPress(e); } }; - handleMouseDown = (e) => { + handleMouseDown: React.MouseEventHandler = (e) => { if (!this.props.disabled && this.props.onMouseDown) { this.props.onMouseDown(e); } }; - handleKeyDown = (e) => { + handleKeyDown: React.KeyboardEventHandler = (e) => { if (!this.props.disabled && this.props.onKeyDown) { this.props.onKeyDown(e); } @@ -132,7 +134,7 @@ export default class IconButton extends React.PureComponent { ); - if (href && !this.prop) { + if (href != null) { contents = ( {contents} diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 5be0070a3..54470f940 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -81,12 +81,10 @@ class Item extends React.PureComponent { render () { const { attachment, lang, index, size, standalone, displayWidth, visible } = this.props; + let badges = [], thumbnail; + let width = 50; let height = 100; - let top = 'auto'; - let left = 'auto'; - let bottom = 'auto'; - let right = 'auto'; if (size === 1) { width = 100; @@ -96,45 +94,13 @@ class Item extends React.PureComponent { height = 50; } - if (size === 2) { - if (index === 0) { - right = '2px'; - } else { - left = '2px'; - } - } else if (size === 3) { - if (index === 0) { - right = '2px'; - } else if (index > 0) { - left = '2px'; - } - - if (index === 1) { - bottom = '2px'; - } else if (index > 1) { - top = '2px'; - } - } else if (size === 4) { - if (index === 0 || index === 2) { - right = '2px'; - } - - if (index === 1 || index === 3) { - left = '2px'; - } - - if (index < 2) { - bottom = '2px'; - } else { - top = '2px'; - } + if (attachment.get('description')?.length > 0) { + badges.push(ALT); } - let thumbnail = ''; - if (attachment.get('type') === 'unknown') { return ( -
+
GIF); + thumbnail = (
); } return ( -
+
+ {visible && thumbnail} + + {badges && ( +
+ {badges} +
+ )}
); } @@ -313,7 +286,7 @@ class MediaGallery extends React.PureComponent { } render () { - const { media, lang, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props; + const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay } = this.props; const { visible } = this.state; const width = this.state.width || defaultWidth; @@ -322,13 +295,9 @@ class MediaGallery extends React.PureComponent { const style = {}; if (this.isFullSizeEligible() && (standalone || !cropImages)) { - if (width) { - style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); - } - } else if (width) { - style.height = width / (16/9); + style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`; } else { - style.height = height; + style.aspectRatio = '16 / 9'; } const size = media.take(4).size; diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx index 6322b1c66..a51c97401 100644 --- a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx +++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx @@ -3,62 +3,22 @@ import PropTypes from 'prop-types'; import Icon from 'mastodon/components/icon'; import { removePictureInPicture } from 'mastodon/actions/picture_in_picture'; import { connect } from 'react-redux'; -import { debounce } from 'lodash'; import { FormattedMessage } from 'react-intl'; class PictureInPicturePlaceholder extends React.PureComponent { static propTypes = { - width: PropTypes.number, dispatch: PropTypes.func.isRequired, }; - state = { - width: this.props.width, - height: this.props.width && (this.props.width / (16/9)), - }; - handleClick = () => { const { dispatch } = this.props; dispatch(removePictureInPicture()); }; - setRef = c => { - this.node = c; - - if (this.node) { - this._setDimensions(); - } - }; - - _setDimensions () { - const width = this.node.offsetWidth; - const height = width / (16/9); - - this.setState({ width, height }); - } - - componentDidMount () { - window.addEventListener('resize', this.handleResize, { passive: true }); - } - - componentWillUnmount () { - window.removeEventListener('resize', this.handleResize); - } - - handleResize = debounce(() => { - if (this.node) { - this._setDimensions(); - } - }, 250, { - trailing: true, - }); - render () { - const { height } = this.state; - return ( -
+
diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index cd8423b2f..b5242e769 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -411,7 +411,7 @@ class Status extends ImmutablePureComponent { } if (pictureInPicture.get('inUse')) { - media = ; + media = ; } else if (status.get('media_attachments').size > 0) { if (this.props.muted) { media = ( @@ -460,12 +460,9 @@ class Status extends ImmutablePureComponent { src={attachment.get('url')} alt={attachment.get('description')} lang={status.get('language')} - width={this.props.cachedMediaWidth} - height={110} inline sensitive={status.get('sensitive')} onOpenVideo={this.handleOpenVideo} - cacheWidth={this.props.cacheMediaWidth} deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} visible={this.state.showMedia} onToggleVisibility={this.handleToggleMediaVisibility} @@ -498,8 +495,6 @@ class Status extends ImmutablePureComponent { onOpenMedia={this.handleOpenMedia} card={status.get('card')} compact - cacheWidth={this.props.cacheMediaWidth} - defaultWidth={this.props.cachedMediaWidth} sensitive={status.get('sensitive')} /> ); diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx index 7bc7bbaa4..a4c5f3cb4 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/app/javascript/mastodon/containers/compose_container.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; -import configureStore from '../store/configureStore'; +import { store } from '../store/configureStore'; import { hydrateStore } from '../actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; @@ -12,8 +12,6 @@ import { fetchCustomEmojis } from '../actions/custom_emojis'; const { localeData, messages } = getLocale(); addLocaleData(localeData); -const store = configureStore(); - if (initialState) { store.dispatch(hydrateStore(initialState)); } diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx index 002b71e93..256ea8e2d 100644 --- a/app/javascript/mastodon/containers/mastodon.jsx +++ b/app/javascript/mastodon/containers/mastodon.jsx @@ -5,7 +5,7 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { Provider as ReduxProvider } from 'react-redux'; import { BrowserRouter, Route } from 'react-router-dom'; import { ScrollContext } from 'react-router-scroll-4'; -import configureStore from 'mastodon/store/configureStore'; +import { store } from 'mastodon/store/configureStore'; import UI from 'mastodon/features/ui'; import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis'; import { hydrateStore } from 'mastodon/actions/store'; @@ -19,7 +19,6 @@ addLocaleData(localeData); const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`; -export const store = configureStore(); const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx index 53f24c6a3..e8fe2c4d9 100644 --- a/app/javascript/mastodon/features/audio/index.jsx +++ b/app/javascript/mastodon/features/audio/index.jsx @@ -384,7 +384,7 @@ class Audio extends React.PureComponent { } _getRadius () { - return parseInt(((this.state.height || this.props.height) - (PADDING * this._getScaleCoefficient()) * 2) / 2); + return parseInt((this.state.height || this.props.height) / 2 - PADDING * this._getScaleCoefficient()); } _getScaleCoefficient () { @@ -396,7 +396,7 @@ class Audio extends React.PureComponent { } _getCY() { - return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); + return Math.floor((this.state.height || this.props.height) / 2); } _getAccentColor () { @@ -470,7 +470,7 @@ class Audio extends React.PureComponent { } return ( -
+
}
diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index b67f671c5..88b38c65a 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -8,7 +8,6 @@ import classnames from 'classnames'; import Icon from 'mastodon/components/icon'; import { useBlurhash } from 'mastodon/initial_state'; import Blurhash from 'mastodon/components/blurhash'; -import { debounce } from 'lodash'; const IDNA_PREFIX = 'xn--'; @@ -54,8 +53,6 @@ export default class Card extends React.PureComponent { card: ImmutablePropTypes.map, onOpenMedia: PropTypes.func.isRequired, compact: PropTypes.bool, - defaultWidth: PropTypes.number, - cacheWidth: PropTypes.func, sensitive: PropTypes.bool, }; @@ -64,7 +61,6 @@ export default class Card extends React.PureComponent { }; state = { - width: this.props.defaultWidth || 280, previewLoaded: false, embedded: false, revealed: !this.props.sensitive, @@ -87,24 +83,6 @@ export default class Card extends React.PureComponent { window.removeEventListener('resize', this.handleResize); } - _setDimensions () { - const width = this.node.offsetWidth; - - if (this.props.cacheWidth) { - this.props.cacheWidth(width); - } - - this.setState({ width }); - } - - handleResize = debounce(() => { - if (this.node) { - this._setDimensions(); - } - }, 250, { - trailing: true, - }); - handlePhotoClick = () => { const { card, onOpenMedia } = this.props; @@ -138,10 +116,6 @@ export default class Card extends React.PureComponent { setRef = c => { this.node = c; - - if (this.node) { - this._setDimensions(); - } }; handleImageLoad = () => { @@ -157,36 +131,31 @@ export default class Card extends React.PureComponent { renderVideo () { const { card } = this.props; const content = { __html: addAutoPlay(card.get('html')) }; - const { width } = this.state; - const ratio = card.get('width') / card.get('height'); - const height = width / ratio; return (
); } render () { const { card, compact } = this.props; - const { width, embedded, revealed } = this.state; + const { embedded, revealed } = this.state; if (card === null) { return null; } const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); - const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded; + const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded; const interactive = card.get('type') !== 'link'; const className = classnames('status-card', { horizontal, compact, interactive }); const title = interactive ? {card.get('title')} : {card.get('title')}; const language = card.get('language') || ''; - const ratio = card.get('width') / card.get('height'); - const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio); const description = (
@@ -196,6 +165,14 @@ export default class Card extends React.PureComponent {
); + const thumbnailStyle = { + visibility: revealed? null : 'hidden', + }; + + if (horizontal) { + thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`; + } + let embed = ''; let canvas = ( ); - let thumbnail = ; + let thumbnail = ; let spoilerButton = (