Merge remote-tracking branch 'catstodon/feature/emoji_reactions'
This commit is contained in:
commit
2cf4b3a95b
686 changed files with 7624 additions and 5030 deletions
|
@ -26,7 +26,6 @@ services:
|
|||
ports:
|
||||
- '127.0.0.1:3000:3000'
|
||||
- '127.0.0.1:4000:4000'
|
||||
- '127.0.0.1:80:3000'
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
|
|
12
.eslintrc.js
12
.eslintrc.js
|
@ -7,6 +7,7 @@ module.exports = {
|
|||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:promise/recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
],
|
||||
|
||||
env: {
|
||||
|
@ -238,6 +239,14 @@ module.exports = {
|
|||
'formatjs/no-useless-message': 'error',
|
||||
'formatjs/prefer-formatted-message': 'error',
|
||||
'formatjs/prefer-pound-in-plural': 'error',
|
||||
|
||||
'jsdoc/check-types': 'off',
|
||||
'jsdoc/no-undefined-types': 'off',
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/require-param-description': 'off',
|
||||
'jsdoc/require-property-description': 'off',
|
||||
'jsdoc/require-returns-description': 'off',
|
||||
'jsdoc/require-returns': 'off',
|
||||
},
|
||||
|
||||
overrides: [
|
||||
|
@ -270,10 +279,13 @@ module.exports = {
|
|||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:promise/recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
],
|
||||
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
7
.github/workflows/build-image.yml
vendored
7
.github/workflows/build-image.yml
vendored
|
@ -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 }}
|
||||
|
|
6
.github/workflows/build-nightly.yml
vendored
6
.github/workflows/build-nightly.yml
vendored
|
@ -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 }}
|
||||
|
|
2
.github/workflows/lint-js.yml
vendored
2
.github/workflows/lint-js.yml
vendored
|
@ -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
|
||||
|
|
15
.github/workflows/test-ruby.yml
vendored
15
.github/workflows/test-ruby.yml
vendored
|
@ -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:
|
||||
|
|
2
.profile
2
.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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,120 +330,6 @@ RSpec/AnyInstance:
|
|||
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
||||
- 'spec/workers/web/push_notification_worker_spec.rb'
|
||||
|
||||
RSpec/BeforeAfterAll:
|
||||
Exclude:
|
||||
- 'spec/requests/localization_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
|
||||
|
@ -705,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'
|
||||
|
@ -713,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'
|
||||
|
@ -749,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'
|
||||
|
@ -763,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'
|
||||
|
@ -835,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
|
||||
|
@ -861,7 +628,7 @@ RSpec/PendingWithoutReason:
|
|||
Exclude:
|
||||
- 'spec/controllers/statuses_controller_spec.rb'
|
||||
- 'spec/models/account_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
- 'spec/models/status_reaction_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers.
|
||||
|
@ -872,181 +639,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'
|
||||
|
@ -1125,7 +717,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'
|
||||
|
@ -1153,45 +744,6 @@ RSpec/VerifiedDoubles:
|
|||
- 'spec/workers/feed_insert_worker_spec.rb'
|
||||
- 'spec/workers/regeneration_worker_spec.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: ExpectedOrder, Include.
|
||||
# ExpectedOrder: index, show, new, edit, create, update, destroy
|
||||
# Include: app/controllers/**/*.rb
|
||||
Rails/ActionOrder:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/announcements_controller.rb'
|
||||
- 'app/controllers/admin/roles_controller.rb'
|
||||
- 'app/controllers/admin/rules_controller.rb'
|
||||
- 'app/controllers/admin/warning_presets_controller.rb'
|
||||
- 'app/controllers/admin/webhooks_controller.rb'
|
||||
- 'app/controllers/api/v1/admin/domain_allows_controller.rb'
|
||||
- 'app/controllers/api/v1/admin/domain_blocks_controller.rb'
|
||||
- 'app/controllers/api/v1/admin/email_domain_blocks_controller.rb'
|
||||
- 'app/controllers/api/v1/admin/ip_blocks_controller.rb'
|
||||
- 'app/controllers/api/v1/filters_controller.rb'
|
||||
- 'app/controllers/api/v1/media_controller.rb'
|
||||
- 'app/controllers/api/v1/push/subscriptions_controller.rb'
|
||||
- 'app/controllers/api/v2/filters/keywords_controller.rb'
|
||||
- 'app/controllers/api/v2/filters/statuses_controller.rb'
|
||||
- 'app/controllers/api/v2/filters_controller.rb'
|
||||
- 'app/controllers/auth/registrations_controller.rb'
|
||||
- 'app/controllers/filters_controller.rb'
|
||||
- 'app/controllers/settings/applications_controller.rb'
|
||||
- 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.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:
|
||||
|
@ -1234,22 +786,6 @@ Rails/BulkChangeTable:
|
|||
- 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb'
|
||||
- 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/CompactBlank:
|
||||
Exclude:
|
||||
- 'app/helpers/application_helper.rb'
|
||||
- 'app/helpers/statuses_helper.rb'
|
||||
- 'app/models/concerns/attachmentable.rb'
|
||||
- 'app/models/poll.rb'
|
||||
- 'app/services/import_service.rb'
|
||||
- 'config/initializers/paperclip.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Rails/ContentTag:
|
||||
Exclude:
|
||||
- 'app/helpers/application_helper.rb'
|
||||
- 'app/helpers/branding_helper.rb'
|
||||
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/migrate/*.rb
|
||||
Rails/CreateTableWithTimestamps:
|
||||
|
@ -1263,12 +799,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:
|
||||
|
@ -1282,74 +812,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:
|
||||
|
@ -1373,39 +835,11 @@ Rails/HasManyOrHasOneDependent:
|
|||
- 'app/models/user.rb'
|
||||
- 'app/models/web/push_subscription.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Include.
|
||||
# Include: spec/**/*, test/**/*
|
||||
Rails/HttpPositionalArguments:
|
||||
Exclude:
|
||||
- 'spec/config/initializers/rack_attack_spec.rb'
|
||||
|
||||
# Configuration parameters: Include.
|
||||
# Include: spec/**/*.rb, test/**/*.rb
|
||||
Rails/I18nLocaleAssignment:
|
||||
Exclude:
|
||||
- 'spec/controllers/auth/registrations_controller_spec.rb'
|
||||
- 'spec/helpers/application_helper_spec.rb'
|
||||
- 'spec/requests/localization_spec.rb'
|
||||
|
||||
Rails/I18nLocaleTexts:
|
||||
Exclude:
|
||||
- 'lib/tasks/mastodon.rake'
|
||||
- 'spec/helpers/flashes_helper_spec.rb'
|
||||
|
||||
# Configuration parameters: IgnoreScopes, Include.
|
||||
# Include: app/models/**/*.rb
|
||||
Rails/InverseOf:
|
||||
Exclude:
|
||||
- 'app/models/appeal.rb'
|
||||
- 'app/models/concerns/account_interactions.rb'
|
||||
- 'app/models/custom_emoji.rb'
|
||||
- 'app/models/domain_block.rb'
|
||||
- 'app/models/follow_recommendation.rb'
|
||||
- 'app/models/instance.rb'
|
||||
- 'app/models/notification.rb'
|
||||
- 'app/models/status.rb'
|
||||
|
||||
# Configuration parameters: Include.
|
||||
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
|
||||
Rails/LexicallyScopedActionFilter:
|
||||
|
@ -1433,23 +867,10 @@ Rails/NegateInclude:
|
|||
- 'app/workers/web/push_notification_worker.rb'
|
||||
- 'lib/paperclip/color_extractor.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Include.
|
||||
# Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb
|
||||
Rails/Output:
|
||||
Exclude:
|
||||
- 'lib/mastodon/ip_blocks_cli.rb'
|
||||
|
||||
Rails/OutputSafety:
|
||||
Exclude:
|
||||
- 'config/initializers/simple_form.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank.
|
||||
Rails/Present:
|
||||
Exclude:
|
||||
- 'config/initializers/content_security_policy.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Include.
|
||||
# Include: **/Rakefile, **/*.rake
|
||||
|
@ -1533,21 +954,30 @@ Rails/SkipsModelValidations:
|
|||
- 'spec/services/follow_service_spec.rb'
|
||||
- 'spec/services/update_account_service_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/SquishedSQLHeredocs:
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/**/*.rb
|
||||
Rails/ThreeStateBooleanColumn:
|
||||
Exclude:
|
||||
- 'db/migrate/20170920024819_status_ids_to_timestamp_ids.rb'
|
||||
- 'db/migrate/20180608213548_reject_following_blocked_users.rb'
|
||||
- 'db/post_migrate/20190519130537_remove_boosts_widening_audience.rb'
|
||||
- 'lib/mastodon/snowflake.rb'
|
||||
- 'lib/tasks/tests.rake'
|
||||
|
||||
Rails/TransactionExitStatement:
|
||||
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/20171210213213_add_local_only_flag_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
|
||||
|
@ -1616,12 +1046,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?
|
||||
|
@ -1639,16 +1063,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'
|
||||
|
@ -2098,7 +1516,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'
|
||||
|
@ -2133,7 +1550,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'
|
||||
|
@ -2141,19 +1557,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:
|
||||
|
@ -2171,22 +1574,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).
|
||||
|
@ -2207,12 +1596,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:
|
||||
|
@ -2296,7 +1683,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'
|
||||
|
@ -2418,11 +1804,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.
|
||||
|
|
1
Aptfile
1
Aptfile
|
@ -1,4 +1,5 @@
|
|||
ffmpeg
|
||||
libopenblas0-pthread
|
||||
libpq-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
|
|
|
@ -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
|
||||
|
|
6
Gemfile
6
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'
|
||||
|
|
33
Gemfile.lock
33
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)
|
||||
|
|
|
@ -14,6 +14,10 @@ class Admin::AnnouncementsController < Admin::BaseController
|
|||
@announcement = Announcement.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :announcement, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :announcement, :create?
|
||||
|
||||
|
@ -28,10 +32,6 @@ class Admin::AnnouncementsController < Admin::BaseController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :announcement, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :announcement, :update?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,10 @@ module Admin
|
|||
@role = UserRole.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @role, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :user_role, :create?
|
||||
|
||||
|
@ -30,10 +34,6 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @role, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @role, :update?
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ module Admin
|
|||
@rule = Rule.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @rule, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :rule, :create?
|
||||
|
||||
|
@ -24,10 +28,6 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @rule, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @rule, :update?
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ module Admin
|
|||
@warning_preset = AccountWarningPreset.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @warning_preset, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :account_warning_preset, :create?
|
||||
|
||||
|
@ -24,10 +28,6 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @warning_preset, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @warning_preset, :update?
|
||||
|
||||
|
|
|
@ -10,12 +10,20 @@ module Admin
|
|||
@webhooks = Webhook.page(params[:page])
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @webhook, :show?
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :webhook, :create?
|
||||
|
||||
@webhook = Webhook.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @webhook, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :webhook, :create?
|
||||
|
||||
|
@ -28,14 +36,6 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @webhook, :show?
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @webhook, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @webhook, :update?
|
||||
|
||||
|
|
|
@ -16,6 +16,16 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
|
|||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :domain_allow, :index?
|
||||
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_allow, :show?
|
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
|
@ -29,16 +39,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
|
|||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
|
||||
end
|
||||
|
||||
def index
|
||||
authorize :domain_allow, :index?
|
||||
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_allow, :show?
|
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_allow, :destroy?
|
||||
UnallowDomainService.new.call(@domain_allow)
|
||||
|
|
|
@ -16,6 +16,16 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :domain_block, :index?
|
||||
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_block, :show?
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_block, :create?
|
||||
|
||||
|
@ -28,16 +38,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def index
|
||||
authorize :domain_block, :index?
|
||||
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @domain_block, :show?
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @domain_block, :update?
|
||||
@domain_block.update!(domain_block_params)
|
||||
|
|
|
@ -18,15 +18,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
limit
|
||||
).freeze
|
||||
|
||||
def create
|
||||
authorize :email_domain_block, :create?
|
||||
|
||||
@email_domain_block = EmailDomainBlock.create!(resource_params)
|
||||
log_action :create, @email_domain_block
|
||||
|
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
end
|
||||
|
||||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
|
@ -37,6 +28,15 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :email_domain_block, :create?
|
||||
|
||||
@email_domain_block = EmailDomainBlock.create!(resource_params)
|
||||
log_action :create, @email_domain_block
|
||||
|
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @email_domain_block, :destroy?
|
||||
@email_domain_block.destroy!
|
||||
|
|
|
@ -18,13 +18,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
limit
|
||||
).freeze
|
||||
|
||||
def create
|
||||
authorize :ip_block, :create?
|
||||
@ip_block = IpBlock.create!(resource_params)
|
||||
log_action :create, @ip_block
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
end
|
||||
|
||||
def index
|
||||
authorize :ip_block, :index?
|
||||
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
|
||||
|
@ -35,6 +28,13 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :ip_block, :create?
|
||||
@ip_block = IpBlock.create!(resource_params)
|
||||
log_action :create, @ip_block
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @ip_block, :update?
|
||||
@ip_block.update(resource_params)
|
||||
|
|
|
@ -11,6 +11,10 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
render json: @filters, each_serializer: REST::V1::FilterSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
render json: @filter, serializer: REST::V1::FilterSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
ApplicationRecord.transaction do
|
||||
filter_category = current_account.custom_filters.create!(filter_params)
|
||||
|
@ -20,10 +24,6 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
render json: @filter, serializer: REST::V1::FilterSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
render json: @filter, serializer: REST::V1::FilterSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
ApplicationRecord.transaction do
|
||||
@filter.update!(keyword_params)
|
||||
|
|
|
@ -6,19 +6,20 @@ class Api::V1::MediaController < Api::BaseController
|
|||
before_action :set_media_attachment, except: [:create]
|
||||
before_action :check_processing, except: [:create]
|
||||
|
||||
def show
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
end
|
||||
|
||||
def create
|
||||
@media_attachment = current_account.media_attachments.create!(media_attachment_params)
|
||||
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
|
||||
|
||||
def show
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
end
|
||||
|
||||
def update
|
||||
@media_attachment.update!(updateable_media_attachment_params)
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
|
|
|
@ -6,6 +6,10 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
|
|||
before_action :set_push_subscription
|
||||
before_action :check_push_subscription, only: [:show, :update]
|
||||
|
||||
def show
|
||||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@push_subscription&.destroy!
|
||||
|
||||
|
@ -21,10 +25,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
|
|||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
@push_subscription.update!(data: data_params)
|
||||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
|
|
|
@ -12,13 +12,13 @@ class Api::V2::Filters::KeywordsController < Api::BaseController
|
|||
render json: @keywords, each_serializer: REST::FilterKeywordSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params)
|
||||
|
||||
def show
|
||||
render json: @keyword, serializer: REST::FilterKeywordSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
def create
|
||||
@keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params)
|
||||
|
||||
render json: @keyword, serializer: REST::FilterKeywordSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ class Api::V2::Filters::StatusesController < Api::BaseController
|
|||
render json: @status_filters, each_serializer: REST::FilterStatusSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params)
|
||||
|
||||
def show
|
||||
render json: @status_filter, serializer: REST::FilterStatusSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
def create
|
||||
@status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params)
|
||||
|
||||
render json: @status_filter, serializer: REST::FilterStatusSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ class Api::V2::FiltersController < Api::BaseController
|
|||
render json: @filters, each_serializer: REST::FilterSerializer, rules_requested: true
|
||||
end
|
||||
|
||||
def create
|
||||
@filter = current_account.custom_filters.create!(resource_params)
|
||||
|
||||
def show
|
||||
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true
|
||||
end
|
||||
|
||||
def show
|
||||
def create
|
||||
@filter = current_account.custom_filters.create!(resource_params)
|
||||
|
||||
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,16 +25,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
super(&:build_invite_request)
|
||||
end
|
||||
|
||||
def destroy
|
||||
not_found
|
||||
end
|
||||
|
||||
def update
|
||||
super do |resource|
|
||||
resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password?
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
not_found
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def update_resource(resource, params)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -18,6 +18,8 @@ class FiltersController < ApplicationController
|
|||
@filter.keywords.build
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@filter = current_account.custom_filters.build(resource_params)
|
||||
|
||||
|
@ -28,8 +30,6 @@ class FiltersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def update
|
||||
if @filter.update(resource_params)
|
||||
redirect_to filters_path
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -8,6 +8,8 @@ class Settings::ApplicationsController < Settings::BaseController
|
|||
@applications = current_user.applications.order(id: :desc).page(params[:page])
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def new
|
||||
@application = Doorkeeper::Application.new(
|
||||
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
|
||||
|
@ -15,8 +17,6 @@ class Settings::ApplicationsController < Settings::BaseController
|
|||
)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@application = current_user.applications.build(application_params)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,9 +8,8 @@ module Settings
|
|||
before_action :require_otp_enabled
|
||||
before_action :require_webauthn_enabled, only: [:index, :destroy]
|
||||
|
||||
def new; end
|
||||
|
||||
def index; end
|
||||
def new; end
|
||||
|
||||
def options
|
||||
current_user.update(webauthn_id: WebAuthn.generate_user_id) unless current_user.webauthn_id
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -118,7 +114,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def check_icon
|
||||
content_tag(:svg, tag(:path, 'fill-rule': 'evenodd', 'clip-rule': 'evenodd', d: 'M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'), xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 20 20', fill: 'currentColor')
|
||||
content_tag(:svg, tag.path('fill-rule': 'evenodd', 'clip-rule': 'evenodd', d: 'M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'), xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 20 20', fill: 'currentColor')
|
||||
end
|
||||
|
||||
def visibility_icon(status)
|
||||
|
@ -147,12 +143,12 @@ 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
|
||||
|
||||
def opengraph(property, content)
|
||||
tag(:meta, content: content, property: property)
|
||||
tag.meta(content: content, property: property)
|
||||
end
|
||||
|
||||
def body_classes
|
||||
|
@ -162,7 +158,7 @@ module ApplicationHelper
|
|||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
||||
output << 'rtl' if locale_direction == 'rtl'
|
||||
output.reject(&:blank?).join(' ')
|
||||
output.compact_blank.join(' ')
|
||||
end
|
||||
|
||||
def cdn_host
|
||||
|
@ -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
|
||||
|
|
|
@ -11,11 +11,11 @@ module BrandingHelper
|
|||
end
|
||||
|
||||
def _logo_as_symbol_wordmark
|
||||
content_tag(:svg, tag(:use, href: '#logo-symbol-wordmark'), viewBox: '0 0 261 66', class: 'logo logo--wordmark')
|
||||
content_tag(:svg, tag.use(href: '#logo-symbol-wordmark'), viewBox: '0 0 261 66', class: 'logo logo--wordmark')
|
||||
end
|
||||
|
||||
def _logo_as_symbol_icon
|
||||
content_tag(:svg, tag(:use, href: '#logo-symbol-icon'), viewBox: '0 0 79 79', class: 'logo logo--icon')
|
||||
content_tag(:svg, tag.use(href: '#logo-symbol-icon'), viewBox: '0 0 79 79', class: 'logo logo--icon')
|
||||
end
|
||||
|
||||
def render_logo
|
||||
|
|
|
@ -51,14 +51,14 @@ module StatusesHelper
|
|||
end
|
||||
|
||||
def status_description(status)
|
||||
components = [[media_summary(status), status_text_summary(status)].reject(&:blank?).join(' · ')]
|
||||
components = [[media_summary(status), status_text_summary(status)].compact_blank.join(' · ')]
|
||||
|
||||
if status.spoiler_text.blank?
|
||||
components << status.text
|
||||
components << poll_summary(status)
|
||||
end
|
||||
|
||||
components.reject(&:blank?).join("\n\n")
|
||||
components.compact_blank.join("\n\n")
|
||||
end
|
||||
|
||||
def stream_link_target
|
||||
|
|
|
@ -81,7 +81,10 @@ export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST';
|
|||
export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS';
|
||||
export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL';
|
||||
|
||||
export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_READY = 'PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_READY';
|
||||
export const PINNED_ACCOUNTS_SUGGESTIONS_FETCH_REQUEST = 'PINNED_ACCOUNTS_SUGGESTIONS_FETCH_REQUEST';
|
||||
export const PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS';
|
||||
export const PINNED_ACCOUNTS_SUGGESTIONS_FETCH_FAIL = 'PINNED_ACCOUNTS_SUGGESTIONS_FETCH_FAIL';
|
||||
|
||||
export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR = 'PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR';
|
||||
export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE = 'PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE';
|
||||
|
||||
|
@ -841,6 +844,8 @@ export function fetchPinnedAccountsFail(error) {
|
|||
|
||||
export function fetchPinnedAccountsSuggestions(q) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchPinnedAccountsSuggestionsRequest());
|
||||
|
||||
const params = {
|
||||
q,
|
||||
resolve: false,
|
||||
|
@ -850,19 +855,32 @@ export function fetchPinnedAccountsSuggestions(q) {
|
|||
|
||||
api(getState).get('/api/v1/accounts/search', { params }).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchPinnedAccountsSuggestionsReady(q, response.data));
|
||||
});
|
||||
dispatch(fetchPinnedAccountsSuggestionsSuccess(q, response.data));
|
||||
}).catch(err => dispatch(fetchPinnedAccountsSuggestionsFail(err)));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPinnedAccountsSuggestionsReady(query, accounts) {
|
||||
export function fetchPinnedAccountsSuggestionsRequest() {
|
||||
return {
|
||||
type: PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_READY,
|
||||
type: PINNED_ACCOUNTS_SUGGESTIONS_FETCH_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPinnedAccountsSuggestionsSuccess(query, accounts) {
|
||||
return {
|
||||
type: PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS,
|
||||
query,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPinnedAccountsSuggestionsFail(error) {
|
||||
return {
|
||||
type: PINNED_ACCOUNTS_SUGGESTIONS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearPinnedAccountsSuggestions() {
|
||||
return {
|
||||
type: PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR,
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export const APP_LAYOUT_CHANGE = 'APP_LAYOUT_CHANGE';
|
||||
|
||||
export const changeLayout = layout => ({
|
||||
type: APP_LAYOUT_CHANGE,
|
||||
layout,
|
||||
});
|
7
app/javascript/flavours/glitch/actions/app.ts
Normal file
7
app/javascript/flavours/glitch/actions/app.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
type ChangeLayoutPayload = {
|
||||
layout: 'mobile' | 'single-column' | 'multi-column';
|
||||
};
|
||||
export const changeLayout =
|
||||
createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE');
|
|
@ -450,16 +450,12 @@ export function changeUploadCompose(id, params) {
|
|||
// Editing already-attached media is deferred to editing the post itself.
|
||||
// For simplicity's sake, fake an API reply.
|
||||
if (media && !media.get('unattached')) {
|
||||
let { description, focus } = params;
|
||||
const data = media.toJS();
|
||||
|
||||
if (description) {
|
||||
data.description = description;
|
||||
}
|
||||
const { focus, ...other } = params;
|
||||
const data = { ...media.toJS(), ...other };
|
||||
|
||||
if (focus) {
|
||||
focus = focus.split(',');
|
||||
data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } };
|
||||
const [x, y] = focus.split(',');
|
||||
data.meta = { focus: { x: parseFloat(x), y: parseFloat(y) } };
|
||||
}
|
||||
|
||||
dispatch(changeUploadComposeSuccess(data, true));
|
||||
|
|
|
@ -20,7 +20,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
|
|||
* @param {string} accountId
|
||||
* @param {string} playerType
|
||||
* @param {MediaProps} props
|
||||
* @return {object}
|
||||
* @returns {object}
|
||||
*/
|
||||
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
|
||||
// @ts-expect-error
|
||||
|
|
|
@ -27,7 +27,7 @@ const { messages } = getLocale();
|
|||
|
||||
/**
|
||||
* @param {number} max
|
||||
* @return {number}
|
||||
* @returns {number}
|
||||
*/
|
||||
const randomUpTo = max =>
|
||||
Math.floor(Math.random() * Math.floor(max));
|
||||
|
@ -40,7 +40,7 @@ const randomUpTo = max =>
|
|||
* @param {function(Function, Function): void} [options.fallback]
|
||||
* @param {function(): void} [options.fillGaps]
|
||||
* @param {function(object): boolean} [options.accept]
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
|
||||
connectStream(channelName, params, (dispatch, getState) => {
|
||||
|
@ -132,7 +132,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
|||
};
|
||||
|
||||
/**
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectUserStream = () =>
|
||||
// @ts-expect-error
|
||||
|
@ -141,7 +141,7 @@ export const connectUserStream = () =>
|
|||
/**
|
||||
* @param {Object} options
|
||||
* @param {boolean} [options.onlyMedia]
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectCommunityStream = ({ onlyMedia } = {}) =>
|
||||
connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) });
|
||||
|
@ -151,7 +151,7 @@ export const connectCommunityStream = ({ onlyMedia } = {}) =>
|
|||
* @param {boolean} [options.onlyMedia]
|
||||
* @param {boolean} [options.onlyRemote]
|
||||
* @param {boolean} [options.allowLocalOnly]
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectPublicStream = ({ onlyMedia, onlyRemote, allowLocalOnly } = {}) =>
|
||||
connectTimelineStream(`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote, allowLocalOnly }) });
|
||||
|
@ -161,20 +161,20 @@ export const connectPublicStream = ({ onlyMedia, onlyRemote, allowLocalOnly } =
|
|||
* @param {string} tagName
|
||||
* @param {boolean} onlyLocal
|
||||
* @param {function(object): boolean} accept
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectHashtagStream = (columnId, tagName, onlyLocal, accept) =>
|
||||
connectTimelineStream(`hashtag:${columnId}${onlyLocal ? ':local' : ''}`, `hashtag${onlyLocal ? ':local' : ''}`, { tag: tagName }, { accept });
|
||||
|
||||
/**
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectDirectStream = () =>
|
||||
connectTimelineStream('direct', 'direct');
|
||||
|
||||
/**
|
||||
* @param {string} listId
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectListStream = listId =>
|
||||
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export default function compareId (id1, id2) {
|
||||
export default function compareId (id1: string, id2: string) {
|
||||
if (id1 === id2) {
|
||||
return 0;
|
||||
}
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||
import classNames from 'classnames';
|
||||
import { autoPlayGif } from 'flavours/glitch/initial_state';
|
||||
import { useHovering } from 'hooks/useHovering';
|
||||
import type { Account } from 'types/resources';
|
||||
import type { Account } from 'flavours/glitch/types/resources';
|
||||
|
||||
type Props = {
|
||||
account: Account | undefined;
|
||||
|
|
|
@ -1,66 +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 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<HTMLCanvasElement>} */ (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 (
|
||||
<canvas {...canvasProps} ref={canvasRef} width={width} height={height} />
|
||||
);
|
||||
}
|
||||
|
||||
Blurhash.propTypes = {
|
||||
hash: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
dummy: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default React.memo(Blurhash);
|
45
app/javascript/flavours/glitch/components/blurhash.tsx
Normal file
45
app/javascript/flavours/glitch/components/blurhash.tsx
Normal file
|
@ -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<HTMLCanvasElement>(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 (
|
||||
<canvas {...canvasProps} ref={canvasRef} width={width} height={height} />
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Blurhash);
|
|
@ -4,7 +4,6 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
/**
|
||||
* Returns custom renderer for one of the common counter types
|
||||
*
|
||||
* @param {"statuses" | "following" | "followers"} counterType
|
||||
* Type of the counter
|
||||
* @param {boolean} isBold Whether display number must be displayed in bold
|
||||
|
|
|
@ -35,7 +35,6 @@ class SilentErrorBoundary extends React.Component {
|
|||
|
||||
/**
|
||||
* Used to render counter of how much people are talking about hashtag
|
||||
*
|
||||
* @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
|
||||
*/
|
||||
export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class Icon extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
fixedWidth: PropTypes.bool,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { id, className, fixedWidth, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<i role='img' className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
|
||||
);
|
||||
}
|
||||
|
||||
}
|
14
app/javascript/flavours/glitch/components/icon.tsx
Normal file
14
app/javascript/flavours/glitch/components/icon.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
className?: string;
|
||||
fixedWidth?: boolean;
|
||||
children?: never;
|
||||
[key: string]: any;
|
||||
}
|
||||
export const Icon: React.FC<Props> = ({ id, className, fixedWidth, ...other }) =>
|
||||
<i className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />;
|
||||
|
||||
export default Icon;
|
|
@ -1,35 +1,37 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
import AnimatedNumber from 'flavours/glitch/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,
|
||||
label: PropTypes.string,
|
||||
counter: PropTypes.number,
|
||||
obfuscateCount: PropTypes.bool,
|
||||
href: PropTypes.string,
|
||||
ariaHidden: PropTypes.bool,
|
||||
};
|
||||
type Props = {
|
||||
className?: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
size: number;
|
||||
active: boolean;
|
||||
expanded?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
activeStyle?: React.CSSProperties;
|
||||
disabled: boolean;
|
||||
inverted?: boolean;
|
||||
animate: boolean;
|
||||
overlay: boolean;
|
||||
tabIndex: number;
|
||||
label: string;
|
||||
counter?: number;
|
||||
obfuscateCount?: boolean;
|
||||
href?: string;
|
||||
ariaHidden: boolean;
|
||||
}
|
||||
type States = {
|
||||
activate: boolean,
|
||||
deactivate: boolean,
|
||||
}
|
||||
export default class IconButton extends React.PureComponent<Props, States> {
|
||||
|
||||
static defaultProps = {
|
||||
size: 18,
|
||||
|
@ -46,7 +48,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) {
|
||||
|
@ -56,27 +58,27 @@ export default class IconButton extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.props.disabled) {
|
||||
if (!this.props.disabled && this.props.onClick != null) {
|
||||
this.props.onClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (this.props.onKeyPress && !this.props.disabled) {
|
||||
this.props.onKeyPress(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
handleMouseDown: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (!this.props.disabled && this.props.onMouseDown) {
|
||||
this.props.onMouseDown(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (!this.props.disabled && this.props.onKeyDown) {
|
||||
this.props.onKeyDown(e);
|
||||
}
|
||||
|
@ -89,7 +91,7 @@ export default class IconButton extends React.PureComponent {
|
|||
containerSize = `${this.props.size * 1.28571429}px`;
|
||||
}
|
||||
|
||||
let style = {
|
||||
const style = {
|
||||
fontSize: `${this.props.size}px`,
|
||||
height: containerSize,
|
||||
lineHeight: `${this.props.size}px`,
|
||||
|
@ -144,7 +146,7 @@ export default class IconButton extends React.PureComponent {
|
|||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (href && !this.prop) {
|
||||
if (href != null) {
|
||||
contents = (
|
||||
<a href={href} target='_blank' rel='noopener noreferrer'>
|
||||
{contents}
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
|
||||
const formatNumber = num => num > 40 ? '40+' : num;
|
||||
|
||||
const IconWithBadge = ({ id, count, issueBadge, className }) => (
|
||||
<i className='icon-with-badge'>
|
||||
<Icon id={id} fixedWidth className={className} />
|
||||
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
|
||||
{issueBadge && <i className='icon-with-badge__issue-badge' />}
|
||||
</i>
|
||||
);
|
||||
|
||||
IconWithBadge.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
issueBadge: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default IconWithBadge;
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const formatNumber = (num: number): number | string => num > 40 ? '40+' : num;
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
count: number;
|
||||
issueBadge: boolean;
|
||||
className: string;
|
||||
}
|
||||
const IconWithBadge: React.FC<Props> = ({ id, count, issueBadge, className }) => (
|
||||
<i className='icon-with-badge'>
|
||||
<Icon id={id} fixedWidth className={className} />
|
||||
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
|
||||
{issueBadge && <i className='icon-with-badge__issue-badge' />}
|
||||
</i>
|
||||
);
|
||||
|
||||
export default IconWithBadge;
|
|
@ -101,12 +101,10 @@ class Item extends React.PureComponent {
|
|||
render () {
|
||||
const { attachment, lang, index, size, standalone, letterbox, 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;
|
||||
|
@ -116,45 +114,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(<span key='alt' className='media-gallery__gifv__label'>ALT</span>);
|
||||
}
|
||||
|
||||
let thumbnail = '';
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
|
||||
<Blurhash
|
||||
hash={attachment.get('blurhash')}
|
||||
|
@ -205,6 +171,8 @@ class Item extends React.PureComponent {
|
|||
} else if (attachment.get('type') === 'gifv') {
|
||||
const autoPlay = this.getAutoPlay();
|
||||
|
||||
badges.push(<span key='gif' className='media-gallery__gifv__label'>GIF</span>);
|
||||
|
||||
thumbnail = (
|
||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||
<video
|
||||
|
@ -222,14 +190,12 @@ class Item extends React.PureComponent {
|
|||
loop
|
||||
muted
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, letterbox })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||
<div className={classNames('media-gallery__item', { standalone, letterbox, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
<Blurhash
|
||||
hash={attachment.get('blurhash')}
|
||||
dummy={!useBlurhash}
|
||||
|
@ -237,7 +203,14 @@ class Item extends React.PureComponent {
|
|||
'media-gallery__preview--hidden': visible && this.state.loaded,
|
||||
})}
|
||||
/>
|
||||
|
||||
{visible && thumbnail}
|
||||
|
||||
{badges && (
|
||||
<div className='media-gallery__item__badges'>
|
||||
{badges}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -358,12 +331,10 @@ class MediaGallery extends React.PureComponent {
|
|||
|
||||
const computedClass = classNames('media-gallery', { 'full-width': fullwidth });
|
||||
|
||||
if (this.isStandaloneEligible() && width) {
|
||||
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
|
||||
} else if (width) {
|
||||
style.height = width / (16/9);
|
||||
if (this.isStandaloneEligible()) { // TODO: cropImages setting
|
||||
style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
|
||||
} else {
|
||||
return (<div className={computedClass} ref={this.handleRef} />);
|
||||
style.aspectRatio = '16 / 9';
|
||||
}
|
||||
|
||||
if (this.isStandaloneEligible()) {
|
||||
|
|
|
@ -3,62 +3,22 @@ import PropTypes from 'prop-types';
|
|||
import Icon from 'flavours/glitch/components/icon';
|
||||
import { removePictureInPicture } from 'flavours/glitch/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 (
|
||||
<div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<Icon id='window-restore' />
|
||||
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, defineMessages, InjectedIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
today: { id: 'relative_time.today', defaultMessage: 'today' },
|
||||
|
@ -28,12 +27,12 @@ const dateFormatOptions = {
|
|||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
};
|
||||
} as const;
|
||||
|
||||
const shortDateFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
};
|
||||
} as const;
|
||||
|
||||
const SECOND = 1000;
|
||||
const MINUTE = 1000 * 60;
|
||||
|
@ -42,7 +41,7 @@ const DAY = 1000 * 60 * 60 * 24;
|
|||
|
||||
const MAX_DELAY = 2147483647;
|
||||
|
||||
const selectUnits = delta => {
|
||||
const selectUnits = (delta: number) => {
|
||||
const absDelta = Math.abs(delta);
|
||||
|
||||
if (absDelta < MINUTE) {
|
||||
|
@ -56,7 +55,7 @@ const selectUnits = delta => {
|
|||
return 'day';
|
||||
};
|
||||
|
||||
const getUnitDelay = units => {
|
||||
const getUnitDelay = (units: string) => {
|
||||
switch (units) {
|
||||
case 'second':
|
||||
return SECOND;
|
||||
|
@ -71,7 +70,7 @@ const getUnitDelay = units => {
|
|||
}
|
||||
};
|
||||
|
||||
export const timeAgoString = (intl, date, now, year, timeGiven, short) => {
|
||||
export const timeAgoString = (intl: InjectedIntl, date: Date, now: number, year: number, timeGiven: boolean, short?: boolean) => {
|
||||
const delta = now - date.getTime();
|
||||
|
||||
let relativeTime;
|
||||
|
@ -99,7 +98,7 @@ export const timeAgoString = (intl, date, now, year, timeGiven, short) => {
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
const timeRemainingString = (intl, date, now, timeGiven = true) => {
|
||||
const timeRemainingString = (intl: InjectedIntl, date: Date, now: number, timeGiven = true) => {
|
||||
const delta = date.getTime() - now;
|
||||
|
||||
let relativeTime;
|
||||
|
@ -121,15 +120,17 @@ const timeRemainingString = (intl, date, now, timeGiven = true) => {
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
class RelativeTimestamp extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
timestamp: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
futureDate: PropTypes.bool,
|
||||
short: PropTypes.bool,
|
||||
};
|
||||
type Props = {
|
||||
intl: InjectedIntl;
|
||||
timestamp: string;
|
||||
year: number;
|
||||
futureDate?: boolean;
|
||||
short?: boolean;
|
||||
}
|
||||
type States = {
|
||||
now: number;
|
||||
}
|
||||
class RelativeTimestamp extends React.Component<Props, States> {
|
||||
|
||||
state = {
|
||||
now: this.props.intl.now(),
|
||||
|
@ -140,7 +141,9 @@ class RelativeTimestamp extends React.Component {
|
|||
short: true,
|
||||
};
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
_timer: number | undefined;
|
||||
|
||||
shouldComponentUpdate (nextProps: Props, nextState: States) {
|
||||
// As of right now the locale doesn't change without a new page load,
|
||||
// but we might as well check in case that ever changes.
|
||||
return this.props.timestamp !== nextProps.timestamp ||
|
||||
|
@ -148,7 +151,7 @@ class RelativeTimestamp extends React.Component {
|
|||
this.state.now !== nextState.now;
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
UNSAFE_componentWillReceiveProps (nextProps: Props) {
|
||||
if (this.props.timestamp !== nextProps.timestamp) {
|
||||
this.setState({ now: this.props.intl.now() });
|
||||
}
|
||||
|
@ -158,16 +161,16 @@ class RelativeTimestamp extends React.Component {
|
|||
this._scheduleNextUpdate(this.props, this.state);
|
||||
}
|
||||
|
||||
componentWillUpdate (nextProps, nextState) {
|
||||
UNSAFE_componentWillUpdate (nextProps: Props, nextState: States) {
|
||||
this._scheduleNextUpdate(nextProps, nextState);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearTimeout(this._timer);
|
||||
window.clearTimeout(this._timer);
|
||||
}
|
||||
|
||||
_scheduleNextUpdate (props, state) {
|
||||
clearTimeout(this._timer);
|
||||
_scheduleNextUpdate (props: Props, state: States) {
|
||||
window.clearTimeout(this._timer);
|
||||
|
||||
const { timestamp } = props;
|
||||
const delta = (new Date(timestamp)).getTime() - state.now;
|
||||
|
@ -176,7 +179,7 @@ class RelativeTimestamp extends React.Component {
|
|||
const updateInterval = 1000 * 10;
|
||||
const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
|
||||
|
||||
this._timer = setTimeout(() => {
|
||||
this._timer = window.setTimeout(() => {
|
||||
this.setState({ now: this.props.intl.now() });
|
||||
}, delay);
|
||||
}
|
|
@ -24,7 +24,6 @@ import { FormattedMessage, FormattedNumber } from 'react-intl';
|
|||
|
||||
/**
|
||||
* Component that renders short big number to a shorter version
|
||||
*
|
||||
* @param {ShortNumberProps} param0 Props for the component
|
||||
* @returns {JSX.Element} Rendered number
|
||||
*/
|
||||
|
@ -58,7 +57,6 @@ ShortNumber.propTypes = {
|
|||
|
||||
/**
|
||||
* Renders short number into corresponding localizable react fragment
|
||||
*
|
||||
* @param {ShortNumberCounterProps} param0 Props for the component
|
||||
* @returns {JSX.Element} FormattedMessage ready to be embedded in code
|
||||
*/
|
||||
|
|
|
@ -628,7 +628,7 @@ class Status extends ImmutablePureComponent {
|
|||
attachments = status.get('media_attachments');
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
media.push(<PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />);
|
||||
media.push(<PictureInPicturePlaceholder />);
|
||||
mediaIcons.push('video-camera');
|
||||
} else if (attachments.size > 0) {
|
||||
if (muted || attachments.some(item => item.get('type') === 'unknown')) {
|
||||
|
@ -684,8 +684,6 @@ class Status extends ImmutablePureComponent {
|
|||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||
preventPlayback={isCollapsed || !isExpanded}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
width={this.props.cachedMediaWidth}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
|
@ -725,8 +723,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')}
|
||||
/>,
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import configureStore from 'flavours/glitch/store/configureStore';
|
||||
import { store } from 'flavours/glitch/store/configureStore';
|
||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from 'mastodon/locales';
|
||||
|
@ -12,8 +12,6 @@ import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
|
|||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
const store = configureStore();
|
||||
|
||||
if (initialState) {
|
||||
store.dispatch(hydrateStore(initialState));
|
||||
}
|
||||
|
|
|
@ -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 'flavours/glitch/store/configureStore';
|
||||
import { store } from 'flavours/glitch/store/configureStore';
|
||||
import UI from 'flavours/glitch/features/ui';
|
||||
import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
|
||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||
|
@ -20,7 +20,6 @@ addLocaleData(localeData);
|
|||
|
||||
const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
|
||||
|
||||
export const store = configureStore();
|
||||
const hydrateAction = hydrateStore(initialState);
|
||||
store.dispatch(hydrateAction);
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import Icon from 'flavours/glitch/components/icon';
|
|||
import IconButton from 'flavours/glitch/components/icon_button';
|
||||
import Avatar from 'flavours/glitch/components/avatar';
|
||||
import Button from 'flavours/glitch/components/button';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||
import AccountNoteContainer from '../containers/account_note_container';
|
||||
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
|
||||
|
|
|
@ -390,7 +390,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 () {
|
||||
|
@ -402,7 +402,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 () {
|
||||
|
@ -476,7 +476,7 @@ class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), aspectRatio: '16 / 9' }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
|
||||
|
||||
<Blurhash
|
||||
hash={blurhash}
|
||||
|
@ -521,9 +521,16 @@ class Audio extends React.PureComponent {
|
|||
{(revealed || editable) && <img
|
||||
src={this.props.poster}
|
||||
alt=''
|
||||
width={(this._getRadius() - TICK_SIZE) * 2}
|
||||
height={(this._getRadius() - TICK_SIZE) * 2}
|
||||
style={{ position: 'absolute', left: this._getCX(), top: this._getCY(), transform: 'translate(-50%, -50%)', borderRadius: '50%', pointerEvents: 'none' }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
height: `calc(${(100 - 2 * 100 * PADDING / 982)}% - ${TICK_SIZE * 2}px)`,
|
||||
aspectRatio: '1',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
borderRadius: '50%',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>}
|
||||
|
||||
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
|
||||
|
|
|
@ -24,6 +24,10 @@ const messages = defineMessages({
|
|||
id: 'compose_form.publish_loud',
|
||||
},
|
||||
saveChanges: { id: 'compose_form.save_changes', defaultMessage: 'Save changes' },
|
||||
public: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
||||
class Publisher extends ImmutablePureComponent {
|
||||
|
@ -68,6 +72,13 @@ class Publisher extends ImmutablePureComponent {
|
|||
publishText = privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||
}
|
||||
|
||||
const privacyNames = {
|
||||
public: messages.public,
|
||||
unlisted: messages.unlisted,
|
||||
private: messages.private,
|
||||
direct: messages.direct,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={computedClass}>
|
||||
{sideArm && !isEditing && sideArm !== 'none' ? (
|
||||
|
@ -78,7 +89,7 @@ class Publisher extends ImmutablePureComponent {
|
|||
onClick={onSecondarySubmit}
|
||||
style={{ padding: null }}
|
||||
text={<Icon id={privacyIcons[sideArm]} />}
|
||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`}
|
||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[sideArm])}`}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
@ -86,7 +97,7 @@ class Publisher extends ImmutablePureComponent {
|
|||
<Button
|
||||
className='primary'
|
||||
text={publishText}
|
||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`}
|
||||
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage(privacyNames[privacy])}`}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@ import Warning from '../components/warning';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import { profileLink, termsLink } from 'flavours/glitch/utils/backend_links';
|
||||
import { profileLink, privacyPolicyLink } from 'flavours/glitch/utils/backend_links';
|
||||
|
||||
const buildHashtagRE = () => {
|
||||
try {
|
||||
|
@ -49,7 +49,7 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning
|
|||
if (directMessageWarning) {
|
||||
const message = (
|
||||
<span>
|
||||
<FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> {!!termsLink && <a href={termsLink} target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a>}
|
||||
<FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> {!!privacyPolicyLink && <a href={privacyPolicyLink} target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a>}
|
||||
</span>
|
||||
);
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ const messages = defineMessages({
|
|||
rewrite_mentions_username: { id: 'settings.rewrite_mentions_username', defaultMessage: 'Rewrite with username' },
|
||||
pop_in_left: { id: 'settings.pop_in_left', defaultMessage: 'Left' },
|
||||
pop_in_right: { id: 'settings.pop_in_right', defaultMessage: 'Right' },
|
||||
public: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
||||
class LocalSettingsPage extends React.PureComponent {
|
||||
|
@ -241,10 +245,10 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
id='mastodon-settings--side_arm'
|
||||
options={[
|
||||
{ value: 'none', message: intl.formatMessage(messages.side_arm_none) },
|
||||
{ value: 'direct', message: intl.formatMessage({ id: 'privacy.direct.short' }) },
|
||||
{ value: 'private', message: intl.formatMessage({ id: 'privacy.private.short' }) },
|
||||
{ value: 'unlisted', message: intl.formatMessage({ id: 'privacy.unlisted.short' }) },
|
||||
{ value: 'public', message: intl.formatMessage({ id: 'privacy.public.short' }) },
|
||||
{ value: 'direct', message: intl.formatMessage(messages.direct) },
|
||||
{ value: 'private', message: intl.formatMessage(messages.private) },
|
||||
{ value: 'unlisted', message: intl.formatMessage(messages.unlisted) },
|
||||
{ value: 'public', message: intl.formatMessage(messages.public) },
|
||||
]}
|
||||
onChange={onChange}
|
||||
>
|
||||
|
|
|
@ -8,7 +8,6 @@ import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
|||
import Icon from 'flavours/glitch/components/icon';
|
||||
import { useBlurhash } from 'flavours/glitch/initial_state';
|
||||
import Blurhash from 'flavours/glitch/components/blurhash';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const getHostname = url => {
|
||||
const parser = document.createElement('a');
|
||||
|
@ -45,8 +44,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,
|
||||
};
|
||||
|
||||
|
@ -55,7 +52,6 @@ export default class Card extends React.PureComponent {
|
|||
};
|
||||
|
||||
state = {
|
||||
width: this.props.defaultWidth || 280,
|
||||
previewLoaded: false,
|
||||
embedded: false,
|
||||
revealed: !this.props.sensitive,
|
||||
|
@ -78,24 +74,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;
|
||||
|
||||
|
@ -129,10 +107,6 @@ export default class Card extends React.PureComponent {
|
|||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
|
||||
if (this.node) {
|
||||
this._setDimensions();
|
||||
}
|
||||
};
|
||||
|
||||
handleImageLoad = () => {
|
||||
|
@ -148,36 +122,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 (
|
||||
<div
|
||||
ref={this.setRef}
|
||||
className='status-card__image status-card-video'
|
||||
dangerouslySetInnerHTML={content}
|
||||
style={{ height }}
|
||||
style={{ aspectRatio: `${card.get('width')} / ${card.get('height')}` }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
|
||||
const language = card.get('language') || '';
|
||||
const ratio = card.get('width') / card.get('height');
|
||||
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
|
||||
|
||||
const description = (
|
||||
<div className='status-card__content' lang={language}>
|
||||
|
@ -187,6 +156,14 @@ export default class Card extends React.PureComponent {
|
|||
</div>
|
||||
);
|
||||
|
||||
const thumbnailStyle = {
|
||||
visibility: revealed? null : 'hidden',
|
||||
};
|
||||
|
||||
if (horizontal) {
|
||||
thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`;
|
||||
}
|
||||
|
||||
let embed = '';
|
||||
let canvas = (
|
||||
<Blurhash
|
||||
|
@ -197,7 +174,7 @@ export default class Card extends React.PureComponent {
|
|||
dummy={!useBlurhash}
|
||||
/>
|
||||
);
|
||||
let thumbnail = <img src={card.get('image')} alt='' style={{ width: horizontal ? width : null, height: horizontal ? height : null, visibility: revealed ? null : 'hidden' }} onLoad={this.handleImageLoad} className='status-card__image-image' />;
|
||||
let thumbnail = <img src={card.get('image')} alt='' style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />;
|
||||
let spoilerButton = (
|
||||
<button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
|
||||
<span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
|
||||
|
|
|
@ -32,6 +32,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent,
|
|||
return onClick(e);
|
||||
};
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid -- intentional to have the same look and feel as other menu items
|
||||
<a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex={0}>
|
||||
{iconElement}
|
||||
<span>{text}</span>
|
||||
|
|
|
@ -125,9 +125,15 @@ const mapStateToProps = state => ({
|
|||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
/** Set options in the redux store */
|
||||
/**
|
||||
* Set options in the redux store
|
||||
* @param opts
|
||||
*/
|
||||
setOpt: (opts) => dispatch(doodleSet(opts)),
|
||||
/** Submit doodle for upload */
|
||||
/**
|
||||
* Submit doodle for upload
|
||||
* @param file
|
||||
*/
|
||||
submit: (file) => dispatch(uploadCompose([file])),
|
||||
});
|
||||
|
||||
|
@ -230,7 +236,10 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
//endregion
|
||||
|
||||
/** Key up handler */
|
||||
/**
|
||||
* Key up handler
|
||||
* @param e
|
||||
*/
|
||||
handleKeyUp = (e) => {
|
||||
if (e.target.nodeName === 'INPUT') return;
|
||||
|
||||
|
@ -256,7 +265,10 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
/** Key down handler */
|
||||
/**
|
||||
* Key down handler
|
||||
* @param e
|
||||
*/
|
||||
handleKeyDown = (e) => {
|
||||
if (e.key === 'Control' || e.key === 'Meta') {
|
||||
this.controlHeld = true;
|
||||
|
@ -292,7 +304,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
/**
|
||||
* Set reference to the canvas element.
|
||||
* This is called during component init
|
||||
*
|
||||
* @param elem - canvas element
|
||||
*/
|
||||
setCanvasRef = (elem) => {
|
||||
|
@ -334,7 +345,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Set up the sketcher instance
|
||||
*
|
||||
* @param canvas - canvas element. Null if we're just resizing
|
||||
*/
|
||||
initSketcher (canvas = null) {
|
||||
|
@ -433,7 +443,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
/**
|
||||
* Palette left click.
|
||||
* Selects Fg color (or Bg, if Control/Meta is held)
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
onPaletteClick = (e) => {
|
||||
|
@ -452,7 +461,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
/**
|
||||
* Palette right click.
|
||||
* Selects Bg color
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
onPaletteRClick = (e) => {
|
||||
|
@ -463,7 +471,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Handle click on the Draw mode button
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
setModeDraw = (e) => {
|
||||
|
@ -473,7 +480,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Handle click on the Fill mode button
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
setModeFill = (e) => {
|
||||
|
@ -483,7 +489,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Handle click on Smooth checkbox
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
tglSmooth = (e) => {
|
||||
|
@ -493,7 +498,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Handle click on Adaptive checkbox
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
tglAdaptive = (e) => {
|
||||
|
@ -503,7 +507,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Handle change of the Weight input field
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
setWeight = (e) => {
|
||||
|
@ -512,7 +515,6 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
|
||||
/**
|
||||
* Set size - clalback from the select box
|
||||
*
|
||||
* @param e - event
|
||||
*/
|
||||
changeSize = (e) => {
|
||||
|
|
|
@ -378,7 +378,7 @@ class UI extends React.Component {
|
|||
|
||||
if (layout !== this.props.layout) {
|
||||
this.handleLayoutChange.cancel();
|
||||
this.props.dispatch(changeLayout(layout));
|
||||
this.props.dispatch(changeLayout({ layout }));
|
||||
} else {
|
||||
this.handleLayoutChange();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { is } from 'immutable';
|
||||
import { throttle, debounce } from 'lodash';
|
||||
import { throttle } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
|
||||
|
@ -102,8 +102,6 @@ class Video extends React.PureComponent {
|
|||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
sensitive: PropTypes.bool,
|
||||
currentTime: PropTypes.number,
|
||||
onOpenVideo: PropTypes.func,
|
||||
|
@ -112,7 +110,6 @@ class Video extends React.PureComponent {
|
|||
inline: PropTypes.bool,
|
||||
editable: PropTypes.bool,
|
||||
alwaysVisible: PropTypes.bool,
|
||||
cacheWidth: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
letterbox: PropTypes.bool,
|
||||
fullwidth: PropTypes.bool,
|
||||
|
@ -138,41 +135,16 @@ class Video extends React.PureComponent {
|
|||
volume: 0.5,
|
||||
paused: true,
|
||||
dragging: false,
|
||||
containerWidth: this.props.width,
|
||||
fullscreen: false,
|
||||
hovered: false,
|
||||
muted: false,
|
||||
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
||||
};
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
||||
this.setState({ revealed: nextProps.visible });
|
||||
}
|
||||
}
|
||||
|
||||
setPlayerRef = c => {
|
||||
this.player = c;
|
||||
|
||||
if (this.player) {
|
||||
this._setDimensions();
|
||||
}
|
||||
};
|
||||
|
||||
_setDimensions () {
|
||||
const width = this.player.offsetWidth;
|
||||
|
||||
if (width && width !== this.state.containerWidth) {
|
||||
if (this.props.cacheWidth) {
|
||||
this.props.cacheWidth(width);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
containerWidth: width,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setVideoRef = c => {
|
||||
this.video = c;
|
||||
|
||||
|
@ -381,12 +353,10 @@ class Video extends React.PureComponent {
|
|||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
|
||||
document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
|
||||
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
|
||||
|
@ -403,26 +373,18 @@ class Video extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.player && this.player.offsetWidth && this.player.offsetWidth !== this.state.containerWidth && !this.state.fullscreen) {
|
||||
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
|
||||
this.setState({
|
||||
containerWidth: this.player.offsetWidth,
|
||||
});
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
||||
this.setState({ revealed: nextProps.visible });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.video && this.state.revealed && this.props.preventPlayback && !prevProps.preventPlayback) {
|
||||
this.video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
handleResize = debounce(() => {
|
||||
if (this.player) {
|
||||
this._setDimensions();
|
||||
}
|
||||
}, 250, {
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
handleScroll = throttle(() => {
|
||||
if (!this.video) {
|
||||
return;
|
||||
|
@ -540,21 +502,12 @@ class Video extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
|
||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||
const playerStyle = {};
|
||||
|
||||
const computedClass = classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable, letterbox, 'full-width': fullwidth });
|
||||
|
||||
let { width, height } = this.props;
|
||||
|
||||
if (inline && containerWidth) {
|
||||
width = containerWidth;
|
||||
height = containerWidth / (16/9);
|
||||
|
||||
playerStyle.height = height;
|
||||
} else if (inline) {
|
||||
return (<div className={computedClass} ref={this.setPlayerRef} tabIndex={0} />);
|
||||
if (inline) {
|
||||
playerStyle.aspectRatio = '16 / 9';
|
||||
}
|
||||
|
||||
let preload;
|
||||
|
@ -578,7 +531,7 @@ class Video extends React.PureComponent {
|
|||
return (
|
||||
<div
|
||||
role='menuitem'
|
||||
className={computedClass}
|
||||
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable, letterbox, 'full-width': fullwidth })}
|
||||
style={playerStyle}
|
||||
ref={this.setPlayerRef}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
|
@ -605,8 +558,6 @@ class Video extends React.PureComponent {
|
|||
aria-label={alt}
|
||||
title={alt}
|
||||
lang={lang}
|
||||
width={width}
|
||||
height={height}
|
||||
volume={volume}
|
||||
onClick={this.togglePlay}
|
||||
onKeyDown={this.handleVideoKeyDown}
|
||||
|
@ -615,6 +566,7 @@ class Video extends React.PureComponent {
|
|||
onLoadedData={this.handleLoadedData}
|
||||
onProgress={this.handleProgress}
|
||||
onVolumeChange={this.handleVolumeChange}
|
||||
style={{ ...playerStyle, width: '100%' }}
|
||||
/>}
|
||||
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
* @property {object} local_settings
|
||||
* @property {number} max_toot_chars
|
||||
* @property {number} poll_limits
|
||||
* @property {number} max_reactions
|
||||
*/
|
||||
|
||||
const element = document.getElementById('initial-state');
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
// @ts-check
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import { forceSingleColumn } from 'flavours/glitch/initial_state';
|
||||
|
||||
const LAYOUT_BREAKPOINT = 630;
|
||||
|
||||
/**
|
||||
* @param {number} width
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isMobile = width => width <= LAYOUT_BREAKPOINT;
|
||||
export const isMobile = (width: number) => width <= LAYOUT_BREAKPOINT;
|
||||
|
||||
/**
|
||||
* @param {string} layout_local_setting
|
||||
* @returns {string}
|
||||
*/
|
||||
export const layoutFromWindow = (layout_local_setting) => {
|
||||
export type LayoutType = 'mobile' | 'single-column' | 'multi-column';
|
||||
export const layoutFromWindow = (layout_local_setting : string): LayoutType => {
|
||||
switch (layout_local_setting) {
|
||||
case 'multiple':
|
||||
return 'multi-column';
|
||||
|
@ -36,8 +27,9 @@ export const layoutFromWindow = (layout_local_setting) => {
|
|||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && window.MSStream != null;
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications';
|
||||
import Mastodon, { store } from 'flavours/glitch/containers/mastodon';
|
||||
import Mastodon from 'flavours/glitch/containers/mastodon';
|
||||
import { store } from 'flavours/glitch/store/configureStore';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import ready from 'flavours/glitch/ready';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
|
||||
import { APP_LAYOUT_CHANGE } from 'flavours/glitch/actions/app';
|
||||
import { changeLayout } from 'flavours/glitch/actions/app';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { layoutFromWindow } from 'flavours/glitch/is_mobile';
|
||||
|
||||
|
@ -16,8 +16,8 @@ export default function meta(state = initialState, action) {
|
|||
return state.merge(action.state.get('meta'))
|
||||
.set('permissions', action.state.getIn(['role', 'permissions']))
|
||||
.set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout'])));
|
||||
case APP_LAYOUT_CHANGE:
|
||||
return state.set('layout', action.layout);
|
||||
case changeLayout.type:
|
||||
return state.set('layout', action.payload.layout);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
PINNED_ACCOUNTS_FETCH_REQUEST,
|
||||
PINNED_ACCOUNTS_FETCH_SUCCESS,
|
||||
PINNED_ACCOUNTS_FETCH_FAIL,
|
||||
PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_READY,
|
||||
PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS,
|
||||
PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR,
|
||||
PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE,
|
||||
ACCOUNT_PIN_SUCCESS,
|
||||
|
@ -38,10 +38,10 @@ export default function listEditorReducer(state = initialState, action) {
|
|||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.accounts.map(item => item.id)));
|
||||
}));
|
||||
case PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS:
|
||||
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
|
||||
case PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE:
|
||||
return state.setIn(['suggestions', 'value'], action.value);
|
||||
case PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_READY:
|
||||
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
|
||||
case PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
|
||||
map.set('items', ImmutableList());
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const easingOutQuint = (x, t, b, c, d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b;
|
||||
|
||||
const scroll = (node, key, target) => {
|
||||
const easingOutQuint = (x: number, t: number, b: number, c: number, d: number) => c * ((t = t / d - 1) * t * t * t * t + 1) + b;
|
||||
const scroll = (node: Element, key: 'scrollTop' | 'scrollLeft', target: number) => {
|
||||
const startTime = Date.now();
|
||||
const offset = node[key];
|
||||
const gap = target - offset;
|
||||
|
@ -28,5 +27,5 @@ const scroll = (node, key, target) => {
|
|||
|
||||
const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style;
|
||||
|
||||
export const scrollRight = (node, position) => isScrollBehaviorSupported ? node.scrollTo({ left: position, behavior: 'smooth' }) : scroll(node, 'scrollLeft', position);
|
||||
export const scrollTop = (node) => isScrollBehaviorSupported ? node.scrollTo({ top: 0, behavior: 'smooth' }) : scroll(node, 'scrollTop', 0);
|
||||
export const scrollRight = (node: Element, position: number) => isScrollBehaviorSupported ? node.scrollTo({ left: position, behavior: 'smooth' }) : scroll(node, 'scrollLeft', position);
|
||||
export const scrollTop = (node: Element) => isScrollBehaviorSupported ? node.scrollTo({ top: 0, behavior: 'smooth' }) : scroll(node, 'scrollTop', 0);
|
|
@ -1,15 +1,16 @@
|
|||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import thunk from 'redux-thunk';
|
||||
import appReducer from '../reducers';
|
||||
import loadingBarMiddleware from '../middleware/loading_bar';
|
||||
import errorsMiddleware from '../middleware/errors';
|
||||
import soundsMiddleware from '../middleware/sounds';
|
||||
|
||||
export default function configureStore() {
|
||||
return createStore(appReducer, compose(applyMiddleware(
|
||||
export const store = configureStore({
|
||||
reducer: appReducer,
|
||||
middleware: [
|
||||
thunk,
|
||||
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
|
||||
errorsMiddleware(),
|
||||
soundsMiddleware(),
|
||||
), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
|
||||
}
|
||||
],
|
||||
});
|
||||
|
|
|
@ -17,10 +17,10 @@ let sharedConnection;
|
|||
*/
|
||||
|
||||
/**
|
||||
* @typedef StreamEvent
|
||||
* @property {string} event
|
||||
* @property {object} payload
|
||||
*/
|
||||
* @typedef StreamEvent
|
||||
* @property {string} event
|
||||
* @property {object} payload
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Array.<Subscription>}
|
||||
|
@ -126,7 +126,7 @@ const sharedCallbacks = {
|
|||
/**
|
||||
* @param {string} channelName
|
||||
* @param {Object.<string, string>} params
|
||||
* @return {string}
|
||||
* @returns {string}
|
||||
*/
|
||||
const channelNameWithInlineParams = (channelName, params) => {
|
||||
if (Object.keys(params).length === 0) {
|
||||
|
@ -140,7 +140,7 @@ const channelNameWithInlineParams = (channelName, params) => {
|
|||
* @param {string} channelName
|
||||
* @param {Object.<string, string>} params
|
||||
* @param {function(Function, Function): { onConnect: (function(): void), onReceive: (function(StreamEvent): void), onDisconnect: (function(): void) }} callbacks
|
||||
* @return {function(): void}
|
||||
* @returns {function(): void}
|
||||
*/
|
||||
// @ts-expect-error
|
||||
export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => {
|
||||
|
@ -227,7 +227,7 @@ const handleEventSourceMessage = (e, received) => {
|
|||
* @param {string} accessToken
|
||||
* @param {string} channelName
|
||||
* @param {{ connected: Function, received: function(StreamEvent): void, disconnected: Function, reconnected: Function }} callbacks
|
||||
* @return {WebSocketClient | EventSource}
|
||||
* @returns {WebSocketClient | EventSource}
|
||||
*/
|
||||
const createConnection = (streamingAPIBaseURL, accessToken, channelName, { connected, received, disconnected, reconnected }) => {
|
||||
const params = channelName.split('&');
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
margin-right: -14px;
|
||||
width: inherit;
|
||||
max-width: none;
|
||||
height: 250px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,30 +43,25 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.media-gallery__gifv__label {
|
||||
display: block;
|
||||
.media-gallery__item__badges {
|
||||
position: absolute;
|
||||
color: $primary-text-color;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
bottom: 6px;
|
||||
inset-inline-start: 6px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s ease;
|
||||
line-height: 18px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.media-gallery__gifv {
|
||||
&:hover {
|
||||
.media-gallery__gifv__label {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.media-gallery__gifv__label {
|
||||
display: block;
|
||||
color: $white;
|
||||
background: rgba($black, 0.65);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.media-gallery {
|
||||
|
@ -77,6 +72,10 @@
|
|||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: 50% 50%;
|
||||
gap: 2px;
|
||||
|
||||
@include fullwidth-gallery;
|
||||
}
|
||||
|
@ -85,13 +84,16 @@
|
|||
border: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
float: left;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.full-width & {
|
||||
border-radius: 0;
|
||||
&--tall {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
&--wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
&.standalone {
|
||||
|
@ -101,6 +103,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.full-width & {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&.letterbox {
|
||||
background: $base-shadow-color;
|
||||
}
|
||||
|
@ -381,6 +387,7 @@
|
|||
background: darken($ui-base-color, 8%);
|
||||
border-radius: 4px;
|
||||
padding-bottom: 44px;
|
||||
width: 100%;
|
||||
|
||||
&.editable {
|
||||
border-radius: 0;
|
||||
|
|
|
@ -707,7 +707,6 @@ a.status__display-name,
|
|||
margin-inline-end: 10px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
box-shadow: 0 0 0 2px $ui-base-color;
|
||||
}
|
||||
|
||||
.muted {
|
||||
|
@ -825,6 +824,10 @@ a.status-card {
|
|||
}
|
||||
|
||||
.status-card-video {
|
||||
// Firefox has a bug where frameborder=0 iframes add some extra blank space
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=155174
|
||||
overflow: hidden;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -1064,12 +1067,12 @@ a.status-card.compact:hover {
|
|||
}
|
||||
|
||||
&__line {
|
||||
height: 16px - 4px;
|
||||
height: 10px - 4px;
|
||||
border-inline-start: 2px solid lighten($ui-base-color, 8%);
|
||||
width: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 16px + ((46px - 2px) * 0.5);
|
||||
inset-inline-start: 14px + ((48px - 2px) * 0.5);
|
||||
|
||||
&--full {
|
||||
top: 0;
|
||||
|
@ -1079,8 +1082,8 @@ a.status-card.compact:hover {
|
|||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 16px - 4px;
|
||||
height: 46px + 4px + 4px;
|
||||
top: 10px - 4px;
|
||||
height: 48px + 4px + 4px;
|
||||
width: 2px;
|
||||
background: $ui-base-color;
|
||||
inset-inline-start: -2px;
|
||||
|
@ -1088,8 +1091,8 @@ a.status-card.compact:hover {
|
|||
}
|
||||
|
||||
&--first {
|
||||
top: 16px + 46px + 4px;
|
||||
height: calc(100% - (16px + 46px + 4px));
|
||||
top: 10px + 48px + 4px;
|
||||
height: calc(100% - (10px + 48px + 4px));
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
|
@ -1171,6 +1174,7 @@ a.status-card.compact:hover {
|
|||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
color: $darker-text-color;
|
||||
aspect-ratio: 16 / 9;
|
||||
|
||||
i {
|
||||
display: block;
|
||||
|
|
10
app/javascript/flavours/glitch/types/resources.ts
Normal file
10
app/javascript/flavours/glitch/types/resources.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import type { Record } from 'immutable';
|
||||
|
||||
type AccountValues = {
|
||||
id: number;
|
||||
avatar: string;
|
||||
avatar_static: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type Account = Record<AccountValues>;
|
1
app/javascript/flavours/glitch/types/util.ts
Normal file
1
app/javascript/flavours/glitch/types/util.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export type ValueOf<T> = T[keyof T];
|
|
@ -1,4 +1,4 @@
|
|||
export const decode = base64 => {
|
||||
export const decode = (base64: string): Uint8Array => {
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export const toServerSideType = columnType => {
|
||||
export const toServerSideType = (columnType: string) => {
|
||||
switch (columnType) {
|
||||
case 'home':
|
||||
case 'notifications':
|
|
@ -1,6 +1,6 @@
|
|||
export function recoverHashtags (recognizedTags, text) {
|
||||
return recognizedTags.map(tag => {
|
||||
const re = new RegExp(`(?:^|[^/)\w])#(${tag.name})`, 'i');
|
||||
const re = new RegExp(`(?:^|[^/)\\w])#(${tag.name})`, 'i');
|
||||
const matched_hashtag = text.match(re);
|
||||
return matched_hashtag ? matched_hashtag[1] : null;
|
||||
},
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue