Merge pull request #2449 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 9a3d047f3e
main
Claire 2023-10-27 17:45:19 +02:00 committed by GitHub
commit 59893a4eab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
425 changed files with 4668 additions and 5836 deletions

View File

@ -236,7 +236,7 @@ module.exports = {
}, },
// Common React utilities // Common React utilities
{ {
pattern: '{classnames,react-helmet,react-router-dom}', pattern: '{classnames,react-helmet,react-router,react-router-dom}',
group: 'external', group: 'external',
position: 'before', position: 'before',
}, },

View File

@ -0,0 +1,19 @@
name: 'Setup Javascript'
description: 'Setup a Javascript environment ready to run the Mastodon code'
inputs:
onlyProduction:
description: Only install production dependencies
default: 'false'
runs:
using: 'composite'
steps:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
shell: bash
run: yarn --frozen-lockfile ${{ inputs.onlyProduction != 'false' && '--production' || '' }}

23
.github/actions/setup-ruby/action.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: 'Setup RUby'
description: 'Setup a Ruby environment ready to run the Mastodon code'
inputs:
ruby-version:
description: The Ruby version to install
default: '.ruby-version'
additional-system-dependencies:
description: 'Additional packages to install'
runs:
using: 'composite'
steps:
- name: Install system dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev ${{ inputs.additional-system-dependencies }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ inputs.ruby-version }}
bundler-cache: true

View File

@ -3,7 +3,6 @@
extends: [ extends: [
'config:recommended', 'config:recommended',
':labels(dependencies)', ':labels(dependencies)',
':maintainLockFilesMonthly', // update non-direct dependencies monthly
':prConcurrentLimitNone', // Remove limit for open PRs at any time. ':prConcurrentLimitNone', // Remove limit for open PRs at any time.
':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour. ':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour.
], ],

View File

@ -27,14 +27,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: sudo apt-get install -y libicu-dev libidn11-dev uses: ./.github/actions/setup-ruby
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Run bundler-audit - name: Run bundler-audit
run: bundle exec bundler-audit run: bundle exec bundler-audit

View File

@ -19,25 +19,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install system dependencies - name: Set up Ruby environment
run: | uses: ./.github/actions/setup-ruby
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby - name: Set up Javascript environment
uses: ruby/setup-ruby@v1 uses: ./.github/actions/setup-javascript
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Check for missing strings in English JSON - name: Check for missing strings in English JSON
run: | run: |

View File

@ -45,14 +45,8 @@ jobs:
run: sudo chown -R runner:docker . run: sudo chown -R runner:docker .
# This is needed to run the normalize step # This is needed to run the normalize step
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: sudo apt-get install -y libicu-dev libidn11-dev uses: ./.github/actions/setup-ruby
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Run i18n normalize task - name: Run i18n normalize task
run: bundle exec i18n-tasks normalize run: bundle exec i18n-tasks normalize

View File

@ -35,14 +35,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- uses: xt0rted/stylelint-problem-matcher@v1 - uses: xt0rted/stylelint-problem-matcher@v1

View File

@ -30,16 +30,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: | uses: ./.github/actions/setup-ruby
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Run haml-lint - name: Run haml-lint
run: | run: |

View File

@ -39,14 +39,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: ESLint - name: ESLint
run: yarn lint:js --max-warnings 0 run: yarn lint:js --max-warnings 0

View File

@ -31,14 +31,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Prettier - name: Prettier
run: yarn lint:json run: yarn lint:json

View File

@ -31,14 +31,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Prettier - name: Prettier
run: yarn lint:md run: yarn lint:md

View File

@ -31,14 +31,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: sudo apt-get install -y libicu-dev libidn11-dev uses: ./.github/actions/setup-ruby
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Set-up RuboCop Problem Matcher - name: Set-up RuboCop Problem Matcher
uses: r7kamura/rubocop-problem-matchers-action@v1 uses: r7kamura/rubocop-problem-matchers-action@v1

View File

@ -33,14 +33,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Prettier - name: Prettier
run: yarn lint:yml run: yarn lint:yml

View File

@ -35,14 +35,8 @@ jobs:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Javascript environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-javascript
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install all yarn packages
run: yarn --frozen-lockfile
- name: Jest testing - name: Jest testing
run: yarn jest --reporters github-actions summary run: yarn jest --reporters github-actions summary

View File

@ -72,16 +72,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: | uses: ./.github/actions/setup-ruby
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Create database - name: Create database
run: './bin/rails db:create' run: './bin/rails db:create'

View File

@ -71,16 +71,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install native Ruby dependencies - name: Set up Ruby environment
run: | uses: ./.github/actions/setup-ruby
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Create database - name: Create database
run: './bin/rails db:create' run: './bin/rails db:create'

View File

@ -34,24 +34,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Ruby environment
uses: actions/setup-node@v3 uses: ./.github/actions/setup-ruby
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
with: with:
cache: yarn onlyProduction: 'true'
node-version-file: '.nvmrc'
- name: Install native Ruby dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- run: yarn --frozen-lockfile --production
- name: Precompile assets - name: Precompile assets
# Previously had set this, but it's not supported # Previously had set this, but it's not supported
# export NODE_OPTIONS=--openssl-legacy-provider # export NODE_OPTIONS=--openssl-legacy-provider
@ -135,20 +125,11 @@ jobs:
path: './public' path: './public'
name: ${{ github.sha }} name: ${{ github.sha }}
- name: Update package index - name: Set up Ruby environment
run: sudo apt-get update uses: ./.github/actions/setup-ruby
- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev
- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick libpam-dev
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true additional-system-dependencies: ffmpeg imagemagick libpam-dev
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'
@ -210,28 +191,14 @@ jobs:
path: './public' path: './public'
name: ${{ github.sha }} name: ${{ github.sha }}
- name: Update package index - name: Set up Ruby environment
run: sudo apt-get update uses: ./.github/actions/setup-ruby
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev
- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true additional-system-dependencies: ffmpeg imagemagick
- run: yarn --frozen-lockfile - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'
@ -328,28 +295,14 @@ jobs:
path: './public' path: './public'
name: ${{ github.sha }} name: ${{ github.sha }}
- name: Update package index - name: Set up Ruby environment
run: sudo apt-get update uses: ./.github/actions/setup-ruby
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
- name: Install native Ruby dependencies
run: sudo apt-get install -y libicu-dev libidn11-dev
- name: Install additional system dependencies
run: sudo apt-get install -y ffmpeg imagemagick
- name: Set up bundler cache
uses: ruby/setup-ruby@v1
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
bundler-cache: true additional-system-dependencies: ffmpeg imagemagick
- run: yarn --frozen-lockfile - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'

View File

@ -1,33 +1,21 @@
# This configuration was generated by # This configuration was generated by
# `haml-lint --auto-gen-config` # `haml-lint --auto-gen-config`
# on 2023-10-11 11:31:24 -0400 using Haml-Lint version 0.51.0. # on 2023-10-25 08:29:48 -0400 using Haml-Lint version 0.51.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base. # one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again. # versions of Haml-Lint, may require this file to be generated again.
linters: linters:
# Offense count: 946 # Offense count: 945
LineLength: LineLength:
enabled: false enabled: false
# Offense count: 22 # Offense count: 10
UnnecessaryStringOutput:
enabled: false
# Offense count: 44
RuboCop: RuboCop:
enabled: false
# Offense count: 3
ViewLength:
exclude: exclude:
- 'app/views/admin/accounts/show.html.haml' - 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/reports/show.html.haml' - 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/disputes/strikes/show.html.haml' - 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/roles/_form.html.haml'
# Offense count: 2 - 'app/views/layouts/application.html.haml'
IdNames:
exclude:
- 'app/views/oauth/authorizations/error.html.haml'
- 'app/views/shared/_error_messages.html.haml'

2
.nvmrc
View File

@ -1 +1 @@
20.8 20.9

View File

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.56.1. # using RuboCop version 1.57.1.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -80,7 +80,6 @@ RSpec/AnyInstance:
- 'spec/controllers/admin/accounts_controller_spec.rb' - 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/resets_controller_spec.rb' - 'spec/controllers/admin/resets_controller_spec.rb'
- 'spec/controllers/admin/settings/branding_controller_spec.rb' - 'spec/controllers/admin/settings/branding_controller_spec.rb'
- 'spec/controllers/api/v1/media_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb' - 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb' - 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb' - 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
@ -180,7 +179,6 @@ RSpec/LetSetup:
RSpec/MessageChain: RSpec/MessageChain:
Exclude: Exclude:
- 'spec/controllers/api/v1/media_controller_spec.rb'
- 'spec/models/concerns/remotable_spec.rb' - 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/session_activation_spec.rb' - 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb' - 'spec/models/setting_spec.rb'
@ -219,19 +217,6 @@ Rails/ApplicationController:
Exclude: Exclude:
- 'app/controllers/health_controller.rb' - 'app/controllers/health_controller.rb'
# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/CreateTableWithTimestamps:
Exclude:
- 'db/migrate/20170508230434_create_conversation_mutes.rb'
- 'db/migrate/20170823162448_create_status_pins.rb'
- 'db/migrate/20171116161857_create_list_accounts.rb'
- 'db/migrate/20180929222014_create_account_conversations.rb'
- 'db/migrate/20181007025445_create_pghero_space_stats.rb'
- 'db/migrate/20190103124649_create_scheduled_statuses.rb'
- 'db/migrate/20220824233535_create_status_trends.rb'
- 'db/migrate/20221006061337_create_preview_card_trends.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity. # Configuration parameters: Severity.
Rails/DuplicateAssociation: Rails/DuplicateAssociation:
@ -273,7 +258,6 @@ Rails/LexicallyScopedActionFilter:
Exclude: Exclude:
- 'app/controllers/auth/passwords_controller.rb' - 'app/controllers/auth/passwords_controller.rb'
- 'app/controllers/auth/registrations_controller.rb' - 'app/controllers/auth/registrations_controller.rb'
- 'app/controllers/auth/sessions_controller.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Rails/NegateInclude: Rails/NegateInclude:
@ -289,7 +273,6 @@ Rails/NegateInclude:
- 'app/models/custom_filter.rb' - 'app/models/custom_filter.rb'
- 'app/services/activitypub/process_status_update_service.rb' - 'app/services/activitypub/process_status_update_service.rb'
- 'app/services/fetch_link_card_service.rb' - 'app/services/fetch_link_card_service.rb'
- 'app/services/search_service.rb'
- 'app/workers/web/push_notification_worker.rb' - 'app/workers/web/push_notification_worker.rb'
- 'lib/paperclip/color_extractor.rb' - 'lib/paperclip/color_extractor.rb'
@ -309,24 +292,6 @@ Rails/RakeEnvironment:
- 'lib/tasks/repo.rake' - 'lib/tasks/repo.rake'
- 'lib/tasks/statistics.rake' - 'lib/tasks/statistics.rake'
# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/ReversibleMigration:
Exclude:
- 'db/migrate/20160223164502_make_uris_nullable_in_statuses.rb'
- 'db/migrate/20161122163057_remove_unneeded_indexes.rb'
- 'db/migrate/20170205175257_remove_devices.rb'
- 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb'
- 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb'
- 'db/migrate/20170609145826_remove_default_language_from_statuses.rb'
- 'db/migrate/20170711225116_fix_null_booleans.rb'
- 'db/migrate/20171129172043_add_index_on_stream_entries.rb'
- 'db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb'
- 'db/migrate/20171226094803_more_faster_index_on_notifications.rb'
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- 'db/migrate/20180617162849_remove_unused_indexes.rb'
- 'db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb'
# Configuration parameters: ForbiddenMethods, AllowedMethods. # Configuration parameters: ForbiddenMethods, AllowedMethods.
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all # ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
Rails/SkipsModelValidations: Rails/SkipsModelValidations:
@ -379,31 +344,6 @@ Rails/SkipsModelValidations:
- 'spec/services/follow_service_spec.rb' - 'spec/services/follow_service_spec.rb'
- 'spec/services/update_account_service_spec.rb' - 'spec/services/update_account_service_spec.rb'
# Configuration parameters: Include.
# Include: db/**/*.rb
Rails/ThreeStateBooleanColumn:
Exclude:
- '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. # Configuration parameters: Include.
# Include: app/models/**/*.rb # Include: app/models/**/*.rb
Rails/UniqueValidationWithoutIndex: Rails/UniqueValidationWithoutIndex:
@ -467,7 +407,7 @@ Style/CaseEquality:
Exclude: Exclude:
- 'config/initializers/trusted_proxies.rb' - 'config/initializers/trusted_proxies.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedMethods, AllowedPatterns. # Configuration parameters: AllowedMethods, AllowedPatterns.
# AllowedMethods: ==, equal?, eql? # AllowedMethods: ==, equal?, eql?
Style/ClassEqualityComparison: Style/ClassEqualityComparison:
@ -675,7 +615,6 @@ Style/RedundantReturn:
Style/SafeNavigation: Style/SafeNavigation:
Exclude: Exclude:
- 'app/models/concerns/account_finder_concern.rb' - 'app/models/concerns/account_finder_concern.rb'
- 'app/models/status.rb'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ RUN apt-get update && \
libgdbm-dev \ libgdbm-dev \
libgmp-dev \ libgmp-dev \
libssl-dev \ libssl-dev \
libyaml-0-2 \ libyaml-dev \
ca-certificates \ ca-certificates \
libreadline8 \ libreadline8 \
python3 \ python3 \

View File

@ -4,7 +4,7 @@ source 'https://rubygems.org'
ruby '>= 3.0.0' ruby '>= 3.0.0'
gem 'puma', '~> 6.3' gem 'puma', '~> 6.3'
gem 'rails', '~> 7.0' gem 'rails', '~> 7.1.1'
gem 'sprockets', '~> 3.7.2' gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2' gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.7' gem 'rack', '~> 2.2.7'

View File

@ -39,75 +39,83 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.8) actioncable (7.1.1)
actionpack (= 7.0.8) actionpack (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.8) zeitwerk (~> 2.6)
actionpack (= 7.0.8) actionmailbox (7.1.1)
activejob (= 7.0.8) actionpack (= 7.1.1)
activerecord (= 7.0.8) activejob (= 7.1.1)
activestorage (= 7.0.8) activerecord (= 7.1.1)
activesupport (= 7.0.8) activestorage (= 7.1.1)
activesupport (= 7.1.1)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.8) actionmailer (7.1.1)
actionpack (= 7.0.8) actionpack (= 7.1.1)
actionview (= 7.0.8) actionview (= 7.1.1)
activejob (= 7.0.8) activejob (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
actionpack (7.0.8) actionpack (7.1.1)
actionview (= 7.0.8) actionview (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
rack (~> 2.0, >= 2.2.4) nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.6)
actiontext (7.0.8) actiontext (7.1.1)
actionpack (= 7.0.8) actionpack (= 7.1.1)
activerecord (= 7.0.8) activerecord (= 7.1.1)
activestorage (= 7.0.8) activestorage (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.8) actionview (7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.11)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.6)
active_model_serializers (0.10.14) active_model_serializers (0.10.14)
actionpack (>= 4.1) actionpack (>= 4.1)
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.8) activejob (7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.8) activemodel (7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
activerecord (7.0.8) activerecord (7.1.1)
activemodel (= 7.0.8) activemodel (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
activestorage (7.0.8) timeout (>= 0.4.0)
actionpack (= 7.0.8) activestorage (7.1.1)
activejob (= 7.0.8) actionpack (= 7.1.1)
activerecord (= 7.0.8) activejob (= 7.1.1)
activesupport (= 7.0.8) activerecord (= 7.1.1)
activesupport (= 7.1.1)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) activesupport (7.1.1)
activesupport (7.0.8) base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.5) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
@ -158,6 +166,7 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bigdecimal (3.1.4)
bindata (2.4.15) bindata (2.4.15)
binding_of_caller (1.0.0) binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
@ -237,6 +246,8 @@ GEM
dotenv-rails (2.8.1) dotenv-rails (2.8.1)
dotenv (= 2.8.1) dotenv (= 2.8.1)
railties (>= 3.2) railties (>= 3.2)
drb (2.1.1)
ruby2_keywords
ed25519 (1.3.0) ed25519 (1.3.0)
elasticsearch (7.13.3) elasticsearch (7.13.3)
elasticsearch-api (= 7.13.3) elasticsearch-api (= 7.13.3)
@ -305,8 +316,8 @@ GEM
fuubar (2.5.1) fuubar (2.5.1)
rspec-core (~> 3.0) rspec-core (~> 3.0)
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
globalid (1.1.0) globalid (1.2.1)
activesupport (>= 5.0) activesupport (>= 6.1)
haml (6.2.0) haml (6.2.0)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
@ -357,7 +368,11 @@ GEM
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.5) idn-ruby (0.1.5)
io-console (0.6.0)
ipaddress (0.8.3) ipaddress (0.8.3)
irb (1.8.1)
rdoc
reline (>= 0.3.8)
jmespath (1.6.2) jmespath (1.6.2)
json (2.6.3) json (2.6.3)
json-canonicalization (0.3.2) json-canonicalization (0.3.2)
@ -434,7 +449,6 @@ GEM
azure-storage-blob (~> 2.0.1) azure-storage-blob (~> 2.0.1)
hashie (~> 5.0) hashie (~> 5.0)
memory_profiler (1.0.1) memory_profiler (1.0.1)
method_source (1.0.0)
mime-types (3.5.1) mime-types (3.5.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808) mime-types-data (3.2023.0808)
@ -444,11 +458,12 @@ GEM
msgpack (1.7.1) msgpack (1.7.1)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.3.0) multipart-post (2.3.0)
mutex_m (0.1.2)
net-http (0.3.2) net-http (0.3.2)
uri uri
net-http-persistent (4.0.2) net-http-persistent (4.0.2)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.3.7) net-imap (0.4.1)
date date
net-protocol net-protocol
net-ldap (0.18.0) net-ldap (0.18.0)
@ -456,7 +471,7 @@ GEM
net-protocol net-protocol
net-protocol (0.2.1) net-protocol (0.2.1)
timeout timeout
net-smtp (0.3.3) net-smtp (0.4.0)
net-protocol net-protocol
nio4r (2.5.9) nio4r (2.5.9)
nokogiri (1.15.4) nokogiri (1.15.4)
@ -512,6 +527,8 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
psych (5.1.1)
stringio
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.4.0) puma (6.4.0)
nio4r (~> 2.0) nio4r (~> 2.0)
@ -534,22 +551,27 @@ GEM
rack rack
rack-proxy (0.7.6) rack-proxy (0.7.6)
rack rack
rack-session (1.0.1)
rack (< 3)
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.8) rackup (1.0.0)
actioncable (= 7.0.8) rack (< 3)
actionmailbox (= 7.0.8) webrick
actionmailer (= 7.0.8) rails (7.1.1)
actionpack (= 7.0.8) actioncable (= 7.1.1)
actiontext (= 7.0.8) actionmailbox (= 7.1.1)
actionview (= 7.0.8) actionmailer (= 7.1.1)
activejob (= 7.0.8) actionpack (= 7.1.1)
activemodel (= 7.0.8) actiontext (= 7.1.1)
activerecord (= 7.0.8) actionview (= 7.1.1)
activestorage (= 7.0.8) activejob (= 7.1.1)
activesupport (= 7.0.8) activemodel (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.8) railties (= 7.1.1)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -564,19 +586,22 @@ GEM
rails-i18n (7.0.8) rails-i18n (7.0.8)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.0.8) railties (7.1.1)
actionpack (= 7.0.8) actionpack (= 7.1.1)
activesupport (= 7.0.8) activesupport (= 7.1.1)
method_source irb
rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.5) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
rdf (3.2.11) rdf (3.2.11)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1) rdf-normalize (0.6.1)
rdf (~> 3.2) rdf (~> 3.2)
rdoc (6.5.0)
psych (>= 4.0.0)
redcarpet (3.6.0) redcarpet (3.6.0)
redis (4.8.1) redis (4.8.1)
redis-namespace (1.11.0) redis-namespace (1.11.0)
@ -584,13 +609,15 @@ GEM
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.8.2) regexp_parser (2.8.2)
reline (0.3.9)
io-console (~> 0.5)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.2.6) rexml (3.2.6)
rotp (6.2.2) rotp (6.3.0)
rouge (4.1.2) rouge (4.1.2)
rpam2 (4.0.2) rpam2 (4.0.2)
rqrcode (2.2.0) rqrcode (2.2.0)
@ -712,6 +739,7 @@ GEM
statsd-ruby (1.5.0) statsd-ruby (1.5.0)
stoplight (3.0.2) stoplight (3.0.2)
redlock (~> 1.0) redlock (~> 1.0)
stringio (3.0.8)
strong_migrations (0.8.0) strong_migrations (0.8.0)
activerecord (>= 5.2) activerecord (>= 5.2)
swd (1.3.0) swd (1.3.0)
@ -783,6 +811,7 @@ GEM
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
webrick (1.8.1)
websocket (1.2.10) websocket (1.2.10)
websocket-driver (0.7.6) websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
@ -878,7 +907,7 @@ DEPENDENCIES
rack-attack (~> 6.6) rack-attack (~> 6.6)
rack-cors (~> 2.0) rack-cors (~> 2.0)
rack-test (~> 2.1) rack-test (~> 2.1)
rails (~> 7.0) rails (~> 7.1.1)
rails-controller-testing (~> 1.0) rails-controller-testing (~> 1.0)
rails-i18n (~> 7.0) rails-i18n (~> 7.0)
rails-settings-cached (~> 0.6)! rails-settings-cached (~> 0.6)!
@ -929,4 +958,4 @@ RUBY VERSION
ruby 3.2.2p53 ruby 3.2.2p53
BUNDLED WITH BUNDLED WITH
2.4.13 2.4.20

View File

@ -49,7 +49,7 @@ module Admin
private private
def set_instance def set_instance
@instance = Instance.find(TagManager.instance.normalize_domain(params[:id]&.strip)) @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(params[:id]&.strip))
end end
def set_instances def set_instances

View File

@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
include ThemingConcern include ThemingConcern
include DatabaseHelper include DatabaseHelper
include AuthorizedFetchHelper include AuthorizedFetchHelper
include SelfDestructHelper
helper_method :current_account helper_method :current_account
helper_method :current_session helper_method :current_session
@ -41,6 +42,8 @@ class ApplicationController < ActionController::Base
service_unavailable service_unavailable
end end
before_action :check_self_destruct!
before_action :store_referrer, except: :raise_not_found, if: :devise_controller? before_action :store_referrer, except: :raise_not_found, if: :devise_controller?
before_action :require_functional!, if: :user_signed_in? before_action :require_functional!, if: :user_signed_in?
@ -169,6 +172,15 @@ class ApplicationController < ActionController::Base
end end
end end
def check_self_destruct!
return unless self_destruct?
respond_to do |format|
format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] }
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[410] }, status: code }
end
end
def set_cache_control_defaults def set_cache_control_defaults
response.cache_control.replace(private: true, no_store: true) response.cache_control.replace(private: true, no_store: true)
end end

View File

@ -8,6 +8,7 @@ class Auth::ChallengesController < ApplicationController
before_action :set_pack before_action :set_pack
before_action :authenticate_user! before_action :authenticate_user!
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
def create def create

View File

@ -13,6 +13,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha] before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha]
before_action :require_captcha_if_needed!, only: [:show] before_action :require_captcha_if_needed!, only: [:show]
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
def show def show

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :check_self_destruct!
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
def self.provides_callback_for(provider) def self.provides_callback_for(provider)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::PasswordsController < Devise::PasswordsController class Auth::PasswordsController < Devise::PasswordsController
skip_before_action :check_self_destruct!
before_action :check_validity_of_reset_password_token, only: :edit before_action :check_validity_of_reset_password_token, only: :edit
before_action :set_pack before_action :set_pack
before_action :set_body_classes before_action :set_body_classes

View File

@ -18,6 +18,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :require_rules_acceptance!, only: :new before_action :require_rules_acceptance!, only: :new
before_action :set_registration_form_time, only: :new before_action :set_registration_form_time, only: :new
skip_before_action :check_self_destruct!, only: [:edit, :update]
skip_before_action :require_functional!, only: [:edit, :update] skip_before_action :require_functional!, only: [:edit, :update]
def new def new

View File

@ -3,6 +3,7 @@
class Auth::SessionsController < Devise::SessionsController class Auth::SessionsController < Devise::SessionsController
layout 'auth' layout 'auth'
skip_before_action :check_self_destruct!
skip_before_action :require_no_authentication, only: [:create] skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional! skip_before_action :require_functional!
skip_before_action :update_user_sign_in skip_before_action :update_user_sign_in

View File

@ -3,6 +3,7 @@
class BackupsController < ApplicationController class BackupsController < ApplicationController
include RoutingHelper include RoutingHelper
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :authenticate_user! before_action :authenticate_user!

View File

@ -7,6 +7,7 @@ module ExportControllerConcern
before_action :authenticate_user! before_action :authenticate_user!
before_action :load_export before_action :load_export
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
end end

View File

@ -5,6 +5,7 @@ class Settings::ExportsController < Settings::BaseController
include Redisable include Redisable
include Lockable include Lockable
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
def show def show

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::LoginActivitiesController < Settings::BaseController class Settings::LoginActivitiesController < Settings::BaseController
skip_before_action :check_self_destruct!
skip_before_action :require_functional!
def index def index
@login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page]) @login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page])
end end

View File

@ -3,6 +3,7 @@
module Settings module Settings
module TwoFactorAuthentication module TwoFactorAuthentication
class WebauthnCredentialsController < BaseController class WebauthnCredentialsController < BaseController
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :require_otp_enabled before_action :require_otp_enabled

View File

@ -4,6 +4,7 @@ module Settings
class TwoFactorAuthenticationMethodsController < BaseController class TwoFactorAuthenticationMethodsController < BaseController
include ChallengableConcern include ChallengableConcern
skip_before_action :check_self_destruct!
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :require_challenge!, only: :disable before_action :require_challenge!, only: :disable

View File

@ -9,6 +9,10 @@ module FormattingHelper
TextFormatter.new(text, options).to_s TextFormatter.new(text, options).to_s
end end
def url_for_preview_card(preview_card)
preview_card.url
end
def extract_status_plain_text(status) def extract_status_plain_text(status)
PlainTextFormatter.new(status.text, status.local?).to_s PlainTextFormatter.new(status.text, status.local?).to_s
end end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module SelfDestructHelper
def self.self_destruct?
value = ENV.fetch('SELF_DESTRUCT', nil)
value.present? && Rails.application.message_verifier('self-destruct').verify(value) == ENV['LOCAL_DOMAIN']
rescue ActiveSupport::MessageVerifier::InvalidSignature
false
end
def self_destruct?
SelfDestructHelper.self_destruct?
end
end

View File

@ -0,0 +1,3 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default 'SvgrURL';
export const ReactComponent = 'div';

View File

@ -1,7 +1,7 @@
// This file will be loaded on admin pages, regardless of theme. // This file will be loaded on admin pages, regardless of theme.
import 'packs/public-path'; import 'packs/public-path';
import { delegate } from '@rails/ujs'; import Rails from '@rails/ujs';
import ready from '../mastodon/ready'; import ready from '../mastodon/ready';
@ -19,7 +19,7 @@ const setAnnouncementEndsAttributes = (target) => {
} }
}; };
delegate(document, 'input[type="datetime-local"]#announcement_starts_at', 'change', ({ target }) => { Rails.delegate(document, 'input[type="datetime-local"]#announcement_starts_at', 'change', ({ target }) => {
setAnnouncementEndsAttributes(target); setAnnouncementEndsAttributes(target);
}); });
@ -42,7 +42,7 @@ const hideSelectAll = () => {
hiddenField.value = '0'; hiddenField.value = '0';
}; };
delegate(document, '#batch_checkbox_all', 'change', ({ target }) => { Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
const selectAllMatchingElement = document.querySelector('.batch-table__select-all'); const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
[].forEach.call(document.querySelectorAll(batchCheckboxClassName), (content) => { [].forEach.call(document.querySelectorAll(batchCheckboxClassName), (content) => {
@ -58,7 +58,7 @@ delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
} }
}); });
delegate(document, '.batch-table__select-all button', 'click', () => { Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
const hiddenField = document.querySelector('#select_all_matching'); const hiddenField = document.querySelector('#select_all_matching');
const active = hiddenField.value === '1'; const active = hiddenField.value === '1';
const selectedMsg = document.querySelector('.batch-table__select-all .selected'); const selectedMsg = document.querySelector('.batch-table__select-all .selected');
@ -75,7 +75,7 @@ delegate(document, '.batch-table__select-all button', 'click', () => {
} }
}); });
delegate(document, batchCheckboxClassName, 'change', () => { Rails.delegate(document, batchCheckboxClassName, 'change', () => {
const checkAllElement = document.querySelector('#batch_checkbox_all'); const checkAllElement = document.querySelector('#batch_checkbox_all');
const selectAllMatchingElement = document.querySelector('.batch-table__select-all'); const selectAllMatchingElement = document.querySelector('.batch-table__select-all');
@ -93,19 +93,19 @@ delegate(document, batchCheckboxClassName, 'change', () => {
} }
}); });
delegate(document, '.media-spoiler-show-button', 'click', () => { Rails.delegate(document, '.media-spoiler-show-button', 'click', () => {
[].forEach.call(document.querySelectorAll('button.media-spoiler'), (element) => { [].forEach.call(document.querySelectorAll('button.media-spoiler'), (element) => {
element.click(); element.click();
}); });
}); });
delegate(document, '.media-spoiler-hide-button', 'click', () => { Rails.delegate(document, '.media-spoiler-hide-button', 'click', () => {
[].forEach.call(document.querySelectorAll('.spoiler-button.spoiler-button--visible button'), (element) => { [].forEach.call(document.querySelectorAll('.spoiler-button.spoiler-button--visible button'), (element) => {
element.click(); element.click();
}); });
}); });
delegate(document, '.filter-subset--with-select select', 'change', ({ target }) => { Rails.delegate(document, '.filter-subset--with-select select', 'change', ({ target }) => {
target.form.submit(); target.form.submit();
}); });
@ -122,7 +122,7 @@ const onDomainBlockSeverityChange = (target) => {
} }
}; };
delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target)); Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target));
const onEnableBootstrapTimelineAccountsChange = (target) => { const onEnableBootstrapTimelineAccountsChange = (target) => {
const bootstrapTimelineAccountsField = document.querySelector('#form_admin_settings_bootstrap_timeline_accounts'); const bootstrapTimelineAccountsField = document.querySelector('#form_admin_settings_bootstrap_timeline_accounts');
@ -139,7 +139,7 @@ const onEnableBootstrapTimelineAccountsChange = (target) => {
} }
}; };
delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'change', ({ target }) => onEnableBootstrapTimelineAccountsChange(target)); Rails.delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'change', ({ target }) => onEnableBootstrapTimelineAccountsChange(target));
const onChangeRegistrationMode = (target) => { const onChangeRegistrationMode = (target) => {
const enabled = target.value === 'approved'; const enabled = target.value === 'approved';
@ -176,7 +176,7 @@ const convertLocalDatetimeToUTC = (value) => {
return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6); return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
}; };
delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target)); Rails.delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target));
ready(() => { ready(() => {
const domainBlockSeverityInput = document.getElementById('domain_block_severity'); const domainBlockSeverityInput = document.getElementById('domain_block_severity');
@ -213,7 +213,7 @@ ready(() => {
} }
}); });
delegate(document, 'form', 'submit', ({ target }) => { Rails.delegate(document, 'form', 'submit', ({ target }) => {
[].forEach.call(target.querySelectorAll('input[type="datetime-local"]'), element => { [].forEach.call(target.querySelectorAll('input[type="datetime-local"]'), element => {
if (element.value && element.validity.valid) { if (element.value && element.validity.valid) {
element.value = convertLocalDatetimeToUTC(element.value); element.value = convertLocalDatetimeToUTC(element.value);

View File

@ -1,9 +1,9 @@
// This file will be loaded on settings pages, regardless of theme. // This file will be loaded on settings pages, regardless of theme.
import 'packs/public-path'; import 'packs/public-path';
import { delegate } from '@rails/ujs'; import Rails from '@rails/ujs';
delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => { Rails.delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
const avatar = document.getElementById(target.id + '-preview'); const avatar = document.getElementById(target.id + '-preview');
const [file] = target.files || []; const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
@ -11,13 +11,13 @@ delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
avatar.src = url; avatar.src = url;
}); });
delegate(document, '.input-copy input', 'click', ({ target }) => { Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
target.focus(); target.focus();
target.select(); target.select();
target.setSelectionRange(0, target.value.length); target.setSelectionRange(0, target.value.length);
}); });
delegate(document, '.input-copy button', 'click', ({ target }) => { Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
const input = target.parentNode.querySelector('.input-copy__wrapper input'); const input = target.parentNode.querySelector('.input-copy__wrapper input');
const oldReadOnly = input.readonly; const oldReadOnly = input.readonly;

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; import { unicodeMapping } from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
import { assetHost } from 'flavours/glitch/utils/config'; import { assetHost } from 'flavours/glitch/utils/config';
export default class AutosuggestEmoji extends PureComponent { export default class AutosuggestEmoji extends PureComponent {

View File

@ -1,53 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import classNames from 'classnames';
export default class Button extends PureComponent {
static propTypes = {
text: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
block: PropTypes.bool,
secondary: PropTypes.bool,
className: PropTypes.string,
title: PropTypes.string,
children: PropTypes.node,
};
handleClick = (e) => {
if (!this.props.disabled) {
this.props.onClick(e);
}
};
setRef = (c) => {
this.node = c;
};
focus() {
this.node.focus();
}
render () {
let attrs = {
className: classNames('button', this.props.className, {
'button-secondary': this.props.secondary,
'button--block': this.props.block,
}),
disabled: this.props.disabled,
onClick: this.handleClick,
ref: this.setRef,
};
if (this.props.title) attrs.title = this.props.title;
return (
<button {...attrs}>
{this.props.text || this.props.children}
</button>
);
}
}

View File

@ -0,0 +1,58 @@
import { useCallback } from 'react';
import classNames from 'classnames';
interface BaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
block?: boolean;
secondary?: boolean;
text?: JSX.Element;
}
interface PropsWithChildren extends BaseProps {
text?: never;
}
interface PropsWithText extends BaseProps {
text: JSX.Element;
children: never;
}
type Props = PropsWithText | PropsWithChildren;
export const Button: React.FC<Props> = ({
text,
type = 'button',
onClick,
disabled,
block,
secondary,
className,
title,
children,
...props
}) => {
const handleClick = useCallback<React.MouseEventHandler<HTMLButtonElement>>(
(e) => {
if (!disabled && onClick) {
onClick(e);
}
},
[disabled, onClick],
);
return (
<button
className={classNames('button', className, {
'button-secondary': secondary,
'button--block': block,
})}
disabled={disabled}
onClick={handleClick}
title={title}
type={type}
{...props}
>
{text ?? children}
</button>
);
};

View File

@ -24,12 +24,6 @@ export default class Column extends PureComponent {
scrollable = document.scrollingElement; scrollable = document.scrollingElement;
} else { } else {
scrollable = this.node.querySelector('.scrollable'); scrollable = this.node.querySelector('.scrollable');
// Some columns have nested `.scrollable` containers, with the outer one
// being a wrapper while the actual scrollable content is deeper.
if (scrollable.classList.contains('scrollable--flex')) {
scrollable = scrollable?.querySelector('.scrollable') || scrollable;
}
} }
if (!scrollable) { if (!scrollable) {

View File

@ -4,26 +4,25 @@ import { createPortal } from 'react-dom';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Icon } from 'flavours/glitch/components/icon'; import { withRouter } from 'react-router-dom';
import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
export default class ColumnBackButton extends PureComponent { export class ColumnBackButton extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
...WithRouterPropTypes,
}; };
handleClick = () => { handleClick = () => {
const { router } = this.context; const { history } = this.props;
if (router.history.location?.state?.fromMastodon) { if (history.location?.state?.fromMastodon) {
router.history.goBack(); history.goBack();
} else { } else {
router.history.push('/'); history.push('/');
} }
}; };
@ -57,3 +56,5 @@ export default class ColumnBackButton extends PureComponent {
} }
} }
export default withRouter(ColumnBackButton);

View File

@ -1,25 +1,27 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
export default class ColumnBackButtonSlim extends PureComponent { class ColumnBackButtonSlim extends PureComponent {
static contextTypes = { static propTypes = {
router: PropTypes.object, ...WithRouterPropTypes,
}; };
handleClick = () => { handleClick = () => {
const { router } = this.context; const { location, history } = this.props;
// Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201
// When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location
if (router.route.location.key) { if (location.key) {
router.history.goBack(); history.goBack();
} else { } else {
router.history.push('/'); history.push('/');
} }
}; };
@ -33,5 +35,6 @@ export default class ColumnBackButtonSlim extends PureComponent {
</div> </div>
); );
} }
} }
export default withRouter(ColumnBackButtonSlim);

View File

@ -5,8 +5,10 @@ import { createPortal } from 'react-dom';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const messages = defineMessages({ const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
@ -18,7 +20,6 @@ const messages = defineMessages({
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object, identity: PropTypes.object,
}; };
@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent {
onClick: PropTypes.func, onClick: PropTypes.func,
appendContent: PropTypes.node, appendContent: PropTypes.node,
collapseIssues: PropTypes.bool, collapseIssues: PropTypes.bool,
...WithRouterPropTypes,
}; };
state = { state = {
@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent {
}; };
handleBackClick = () => { handleBackClick = () => {
const { router } = this.context; const { history } = this.props;
if (router.history.location?.state?.fromMastodon) { if (history.location?.state?.fromMastodon) {
router.history.goBack(); history.goBack();
} else { } else {
router.history.push('/'); history.push('/');
} }
}; };
@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent {
handlePin = () => { handlePin = () => {
if (!this.props.pinned) { if (!this.props.pinned) {
this.context.router.history.replace('/'); this.props.history.replace('/');
} }
this.props.onPin(); this.props.onPin();
}; };
render () { render () {
const { router } = this.context; const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', { const wrapperClassName = classNames('column-header__wrapper', {
@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
} }
if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
backButton = ( backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'> <button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth /> <Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
@ -215,4 +216,4 @@ class ColumnHeader extends PureComponent {
} }
export default injectIntl(ColumnHeader); export default injectIntl(withRouter(ColumnHeader));

View File

@ -2,13 +2,16 @@ import PropTypes from 'prop-types';
import { PureComponent, cloneElement, Children } from 'react'; import { PureComponent, cloneElement, Children } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
import { CircularProgress } from "./circular_progress"; import { CircularProgress } from 'flavours/glitch/components/circular_progress';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
@ -16,10 +19,6 @@ let id = 0;
class DropdownMenu extends PureComponent { class DropdownMenu extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired, items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
loading: PropTypes.bool, loading: PropTypes.bool,
@ -159,11 +158,7 @@ class DropdownMenu extends PureComponent {
} }
export default class Dropdown extends PureComponent { class Dropdown extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
@ -183,6 +178,7 @@ export default class Dropdown extends PureComponent {
renderItem: PropTypes.func, renderItem: PropTypes.func,
renderHeader: PropTypes.func, renderHeader: PropTypes.func,
onItemClick: PropTypes.func, onItemClick: PropTypes.func,
...WithRouterPropTypes
}; };
static defaultProps = { static defaultProps = {
@ -250,7 +246,7 @@ export default class Dropdown extends PureComponent {
item.action(); item.action();
} else if (item && item.to) { } else if (item && item.to) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(item.to); this.props.history.push(item.to);
} }
}; };
@ -338,3 +334,5 @@ export default class Dropdown extends PureComponent {
} }
} }
export default withRouter(Dropdown);

View File

@ -2,14 +2,13 @@ import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import 'wicg-inert'; import 'wicg-inert';
import { multiply } from 'color-blend'; import { multiply } from 'color-blend';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
export default class ModalRoot extends PureComponent { import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router';
static contextTypes = { class ModalRoot extends PureComponent {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
@ -21,6 +20,7 @@ export default class ModalRoot extends PureComponent {
}), }),
noEsc: PropTypes.bool, noEsc: PropTypes.bool,
ignoreFocus: PropTypes.bool, ignoreFocus: PropTypes.bool,
...WithOptionalRouterPropTypes,
}; };
activeElement = this.props.children ? document.activeElement : null; activeElement = this.props.children ? document.activeElement : null;
@ -56,7 +56,7 @@ export default class ModalRoot extends PureComponent {
componentDidMount () { componentDidMount () {
window.addEventListener('keyup', this.handleKeyUp, false); window.addEventListener('keyup', this.handleKeyUp, false);
window.addEventListener('keydown', this.handleKeyDown, false); window.addEventListener('keydown', this.handleKeyDown, false);
this.history = this.context.router ? this.context.router.history : createBrowserHistory(); this.history = this.props.history || createBrowserHistory();
if (this.props.children) { if (this.props.children) {
this._handleModalOpen(); this._handleModalOpen();
@ -160,3 +160,5 @@ export default class ModalRoot extends PureComponent {
} }
} }
export default withOptionalRouter(ModalRoot);

View File

@ -1,36 +0,0 @@
import { PureComponent } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import AccountNavigation from 'flavours/glitch/features/account/navigation';
import Trends from 'flavours/glitch/features/getting_started/containers/trends_container';
import { showTrends } from 'flavours/glitch/initial_state';
const DefaultNavigation = () => (
showTrends ? (
<>
<div className='flex-spacer' />
<Trends />
</>
) : null
);
class NavigationPortal extends PureComponent {
render () {
return (
<Switch>
<Route path='/@:acct' exact component={AccountNavigation} />
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
<Route path='/@:acct/followers' exact component={AccountNavigation} />
<Route path='/@:acct/following' exact component={AccountNavigation} />
<Route path='/@:acct/media' exact component={AccountNavigation} />
<Route component={DefaultNavigation} />
</Switch>
);
}
}
export default withRouter(NavigationPortal);

View File

@ -0,0 +1,25 @@
import { Switch, Route } from 'react-router-dom';
import AccountNavigation from 'flavours/glitch/features/account/navigation';
import Trends from 'flavours/glitch/features/getting_started/containers/trends_container';
import { showTrends } from 'flavours/glitch/initial_state';
const DefaultNavigation: React.FC = () =>
showTrends ? (
<>
<div className='flex-spacer' />
<Trends />
</>
) : null;
export const NavigationPortal: React.FC = () => (
<Switch>
<Route path='/@:acct' exact component={AccountNavigation} />
<Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} />
<Route path='/@:acct/with_replies' exact component={AccountNavigation} />
<Route path='/@:acct/followers' exact component={AccountNavigation} />
<Route path='/@:acct/following' exact component={AccountNavigation} />
<Route path='/@:acct/media' exact component={AccountNavigation} />
<Route component={DefaultNavigation} />
</Switch>
);

View File

@ -1,11 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
export default class Permalink extends PureComponent { import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
static contextTypes = { class Permalink extends PureComponent {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
className: PropTypes.string, className: PropTypes.string,
@ -13,6 +11,7 @@ export default class Permalink extends PureComponent {
to: PropTypes.string.isRequired, to: PropTypes.string.isRequired,
children: PropTypes.node, children: PropTypes.node,
onInterceptClick: PropTypes.func, onInterceptClick: PropTypes.func,
...WithOptionalRouterPropTypes,
}; };
handleClick = (e) => { handleClick = (e) => {
@ -22,9 +21,9 @@ export default class Permalink extends PureComponent {
return; return;
} }
if (this.context.router) { if (this.props.history) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(this.props.to); this.props.history.push(this.props.to);
} }
} }
}; };
@ -47,3 +46,5 @@ export default class Permalink extends PureComponent {
} }
} }
export default withOptionalRouter(Permalink);

View File

@ -1,15 +1,22 @@
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import React from 'react'; import React from 'react';
import { createBrowserHistory } from 'history';
import { Router as OriginalRouter } from 'react-router'; import { Router as OriginalRouter } from 'react-router';
import type {
LocationDescriptor,
LocationDescriptorObject,
Path,
} from 'history';
import { createBrowserHistory } from 'history';
import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { layoutFromWindow } from 'flavours/glitch/is_mobile';
interface MastodonLocationState { interface MastodonLocationState {
fromMastodon?: boolean; fromMastodon?: boolean;
mastodonModalKey?: string; mastodonModalKey?: string;
} }
type HistoryPath = Path | LocationDescriptor<MastodonLocationState>;
const browserHistory = createBrowserHistory< const browserHistory = createBrowserHistory<
MastodonLocationState | undefined MastodonLocationState | undefined
@ -17,28 +24,55 @@ const browserHistory = createBrowserHistory<
const originalPush = browserHistory.push.bind(browserHistory); const originalPush = browserHistory.push.bind(browserHistory);
const originalReplace = browserHistory.replace.bind(browserHistory); const originalReplace = browserHistory.replace.bind(browserHistory);
browserHistory.push = (path: string, state?: MastodonLocationState) => { function normalizePath(
state = state ?? {}; path: HistoryPath,
state.fromMastodon = true; state?: MastodonLocationState,
): LocationDescriptorObject<MastodonLocationState> {
const location = typeof path === 'string' ? { pathname: path } : { ...path };
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { if (location.state === undefined && state !== undefined) {
originalPush(`/deck${path}`, state); location.state = state;
} else { } else if (
originalPush(path, state); location.state !== undefined &&
state !== undefined &&
process.env.NODE_ENV === 'development'
) {
// eslint-disable-next-line no-console
console.log(
'You should avoid providing a 2nd state argument to push when the 1st argument is a location-like object that already has state; it is ignored',
);
} }
if (
layoutFromWindow() === 'multi-column' &&
!location.pathname?.startsWith('/deck')
) {
location.pathname = `/deck${location.pathname}`;
}
return location;
}
browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => {
const location = normalizePath(path, state);
location.state = location.state ?? {};
location.state.fromMastodon = true;
originalPush(location);
}; };
browserHistory.replace = (path: string, state?: MastodonLocationState) => { browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => {
const location = normalizePath(path, state);
if (!location.pathname) return;
if (browserHistory.location.state?.fromMastodon) { if (browserHistory.location.state?.fromMastodon) {
state = state ?? {}; location.state = location.state ?? {};
state.fromMastodon = true; location.state.fromMastodon = true;
} }
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { originalReplace(location);
originalReplace(`/deck${path}`, state);
} else {
originalReplace(path, state);
}
}; };
export const Router: React.FC<PropsWithChildren> = ({ children }) => { export const Router: React.FC<PropsWithChildren> = ({ children }) => {

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import { Children, cloneElement, PureComponent } from 'react'; import { Children, cloneElement, PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { useLocation } from 'react-router-dom';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -34,11 +35,32 @@ const mapStateToProps = (state, { scrollKey }) => {
}; };
}; };
class ScrollableList extends PureComponent { // This component only exists to be able to call useLocation()
const IOArticleContainerWrapper = ({id, index, listLength, intersectionObserverWrapper, trackScroll, scrollKey, children}) => {
const location = useLocation();
static contextTypes = { return (<IntersectionObserverArticleContainer
router: PropTypes.object, id={id}
}; index={index}
listLength={listLength}
intersectionObserverWrapper={intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${location.key}:${scrollKey}` : null}
>
{children}
</IntersectionObserverArticleContainer>);
};
IOArticleContainerWrapper.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
scrollKey: PropTypes.string.isRequired,
intersectionObserverWrapper: PropTypes.object.isRequired,
trackScroll: PropTypes.bool.isRequired,
children: PropTypes.node,
};
class ScrollableList extends PureComponent {
static propTypes = { static propTypes = {
scrollKey: PropTypes.string.isRequired, scrollKey: PropTypes.string.isRequired,
@ -331,13 +353,14 @@ class ScrollableList extends PureComponent {
{loadPending} {loadPending}
{Children.map(this.props.children, (child, index) => ( {Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticleContainer <IOArticleContainerWrapper
key={child.key} key={child.key}
id={child.key} id={child.key}
index={index} index={index}
listLength={childrenCount} listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper} intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null} trackScroll={trackScroll}
scrollKey={scrollKey}
> >
{cloneElement(child, { {cloneElement(child, {
getScrollPosition: this.getScrollPosition, getScrollPosition: this.getScrollPosition,
@ -345,7 +368,7 @@ class ScrollableList extends PureComponent {
cachedMediaWidth: this.state.cachedMediaWidth, cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth, cacheMediaWidth: this.cacheMediaWidth,
})} })}
</IntersectionObserverArticleContainer> </IOArticleContainerWrapper>
))} ))}
{loadMore} {loadMore}

View File

@ -14,6 +14,7 @@ import PollContainer from 'flavours/glitch/containers/poll_container';
import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import { displayMedia } from 'flavours/glitch/initial_state'; import { displayMedia } from 'flavours/glitch/initial_state';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
import Card from '../features/status/components/card'; import Card from '../features/status/components/card';
import Bundle from '../features/ui/components/bundle'; import Bundle from '../features/ui/components/bundle';
@ -67,10 +68,6 @@ export const defaultMediaVisibility = (status, settings) => {
class Status extends ImmutablePureComponent { class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
containerId: PropTypes.string, containerId: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
@ -117,6 +114,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool, inUse: PropTypes.bool,
available: PropTypes.bool, available: PropTypes.bool,
}), }),
...WithOptionalRouterPropTypes,
}; };
state = { state = {
@ -356,10 +354,9 @@ class Status extends ImmutablePureComponent {
// Otherwise, we open the url handed to us in `destination`, if // Otherwise, we open the url handed to us in `destination`, if
// applicable. // applicable.
parseClick = (e, destination) => { parseClick = (e, destination) => {
const { router } = this.context; const { status, history } = this.props;
const { status } = this.props;
const { isCollapsed } = this.state; const { isCollapsed } = this.state;
if (!router) return; if (!history) return;
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
if (isCollapsed) this.setCollapsed(false); if (isCollapsed) this.setCollapsed(false);
@ -377,7 +374,7 @@ class Status extends ImmutablePureComponent {
status.getIn(['reblog', 'id'], status.get('id')) status.getIn(['reblog', 'id'], status.get('id'))
}`; }`;
} }
router.history.push(destination); history.push(destination);
} }
e.preventDefault(); e.preventDefault();
} }
@ -431,7 +428,7 @@ class Status extends ImmutablePureComponent {
handleHotkeyReply = e => { handleHotkeyReply = e => {
e.preventDefault(); e.preventDefault();
this.props.onReply(this.props.status, this.context.router.history); this.props.onReply(this.props.status, this.props.history);
}; };
handleHotkeyFavourite = (e) => { handleHotkeyFavourite = (e) => {
@ -448,16 +445,16 @@ class Status extends ImmutablePureComponent {
handleHotkeyMention = e => { handleHotkeyMention = e => {
e.preventDefault(); e.preventDefault();
this.props.onMention(this.props.status.get('account'), this.context.router.history); this.props.onMention(this.props.status.get('account'), this.props.history);
}; };
handleHotkeyOpen = () => { handleHotkeyOpen = () => {
const status = this.props.status; const status = this.props.status;
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
}; };
handleHotkeyOpenProfile = () => { handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}; };
handleHotkeyMoveUp = e => { handleHotkeyMoveUp = e => {
@ -514,7 +511,6 @@ class Status extends ImmutablePureComponent {
parseClick, parseClick,
setCollapsed, setCollapsed,
} = this; } = this;
const { router } = this.context;
const { const {
intl, intl,
status, status,
@ -533,6 +529,7 @@ class Status extends ImmutablePureComponent {
previousId, previousId,
nextInReplyToId, nextInReplyToId,
rootId, rootId,
history,
...other ...other
} = this.props; } = this.props;
const { isCollapsed } = this.state; const { isCollapsed } = this.state;
@ -828,7 +825,7 @@ class Status extends ImmutablePureComponent {
onExpandedToggle={this.handleExpandedToggle} onExpandedToggle={this.handleExpandedToggle}
onTranslate={this.handleTranslate} onTranslate={this.handleTranslate}
parseClick={parseClick} parseClick={parseClick}
disabled={!router} disabled={!history}
tagLinks={settings.get('tag_misleading_links')} tagLinks={settings.get('tag_misleading_links')}
rewriteMentions={settings.get('rewrite_mentions')} rewriteMentions={settings.get('rewrite_mentions')}
/> />
@ -854,4 +851,4 @@ class Status extends ImmutablePureComponent {
} }
export default injectIntl(Status); export default withOptionalRouter(injectIntl(Status));

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -11,6 +12,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
@ -53,7 +55,6 @@ const messages = defineMessages({
class StatusActionBar extends ImmutablePureComponent { class StatusActionBar extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object, identity: PropTypes.object,
}; };
@ -80,6 +81,7 @@ class StatusActionBar extends ImmutablePureComponent {
showReplyCount: PropTypes.bool, showReplyCount: PropTypes.bool,
scrollKey: PropTypes.string, scrollKey: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
...WithRouterPropTypes,
}; };
// Avoid checking props that are functions (and whose equality will always // Avoid checking props that are functions (and whose equality will always
@ -95,7 +97,7 @@ class StatusActionBar extends ImmutablePureComponent {
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
if (signedIn) { if (signedIn) {
this.props.onReply(this.props.status, this.context.router.history); this.props.onReply(this.props.status, this.props.history);
} else { } else {
this.props.onInteractionModal('reply', this.props.status); this.props.onInteractionModal('reply', this.props.status);
} }
@ -132,15 +134,15 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleDeleteClick = () => { handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history); this.props.onDelete(this.props.status, this.props.history);
}; };
handleRedraftClick = () => { handleRedraftClick = () => {
this.props.onDelete(this.props.status, this.context.router.history, true); this.props.onDelete(this.props.status, this.props.history, true);
}; };
handleEditClick = () => { handleEditClick = () => {
this.props.onEdit(this.props.status, this.context.router.history); this.props.onEdit(this.props.status, this.props.history);
}; };
handlePinClick = () => { handlePinClick = () => {
@ -148,11 +150,11 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleMentionClick = () => { handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history); this.props.onMention(this.props.status.get('account'), this.props.history);
}; };
handleDirectClick = () => { handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history); this.props.onDirect(this.props.status.get('account'), this.props.history);
}; };
handleMuteClick = () => { handleMuteClick = () => {
@ -164,12 +166,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleOpen = () => { handleOpen = () => {
let state = { ...this.context.router.history.location.state }; this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
if (state.mastodonModalKey) {
this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
} else {
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
}
}; };
handleEmbed = () => { handleEmbed = () => {
@ -340,4 +337,4 @@ class StatusActionBar extends ImmutablePureComponent {
} }
export default injectIntl(StatusActionBar); export default withRouter(injectIntl(StatusActionBar));

View File

@ -4,6 +4,7 @@ import { PureComponent } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -131,6 +132,10 @@ class StatusContent extends PureComponent {
rewriteMentions: PropTypes.string, rewriteMentions: PropTypes.string,
languages: ImmutablePropTypes.map, languages: ImmutablePropTypes.map,
intl: PropTypes.object, intl: PropTypes.object,
// from react-router
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}; };
static defaultProps = { static defaultProps = {
@ -472,4 +477,4 @@ class StatusContent extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(StatusContent)); export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));

View File

@ -14,10 +14,6 @@ const messages = defineMessages({
class FeaturedTags extends ImmutablePureComponent { class FeaturedTags extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
featuredTags: ImmutablePropTypes.list, featuredTags: ImmutablePropTypes.list,

View File

@ -4,18 +4,20 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { Avatar } from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state'; import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import AccountNoteContainer from '../containers/account_note_container'; import AccountNoteContainer from '../containers/account_note_container';
import FollowRequestNoteContainer from '../containers/follow_request_note_container'; import FollowRequestNoteContainer from '../containers/follow_request_note_container';
@ -81,10 +83,6 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
identity_props: ImmutablePropTypes.list, identity_props: ImmutablePropTypes.list,
@ -107,6 +105,11 @@ class Header extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired, domain: PropTypes.string.isRequired,
hidden: PropTypes.bool, hidden: PropTypes.bool,
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
}; };
openEditProfile = () => { openEditProfile = () => {
@ -406,4 +409,4 @@ class Header extends ImmutablePureComponent {
} }
export default injectIntl(Header); export default withRouter(injectIntl(Header));

View File

@ -2,18 +2,19 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom'; import { NavLink, withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ActionBar from 'flavours/glitch/features/account/components/action_bar'; import ActionBar from 'flavours/glitch/features/account/components/action_bar';
import InnerHeader from 'flavours/glitch/features/account/components/header'; import InnerHeader from 'flavours/glitch/features/account/components/header';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import MemorialNote from './memorial_note'; import MemorialNote from './memorial_note';
import MovedNote from './moved_note'; import MovedNote from './moved_note';
export default class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
@ -34,10 +35,7 @@ export default class Header extends ImmutablePureComponent {
hideTabs: PropTypes.bool, hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired, domain: PropTypes.string.isRequired,
hidden: PropTypes.bool, hidden: PropTypes.bool,
}; ...WithRouterPropTypes,
static contextTypes = {
router: PropTypes.object,
}; };
handleFollow = () => { handleFollow = () => {
@ -49,11 +47,11 @@ export default class Header extends ImmutablePureComponent {
}; };
handleMention = () => { handleMention = () => {
this.props.onMention(this.props.account, this.context.router.history); this.props.onMention(this.props.account, this.props.history);
}; };
handleDirect = () => { handleDirect = () => {
this.props.onDirect(this.props.account, this.context.router.history); this.props.onDirect(this.props.account, this.props.history);
}; };
handleReport = () => { handleReport = () => {
@ -162,3 +160,5 @@ export default class Header extends ImmutablePureComponent {
} }
} }
export default withRouter(Header);

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { revealAccount } from 'flavours/glitch/actions/accounts'; import { revealAccount } from 'flavours/glitch/actions/accounts';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
const mapDispatchToProps = (dispatch, { accountId }) => ({ const mapDispatchToProps = (dispatch, { accountId }) => ({

View File

@ -1,30 +1,28 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import AvatarOverlay from '../../../components/avatar_overlay'; import AvatarOverlay from '../../../components/avatar_overlay';
import { DisplayName } from '../../../components/display_name'; import { DisplayName } from '../../../components/display_name';
export default class MovedNote extends ImmutablePureComponent { class MovedNote extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
from: ImmutablePropTypes.map.isRequired, from: ImmutablePropTypes.map.isRequired,
to: ImmutablePropTypes.map.isRequired, to: ImmutablePropTypes.map.isRequired,
...WithRouterPropTypes,
}; };
handleAccountClick = e => { handleAccountClick = e => {
if (e.button === 0) { if (e.button === 0) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(`/@${this.props.to.get('acct')}`); this.props.history.push(`/@${this.props.to.get('acct')}`);
} }
e.stopPropagation(); e.stopPropagation();
@ -50,3 +48,5 @@ export default class MovedNote extends ImmutablePureComponent {
} }
} }
export default withRouter(MovedNote);

View File

@ -44,7 +44,6 @@ class CommunityTimeline extends PureComponent {
}; };
static contextTypes = { static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object, identity: PropTypes.object,
}; };

View File

@ -9,6 +9,7 @@ import { length } from 'stringz';
import { maxChars } from 'flavours/glitch/initial_state'; import { maxChars } from 'flavours/glitch/initial_state';
import { isMobile } from 'flavours/glitch/is_mobile'; import { isMobile } from 'flavours/glitch/is_mobile';
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router';
import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestTextarea from '../../../components/autosuggest_textarea';
@ -38,11 +39,6 @@ const messages = defineMessages({
}); });
class ComposeForm extends ImmutablePureComponent { class ComposeForm extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
text: PropTypes.string, text: PropTypes.string,
@ -70,7 +66,6 @@ class ComposeForm extends ImmutablePureComponent {
isInReply: PropTypes.bool, isInReply: PropTypes.bool,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
lang: PropTypes.string, lang: PropTypes.string,
advancedOptions: ImmutablePropTypes.map, advancedOptions: ImmutablePropTypes.map,
layout: PropTypes.string, layout: PropTypes.string,
media: ImmutablePropTypes.list, media: ImmutablePropTypes.list,
@ -82,6 +77,7 @@ class ComposeForm extends ImmutablePureComponent {
onChangeSpoilerness: PropTypes.func, onChangeSpoilerness: PropTypes.func,
onChangeVisibility: PropTypes.func, onChangeVisibility: PropTypes.func,
onMediaDescriptionConfirm: PropTypes.func, onMediaDescriptionConfirm: PropTypes.func,
...WithOptionalRouterPropTypes
}; };
static defaultProps = { static defaultProps = {
@ -129,12 +125,12 @@ class ComposeForm extends ImmutablePureComponent {
// Submit unless there are media with missing descriptions // Submit unless there are media with missing descriptions
if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) {
const firstWithoutDescription = media.find(item => !item.get('description')); const firstWithoutDescription = media.find(item => !item.get('description'));
onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null, firstWithoutDescription.get('id'), overriddenVisibility); onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility);
} else if (onSubmit) { } else if (onSubmit) {
if (onChangeVisibility && overriddenVisibility) { if (onChangeVisibility && overriddenVisibility) {
onChangeVisibility(overriddenVisibility); onChangeVisibility(overriddenVisibility);
} }
onSubmit(this.context.router ? this.context.router.history : null); onSubmit(this.props.history || null);
} }
}; };
@ -390,4 +386,4 @@ class ComposeForm extends ImmutablePureComponent {
} }
export default injectIntl(ComposeForm); export default withOptionalRouter(injectIntl(ComposeForm));

View File

@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz'; import { length } from 'stringz';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { maxChars } from 'flavours/glitch/initial_state'; import { maxChars } from 'flavours/glitch/initial_state';

View File

@ -1,4 +1,3 @@
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
@ -6,18 +5,13 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
// Components.
import AttachmentList from 'flavours/glitch/components/attachment_list'; import AttachmentList from 'flavours/glitch/components/attachment_list';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import AccountContainer from 'flavours/glitch/containers/account_container'; import AccountContainer from 'flavours/glitch/containers/account_container';
// Messages.
const messages = defineMessages({
cancel: {
defaultMessage: 'Cancel',
id: 'reply_indicator.cancel',
},
});
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
});
class ReplyIndicator extends ImmutablePureComponent { class ReplyIndicator extends ImmutablePureComponent {

View File

@ -4,14 +4,14 @@ import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { focusRoot } from 'flavours/glitch/utils/dom_helpers';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const messages = defineMessages({ const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@ -32,7 +32,6 @@ const labelForRecentSearch = search => {
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired, identity: PropTypes.object.isRequired,
}; };
@ -50,6 +49,7 @@ class Search extends PureComponent {
openInRoute: PropTypes.bool, openInRoute: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
...WithRouterPropTypes,
}; };
state = { state = {
@ -122,8 +122,7 @@ class Search extends PureComponent {
switch(e.key) { switch(e.key) {
case 'Escape': case 'Escape':
e.preventDefault(); e.preventDefault();
this._unfocus();
focusRoot();
break; break;
case 'ArrowDown': case 'ArrowDown':
@ -170,32 +169,29 @@ class Search extends PureComponent {
}; };
handleHashtagClick = () => { handleHashtagClick = () => {
const { router } = this.context; const { value, onClickSearchResult, history } = this.props;
const { value, onClickSearchResult } = this.props;
const query = value.trim().replace(/^#/, ''); const query = value.trim().replace(/^#/, '');
router.history.push(`/tags/${query}`); history.push(`/tags/${query}`);
onClickSearchResult(query, 'hashtag'); onClickSearchResult(query, 'hashtag');
this._unfocus(); this._unfocus();
}; };
handleAccountClick = () => { handleAccountClick = () => {
const { router } = this.context; const { value, onClickSearchResult, history } = this.props;
const { value, onClickSearchResult } = this.props;
const query = value.trim().replace(/^@/, ''); const query = value.trim().replace(/^@/, '');
router.history.push(`/@${query}`); history.push(`/@${query}`);
onClickSearchResult(query, 'account'); onClickSearchResult(query, 'account');
this._unfocus(); this._unfocus();
}; };
handleURLClick = () => { handleURLClick = () => {
const { router } = this.context; const { onOpenURL, history } = this.props;
const { onOpenURL } = this.props;
onOpenURL(router.history); onOpenURL(history);
this._unfocus(); this._unfocus();
}; };
@ -208,13 +204,12 @@ class Search extends PureComponent {
}; };
handleRecentSearchClick = search => { handleRecentSearchClick = search => {
const { onChange } = this.props; const { onChange, history } = this.props;
const { router } = this.context;
if (search.get('type') === 'account') { if (search.get('type') === 'account') {
router.history.push(`/@${search.get('q')}`); history.push(`/@${search.get('q')}`);
} else if (search.get('type') === 'hashtag') { } else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`); history.push(`/tags/${search.get('q')}`);
} else { } else {
onChange(search.get('q')); onChange(search.get('q'));
this._submit(search.get('type')); this._submit(search.get('type'));
@ -246,8 +241,7 @@ class Search extends PureComponent {
} }
_submit (type) { _submit (type) {
const { onSubmit, openInRoute, value, onClickSearchResult } = this.props; const { onSubmit, openInRoute, value, onClickSearchResult, history } = this.props;
const { router } = this.context;
onSubmit(type); onSubmit(type);
@ -256,7 +250,7 @@ class Search extends PureComponent {
} }
if (openInRoute) { if (openInRoute) {
router.history.push('/search'); history.push('/search');
} }
this._unfocus(); this._unfocus();
@ -403,4 +397,4 @@ class Search extends PureComponent {
} }
export default injectIntl(Search); export default withRouter(injectIntl(Search));

View File

@ -13,10 +13,6 @@ import Motion from '../../ui/util/optional_motion';
export default class Upload extends ImmutablePureComponent { export default class Upload extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
media: ImmutablePropTypes.map.isRequired, media: ImmutablePropTypes.map.isRequired,
onUndo: PropTypes.func.isRequired, onUndo: PropTypes.func.isRequired,

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -17,6 +18,7 @@ import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp
import StatusContent from 'flavours/glitch/components/status_content'; import StatusContent from 'flavours/glitch/components/status_content';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { autoPlayGif } from 'flavours/glitch/initial_state'; import { autoPlayGif } from 'flavours/glitch/initial_state';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const messages = defineMessages({ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' }, more: { id: 'status.more', defaultMessage: 'More' },
@ -30,10 +32,6 @@ const messages = defineMessages({
class Conversation extends ImmutablePureComponent { class Conversation extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
conversationId: PropTypes.string.isRequired, conversationId: PropTypes.string.isRequired,
accounts: ImmutablePropTypes.list.isRequired, accounts: ImmutablePropTypes.list.isRequired,
@ -45,6 +43,7 @@ class Conversation extends ImmutablePureComponent {
markRead: PropTypes.func.isRequired, markRead: PropTypes.func.isRequired,
delete: PropTypes.func.isRequired, delete: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
...WithRouterPropTypes,
}; };
state = { state = {
@ -52,9 +51,8 @@ class Conversation extends ImmutablePureComponent {
}; };
parseClick = (e, destination) => { parseClick = (e, destination) => {
const { router } = this.context; const { history, lastStatus, unread, markRead } = this.props;
const { lastStatus, unread, markRead } = this.props; if (!history) return;
if (!router) return;
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
if (destination === undefined) { if (destination === undefined) {
@ -63,7 +61,7 @@ class Conversation extends ImmutablePureComponent {
} }
destination = `/statuses/${lastStatus.get('id')}`; destination = `/statuses/${lastStatus.get('id')}`;
} }
router.history.push(destination); history.push(destination);
e.preventDefault(); e.preventDefault();
} }
}; };
@ -95,7 +93,7 @@ class Conversation extends ImmutablePureComponent {
}; };
handleClick = () => { handleClick = () => {
if (!this.context.router) { if (!this.props.history) {
return; return;
} }
@ -105,7 +103,7 @@ class Conversation extends ImmutablePureComponent {
markRead(); markRead();
} }
this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); this.props.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`);
}; };
handleMarkAsRead = () => { handleMarkAsRead = () => {
@ -113,7 +111,7 @@ class Conversation extends ImmutablePureComponent {
}; };
handleReply = () => { handleReply = () => {
this.props.reply(this.props.lastStatus, this.context.router.history); this.props.reply(this.props.lastStatus, this.props.history);
}; };
handleDelete = () => { handleDelete = () => {
@ -232,4 +230,4 @@ class Conversation extends ImmutablePureComponent {
} }
export default injectIntl(Conversation); export default withRouter(injectIntl(Conversation));

View File

@ -16,7 +16,7 @@ import {
} from 'flavours/glitch/actions/accounts'; } from 'flavours/glitch/actions/accounts';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { Avatar } from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { DisplayName } from 'flavours/glitch/components/display_name'; import { DisplayName } from 'flavours/glitch/components/display_name';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';

View File

@ -36,10 +36,6 @@ const mapStateToProps = state => ({
class Directory extends PureComponent { class Directory extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
accountIds: ImmutablePropTypes.list.isRequired, accountIds: ImmutablePropTypes.list.isRequired,

View File

@ -3,7 +3,7 @@ import Trie from 'substring-trie';
import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/initial_state'; import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/initial_state';
import { assetHost } from 'flavours/glitch/utils/config'; import { assetHost } from 'flavours/glitch/utils/config';
import unicodeMapping from './emoji_unicode_mapping_light'; import { unicodeMapping } from './emoji_unicode_mapping_light';
const trie = new Trie(Object.keys(unicodeMapping)); const trie = new Trie(Object.keys(unicodeMapping));

View File

@ -13,15 +13,20 @@ export type Search = string;
* This could be a potential area of refactoring or error handling. * This could be a potential area of refactoring or error handling.
* The non-existence of 'skins' property is evident at [this location]{@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.js:121}. * The non-existence of 'skins' property is evident at [this location]{@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.js:121}.
*/ */
export type Skins = null; type Skins = null;
export type FilenameData = string[] | string[][]; type Filename = string;
type UnicodeFilename = string;
export type FilenameData = [
filename: Filename,
unicodeFilename?: UnicodeFilename,
][];
export type ShortCodesToEmojiDataKey = export type ShortCodesToEmojiDataKey =
| EmojiData['id'] | EmojiData['id']
| BaseEmoji['native'] | BaseEmoji['native']
| keyof NimbleEmojiIndex['emojis']; | keyof NimbleEmojiIndex['emojis'];
export type SearchData = [ type SearchData = [
BaseEmoji['native'], BaseEmoji['native'],
Emoji['short_names'], Emoji['short_names'],
Search, Search,
@ -32,9 +37,9 @@ export type ShortCodesToEmojiData = Record<
ShortCodesToEmojiDataKey, ShortCodesToEmojiDataKey,
[FilenameData, SearchData] [FilenameData, SearchData]
>; >;
export type EmojisWithoutShortCodes = FilenameData[]; type EmojisWithoutShortCodes = FilenameData;
export type EmojiCompressed = [ type EmojiCompressed = [
ShortCodesToEmojiData, ShortCodesToEmojiData,
Skins, Skins,
Category[], Category[],

View File

@ -30,22 +30,13 @@ const emojis: Emojis = {};
// decompress // decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => { Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
const [_filenameData, searchData] = shortCodesToEmojiData[shortCode]; const [_filenameData, searchData] = shortCodesToEmojiData[shortCode];
const native = searchData[0]; const [native, short_names, search, unified] = searchData;
let short_names = searchData[1];
const search = searchData[2];
let unified = searchData[3];
if (!unified) {
// unified name can be derived from unicodeToUnifiedName
unified = unicodeToUnifiedName(native);
}
if (short_names) short_names = [shortCode].concat(short_names);
emojis[shortCode] = { emojis[shortCode] = {
native, native,
search, search,
short_names, short_names: short_names ? [shortCode].concat(short_names) : undefined,
unified, unified: unified ?? unicodeToUnifiedName(native),
}; };
}); });

View File

@ -1,37 +0,0 @@
// A mapping of unicode strings to an object containing the filename
// (i.e. the svg filename) and a shortCode intended to be shown
// as a "title" attribute in an HTML element (aka tooltip).
import emojiCompressed from './emoji_compressed';
import { unicodeToFilename } from './unicode_to_filename';
const [
shortCodesToEmojiData,
_skins,
_categories,
_short_names,
emojisWithoutShortCodes,
] = emojiCompressed;
// decompress
const unicodeMapping = {};
function processEmojiMapData(emojiMapData, shortCode) {
let [ native, filename ] = emojiMapData;
if (!filename) {
// filename name can be derived from unicodeToFilename
filename = unicodeToFilename(native);
}
unicodeMapping[native] = {
shortCode: shortCode,
filename: filename,
};
}
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
let [ filenameData ] = shortCodesToEmojiData[shortCode];
filenameData.forEach(emojiMapData => processEmojiMapData(emojiMapData, shortCode));
});
emojisWithoutShortCodes.forEach(emojiMapData => processEmojiMapData(emojiMapData));
export default unicodeMapping;

View File

@ -0,0 +1,60 @@
// A mapping of unicode strings to an object containing the filename
// (i.e. the svg filename) and a shortCode intended to be shown
// as a "title" attribute in an HTML element (aka tooltip).
import type {
FilenameData,
ShortCodesToEmojiDataKey,
} from './emoji_compressed';
import emojiCompressed from './emoji_compressed';
import { unicodeToFilename } from './unicode_to_filename';
type UnicodeMapping = {
[key in FilenameData[number][0]]: {
shortCode: ShortCodesToEmojiDataKey;
filename: FilenameData[number][number];
};
};
const [
shortCodesToEmojiData,
_skins,
_categories,
_short_names,
emojisWithoutShortCodes,
] = emojiCompressed;
// decompress
const unicodeMapping: UnicodeMapping = {};
function processEmojiMapData(
emojiMapData: FilenameData[number],
shortCode?: ShortCodesToEmojiDataKey,
) {
const [native, _filename] = emojiMapData;
let filename = emojiMapData[1];
if (!filename) {
// filename name can be derived from unicodeToFilename
filename = unicodeToFilename(native);
}
unicodeMapping[native] = {
shortCode,
filename,
};
}
Object.keys(shortCodesToEmojiData).forEach(
(shortCode: ShortCodesToEmojiDataKey) => {
if (shortCode === undefined) return;
const [filenameData, _searchData] = shortCodesToEmojiData[shortCode];
filenameData.forEach((emojiMapData) => {
processEmojiMapData(emojiMapData, shortCode);
});
},
);
emojisWithoutShortCodes.forEach((emojiMapData) => {
processEmojiMapData(emojiMapData);
});
export { unicodeMapping };

View File

@ -34,7 +34,6 @@ const mapStateToProps = state => ({
class Explore extends PureComponent { class Explore extends PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object, identity: PropTypes.object,
}; };
@ -69,47 +68,45 @@ class Explore extends PureComponent {
<Search /> <Search />
</div> </div>
<div className='scrollable scrollable--flex' data-nosnippet> {isSearching ? (
{isSearching ? ( <SearchResults />
<SearchResults /> ) : (
) : ( <>
<> <div className='account__section-headline'>
<div className='account__section-headline'> <NavLink exact to='/explore'>
<NavLink exact to='/explore'> <FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' />
<FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' /> </NavLink>
<NavLink exact to='/explore/tags'>
<FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' />
</NavLink>
{signedIn && (
<NavLink exact to='/explore/suggestions'>
<FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='People' />
</NavLink> </NavLink>
)}
<NavLink exact to='/explore/tags'> <NavLink exact to='/explore/links'>
<FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' /> <FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' />
</NavLink> </NavLink>
</div>
{signedIn && ( <Switch>
<NavLink exact to='/explore/suggestions'> <Route path='/explore/tags' component={Tags} />
<FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='People' /> <Route path='/explore/links' component={Links} />
</NavLink> <Route path='/explore/suggestions' component={Suggestions} />
)} <Route exact path={['/explore', '/explore/posts', '/search']}>
<Statuses multiColumn={multiColumn} />
</Route>
</Switch>
<NavLink exact to='/explore/links'> <Helmet>
<FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' /> <title>{intl.formatMessage(messages.title)}</title>
</NavLink> <meta name='robots' content={isSearching ? 'noindex' : 'all'} />
</div> </Helmet>
</>
<Switch> )}
<Route path='/explore/tags' component={Tags} />
<Route path='/explore/links' component={Links} />
<Route path='/explore/suggestions' component={Suggestions} />
<Route exact path={['/explore', '/explore/posts', '/search']}>
<Statuses multiColumn={multiColumn} />
</Route>
</Switch>
<Helmet>
<title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content={isSearching ? 'noindex' : 'all'} />
</Helmet>
</>
)}
</div>
</Column> </Column>
); );
} }

View File

@ -3,12 +3,15 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchTrendingLinks } from 'flavours/glitch/actions/trends'; import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import Story from './components/story'; import Story from './components/story';
@ -23,10 +26,17 @@ class Links extends PureComponent {
links: ImmutablePropTypes.list, links: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
...WithRouterPropTypes,
}; };
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, links, history } = this.props;
// If we're navigating back to the screen, do not trigger a reload
if (history.action === 'POP' && links.size > 0) {
return;
}
dispatch(fetchTrendingLinks()); dispatch(fetchTrendingLinks());
} }
@ -52,7 +62,7 @@ class Links extends PureComponent {
} }
return ( return (
<div className='explore__links'> <div className='explore__links scrollable' data-nosnippet>
{banner} {banner}
{isLoading ? (<LoadingIndicator />) : links.map((link, i) => ( {isLoading ? (<LoadingIndicator />) : links.map((link, i) => (
@ -77,4 +87,4 @@ class Links extends PureComponent {
} }
export default connect(mapStateToProps)(Links); export default connect(mapStateToProps)(withRouter(Links));

View File

@ -204,7 +204,7 @@ class Results extends PureComponent {
<button onClick={this.handleSelectStatuses} className={type === 'statuses' ? 'active' : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button> <button onClick={this.handleSelectStatuses} className={type === 'statuses' ? 'active' : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
</div> </div>
<div className='explore__search-results'> <div className='explore__search-results' data-nosnippet>
<ScrollableList <ScrollableList
scrollKey='search-results' scrollKey='search-results'
isLoading={isLoading} isLoading={isLoading}

View File

@ -3,15 +3,19 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends'; import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import StatusList from 'flavours/glitch/components/status_list'; import StatusList from 'flavours/glitch/components/status_list';
import { getStatusList } from 'flavours/glitch/selectors'; import { getStatusList } from 'flavours/glitch/selectors';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
statusIds: getStatusList(state, 'trending'), statusIds: getStatusList(state, 'trending'),
@ -27,10 +31,17 @@ class Statuses extends PureComponent {
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
...WithRouterPropTypes,
}; };
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, statusIds, history } = this.props;
// If we're navigating back to the screen, do not trigger a reload
if (history.action === 'POP' && statusIds.size > 0) {
return;
}
dispatch(fetchTrendingStatuses()); dispatch(fetchTrendingStatuses());
} }
@ -45,27 +56,23 @@ class Statuses extends PureComponent {
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />; const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;
return ( return (
<> <StatusList
<DismissableBanner id='explore/statuses'> trackScroll
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /> prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
</DismissableBanner> alwaysPrepend
timelineId='explore'
<StatusList statusIds={statusIds}
trackScroll scrollKey='explore-statuses'
timelineId='explore' hasMore={hasMore}
statusIds={statusIds} isLoading={isLoading}
scrollKey='explore-statuses' onLoadMore={this.handleLoadMore}
hasMore={hasMore} emptyMessage={emptyMessage}
isLoading={isLoading} bindToDocument={!multiColumn}
onLoadMore={this.handleLoadMore} withCounters
emptyMessage={emptyMessage} />
bindToDocument={!multiColumn}
withCounters
/>
</>
); );
} }
} }
export default connect(mapStateToProps)(Statuses); export default connect(mapStateToProps)(withRouter(Statuses));

View File

@ -3,12 +3,15 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions'; import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import AccountCard from 'flavours/glitch/features/directory/components/account_card'; import AccountCard from 'flavours/glitch/features/directory/components/account_card';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']), suggestions: state.getIn(['suggestions', 'items']),
@ -21,10 +24,17 @@ class Suggestions extends PureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
suggestions: ImmutablePropTypes.list, suggestions: ImmutablePropTypes.list,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
...WithRouterPropTypes,
}; };
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, suggestions, history } = this.props;
// If we're navigating back to the screen, do not trigger a reload
if (history.action === 'POP' && suggestions.size > 0) {
return;
}
dispatch(fetchSuggestions(true)); dispatch(fetchSuggestions(true));
} }
@ -47,7 +57,7 @@ class Suggestions extends PureComponent {
} }
return ( return (
<div className='explore__suggestions'> <div className='explore__suggestions scrollable' data-nosnippet>
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => ( {isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
<AccountCard key={suggestion.get('account')} id={suggestion.get('account')} onDismiss={suggestion.get('source') === 'past_interactions' ? this.handleDismiss : null} /> <AccountCard key={suggestion.get('account')} id={suggestion.get('account')} onDismiss={suggestion.get('source') === 'past_interactions' ? this.handleDismiss : null} />
))} ))}
@ -57,4 +67,4 @@ class Suggestions extends PureComponent {
} }
export default connect(mapStateToProps)(Suggestions); export default connect(mapStateToProps)(withRouter(Suggestions));

View File

@ -3,6 +3,8 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -10,6 +12,7 @@ import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -24,10 +27,17 @@ class Tags extends PureComponent {
hashtags: ImmutablePropTypes.list, hashtags: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
...WithRouterPropTypes,
}; };
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, history, hashtags } = this.props;
// If we're navigating back to the screen, do not trigger a reload
if (history.action === 'POP' && hashtags.size > 0) {
return;
}
dispatch(fetchTrendingHashtags()); dispatch(fetchTrendingHashtags());
} }
@ -53,7 +63,7 @@ class Tags extends PureComponent {
} }
return ( return (
<div className='explore__links'> <div className='scrollable explore__links' data-nosnippet>
{banner} {banner}
{isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => ( {isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => (
@ -65,4 +75,4 @@ class Tags extends PureComponent {
} }
export default connect(mapStateToProps)(Tags); export default connect(mapStateToProps)(withRouter(Tags));

View File

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { toServerSideType } from 'flavours/glitch/utils/filters'; import { toServerSideType } from 'flavours/glitch/utils/filters';
const mapStateToProps = (state, { filterId }) => ({ const mapStateToProps = (state, { filterId }) => ({

View File

@ -188,33 +188,31 @@ const Firehose = ({ feedType, multiColumn }) => {
<ColumnSettings /> <ColumnSettings />
</ColumnHeader> </ColumnHeader>
<div className='scrollable scrollable--flex'> <div className='account__section-headline'>
<div className='account__section-headline'> <NavLink exact to='/public/local'>
<NavLink exact to='/public/local'> <FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' />
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='This server' /> </NavLink>
</NavLink>
<NavLink exact to='/public/remote'> <NavLink exact to='/public/remote'>
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' /> <FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Other servers' />
</NavLink> </NavLink>
<NavLink exact to='/public'> <NavLink exact to='/public'>
<FormattedMessage tagName='div' id='firehose.all' defaultMessage='All' /> <FormattedMessage tagName='div' id='firehose.all' defaultMessage='All' />
</NavLink> </NavLink>
</div>
<StatusListContainer
prepend={prependBanner}
timelineId={`${feedType}${feedType === 'public' && allowLocalOnly ? ':allow_local_only' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={handleLoadMore}
trackScroll
scrollKey='firehose'
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
regex={regex}
/>
</div> </div>
<StatusListContainer
prepend={prependBanner}
timelineId={`${feedType}${feedType === 'public' && allowLocalOnly ? ':allow_local_only' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={handleLoadMore}
trackScroll
scrollKey='firehose'
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
regex={regex}
/>
<Helmet> <Helmet>
<title>{intl.formatMessage(messages.title)}</title> <title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content='noindex' /> <meta name='robots' content='noindex' />

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -12,8 +13,9 @@ import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'
import { changeSetting, saveSettings } from 'flavours/glitch/actions/settings'; import { changeSetting, saveSettings } from 'flavours/glitch/actions/settings';
import { fetchSuggestions } from 'flavours/glitch/actions/suggestions'; import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
import { markAsPartial } from 'flavours/glitch/actions/timelines'; import { markAsPartial } from 'flavours/glitch/actions/timelines';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg'; import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
import Account from './components/account'; import Account from './components/account';
@ -25,14 +27,11 @@ const mapStateToProps = state => ({
class FollowRecommendations extends ImmutablePureComponent { class FollowRecommendations extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list, suggestions: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
...WithRouterPropTypes,
}; };
componentDidMount () { componentDidMount () {
@ -56,8 +55,7 @@ class FollowRecommendations extends ImmutablePureComponent {
} }
handleDone = () => { handleDone = () => {
const { dispatch } = this.props; const { history, dispatch } = this.props;
const { router } = this.context;
dispatch(requestBrowserPermission((permission) => { dispatch(requestBrowserPermission((permission) => {
if (permission === 'granted') { if (permission === 'granted') {
@ -71,7 +69,7 @@ class FollowRecommendations extends ImmutablePureComponent {
} }
})); }));
router.history.push('/home'); history.push('/home');
}; };
render () { render () {
@ -118,4 +116,4 @@ class FollowRecommendations extends ImmutablePureComponent {
} }
export default connect(mapStateToProps)(FollowRecommendations); export default withRouter(connect(mapStateToProps)(FollowRecommendations));

View File

@ -4,6 +4,7 @@ import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -16,27 +17,22 @@ import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; import { unicodeMapping } from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state';
import { assetHost } from 'flavours/glitch/utils/config'; import { assetHost } from 'flavours/glitch/utils/config';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
next: { id: 'lightbox.next', defaultMessage: 'Next' }, next: { id: 'lightbox.next', defaultMessage: 'Next' },
}); });
class Content extends ImmutablePureComponent { class ContentWithRouter extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
announcement: ImmutablePropTypes.map.isRequired, announcement: ImmutablePropTypes.map.isRequired,
...WithRouterPropTypes,
}; };
setRef = c => { setRef = c => {
@ -91,25 +87,25 @@ class Content extends ImmutablePureComponent {
} }
onMentionClick = (mention, e) => { onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(`/@${mention.get('acct')}`); this.props.history.push(`/@${mention.get('acct')}`);
} }
}; };
onHashtagClick = (hashtag, e) => { onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, ''); hashtag = hashtag.replace(/^#/, '');
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(`/tags/${hashtag}`); this.props.history.push(`/tags/${hashtag}`);
} }
}; };
onStatusClick = (status, e) => { onStatusClick = (status, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
} }
}; };
@ -155,6 +151,8 @@ class Content extends ImmutablePureComponent {
} }
const Content = withRouter(ContentWithRouter);
class Emoji extends PureComponent { class Emoji extends PureComponent {
static propTypes = { static propTypes = {

View File

@ -88,7 +88,6 @@ const badgeDisplay = (number, limit) => {
class GettingStarted extends ImmutablePureComponent { class GettingStarted extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object, identity: PropTypes.object,
}; };

View File

@ -28,7 +28,6 @@ const messages = defineMessages({
class GettingStartedMisc extends ImmutablePureComponent { class GettingStartedMisc extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object, identity: PropTypes.object,
}; };

View File

@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { ShortNumber } from 'flavours/glitch/components/short_number'; import { ShortNumber } from 'flavours/glitch/components/short_number';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -11,7 +11,7 @@ import { throttle, escapeRegExp } from 'lodash';
import { openModal, closeModal } from 'flavours/glitch/actions/modal'; import { openModal, closeModal } from 'flavours/glitch/actions/modal';
import api from 'flavours/glitch/api'; import api from 'flavours/glitch/api';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { registrationsOpen, sso_redirect } from 'flavours/glitch/initial_state'; import { registrationsOpen, sso_redirect } from 'flavours/glitch/initial_state';

View File

@ -4,6 +4,7 @@ import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -22,6 +23,7 @@ import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { RadioButton } from 'flavours/glitch/components/radio_button'; import { RadioButton } from 'flavours/glitch/components/radio_button';
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
const messages = defineMessages({ const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
@ -38,10 +40,6 @@ const mapStateToProps = (state, props) => ({
class ListTimeline extends PureComponent { class ListTimeline extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -50,6 +48,7 @@ class ListTimeline extends PureComponent {
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
...WithRouterPropTypes,
}; };
handlePin = () => { handlePin = () => {
@ -59,7 +58,7 @@ class ListTimeline extends PureComponent {
dispatch(removeColumn(columnId)); dispatch(removeColumn(columnId));
} else { } else {
dispatch(addColumn('LIST', { id: this.props.params.id })); dispatch(addColumn('LIST', { id: this.props.params.id }));
this.context.router.history.push('/'); this.props.history.push('/');
} }
}; };
@ -137,7 +136,7 @@ class ListTimeline extends PureComponent {
if (columnId) { if (columnId) {
dispatch(removeColumn(columnId)); dispatch(removeColumn(columnId));
} else { } else {
this.context.router.history.push('/lists'); this.props.history.push('/lists');
} }
}, },
}, },
@ -242,4 +241,4 @@ class ListTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(ListTimeline)); export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline)));

View File

@ -1,25 +1,24 @@
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
// Our imports.
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import NotificationOverlayContainer from '../containers/overlay_container'; import NotificationOverlayContainer from '../containers/overlay_container';
import Report from './report'; import Report from './report';
export default class AdminReport extends ImmutablePureComponent { class AdminReport extends ImmutablePureComponent {
static propTypes = { static propTypes = {
hidden: PropTypes.bool, hidden: PropTypes.bool,
@ -28,6 +27,7 @@ export default class AdminReport extends ImmutablePureComponent {
notification: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired,
unread: PropTypes.bool, unread: PropTypes.bool,
report: ImmutablePropTypes.map.isRequired, report: ImmutablePropTypes.map.isRequired,
...WithRouterPropTypes,
}; };
handleMoveUp = () => { handleMoveUp = () => {
@ -45,15 +45,15 @@ export default class AdminReport extends ImmutablePureComponent {
}; };
handleOpenProfile = () => { handleOpenProfile = () => {
const { notification } = this.props; const { history, notification } = this.props;
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); history.push(`/@${notification.getIn(['account', 'acct'])}`);
}; };
handleMention = e => { handleMention = e => {
e.preventDefault(); e.preventDefault();
const { notification, onMention } = this.props; const { history, notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), history);
}; };
getHandlers () { getHandlers () {
@ -111,3 +111,5 @@ export default class AdminReport extends ImmutablePureComponent {
} }
} }
export default withRouter(AdminReport);

View File

@ -1,24 +1,23 @@
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
// Our imports.
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import AccountContainer from 'flavours/glitch/containers/account_container'; import AccountContainer from 'flavours/glitch/containers/account_container';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import NotificationOverlayContainer from '../containers/overlay_container'; import NotificationOverlayContainer from '../containers/overlay_container';
export default class NotificationFollow extends ImmutablePureComponent { class NotificationAdminSignup extends ImmutablePureComponent {
static propTypes = { static propTypes = {
hidden: PropTypes.bool, hidden: PropTypes.bool,
@ -26,6 +25,7 @@ export default class NotificationFollow extends ImmutablePureComponent {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
notification: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired,
unread: PropTypes.bool, unread: PropTypes.bool,
...WithRouterPropTypes,
}; };
handleMoveUp = () => { handleMoveUp = () => {
@ -43,15 +43,15 @@ export default class NotificationFollow extends ImmutablePureComponent {
}; };
handleOpenProfile = () => { handleOpenProfile = () => {
const { notification } = this.props; const { history, notification } = this.props;
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); history.push(`/@${notification.getIn(['account', 'acct'])}`);
}; };
handleMention = e => { handleMention = e => {
e.preventDefault(); e.preventDefault();
const { notification, onMention } = this.props; const { history, notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), history);
}; };
getHandlers () { getHandlers () {
@ -104,3 +104,5 @@ export default class NotificationFollow extends ImmutablePureComponent {
} }
} }
export default withRouter(NotificationAdminSignup);

View File

@ -1,24 +1,23 @@
// Package imports.
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
// Our imports.
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import AccountContainer from 'flavours/glitch/containers/account_container'; import AccountContainer from 'flavours/glitch/containers/account_container';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import NotificationOverlayContainer from '../containers/overlay_container'; import NotificationOverlayContainer from '../containers/overlay_container';
export default class NotificationFollow extends ImmutablePureComponent { class NotificationFollow extends ImmutablePureComponent {
static propTypes = { static propTypes = {
hidden: PropTypes.bool, hidden: PropTypes.bool,
@ -26,6 +25,7 @@ export default class NotificationFollow extends ImmutablePureComponent {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
notification: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired,
unread: PropTypes.bool, unread: PropTypes.bool,
...WithRouterPropTypes,
}; };
handleMoveUp = () => { handleMoveUp = () => {
@ -43,15 +43,15 @@ export default class NotificationFollow extends ImmutablePureComponent {
}; };
handleOpenProfile = () => { handleOpenProfile = () => {
const { notification } = this.props; const { history, notification } = this.props;
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); history.push(`/@${notification.getIn(['account', 'acct'])}`);
}; };
handleMention = e => { handleMention = e => {
e.preventDefault(); e.preventDefault();
const { notification, onMention } = this.props; const { history, notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), history);
}; };
getHandlers () { getHandlers () {
@ -104,3 +104,5 @@ export default class NotificationFollow extends ImmutablePureComponent {
} }
} }
export default withRouter(NotificationFollow);

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -14,6 +15,7 @@ import { DisplayName } from 'flavours/glitch/components/display_name';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import NotificationOverlayContainer from '../containers/overlay_container'; import NotificationOverlayContainer from '../containers/overlay_container';
@ -31,6 +33,7 @@ class FollowRequest extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
notification: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired,
unread: PropTypes.bool, unread: PropTypes.bool,
...WithRouterPropTypes,
}; };
handleMoveUp = () => { handleMoveUp = () => {
@ -48,15 +51,15 @@ class FollowRequest extends ImmutablePureComponent {
}; };
handleOpenProfile = () => { handleOpenProfile = () => {
const { notification } = this.props; const { history, notification } = this.props;
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); history.push(`/@${notification.getIn(['account', 'acct'])}`);
}; };
handleMention = e => { handleMention = e => {
e.preventDefault(); e.preventDefault();
const { notification, onMention } = this.props; const { history, notification, onMention } = this.props;
onMention(notification.get('account'), this.context.router.history); onMention(notification.get('account'), history);
}; };
getHandlers () { getHandlers () {
@ -135,4 +138,4 @@ class FollowRequest extends ImmutablePureComponent {
} }
export default injectIntl(FollowRequest); export default withRouter(injectIntl(FollowRequest));

View File

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { requestBrowserPermission } from 'flavours/glitch/actions/notifications'; import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
import Button from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';

Some files were not shown because too many files have changed in this diff Show More