Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Essem 2024-05-20 21:35:02 -05:00
commit 9b0c46ebf2
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
220 changed files with 2254 additions and 869 deletions

View file

@ -4,7 +4,8 @@ NODE_ENV=production
LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature # Secret values required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR # Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION

View file

@ -264,8 +264,8 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
search: elasticsearch:
image: ${{ matrix.search-image }} image: ${{ contains(matrix.search-image, 'elasticsearch') && matrix.search-image || '' }}
env: env:
discovery.type: single-node discovery.type: single-node
xpack.security.enabled: false xpack.security.enabled: false
@ -277,6 +277,20 @@ jobs:
ports: ports:
- 9200:9200 - 9200:9200
opensearch:
image: ${{ contains(matrix.search-image, 'opensearch') && matrix.search-image || '' }}
env:
discovery.type: single-node
DISABLE_INSTALL_DEMO_CONFIG: true
DISABLE_SECURITY_PLUGIN: true
options: >-
--health-cmd "curl http://localhost:9200/_cluster/health"
--health-interval 10s
--health-timeout 5s
--health-retries 10
ports:
- 9200:9200
env: env:
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
@ -300,6 +314,8 @@ jobs:
include: include:
- ruby-version: '.ruby-version' - ruby-version: '.ruby-version'
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2 search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
- ruby-version: '.ruby-version'
search-image: opensearchproject/opensearch:2
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -211,6 +211,11 @@ Style/PercentLiteralDelimiters:
Style/RedundantBegin: Style/RedundantBegin:
Enabled: false Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantfetchblock
Style/RedundantFetchBlock:
Enabled: false
# Reason: Overridden to reduce implicit StandardError rescues # Reason: Overridden to reduce implicit StandardError rescues
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror # https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
Style/RescueStandardError: Style/RescueStandardError:

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.62.1. # using RuboCop version 1.63.5.
# 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
@ -122,13 +122,6 @@ Style/HashTransformValues:
- 'app/serializers/rest/web_push_subscription_serializer.rb' - 'app/serializers/rest/web_push_subscription_serializer.rb'
- 'app/services/import_service.rb' - 'app/services/import_service.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/IfUnlessModifier:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/ffmpeg.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Style/MapToHash: Style/MapToHash:
Exclude: Exclude:
@ -176,16 +169,6 @@ Style/RedundantConstantBase:
- 'config/environments/production.rb' - 'config/environments/production.rb'
- 'config/initializers/sidekiq.rb' - 'config/initializers/sidekiq.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeForConstants.
Style/RedundantFetchBlock:
Exclude:
- 'config/initializers/1_hosts.rb'
- 'config/initializers/chewy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/puma.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try! # AllowedMethods: present?, blank?, presence, try, try!

View file

@ -1,22 +0,0 @@
# frozen_string_literal: true
if ENV['CI']
require 'simplecov-lcov'
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
else
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
end
SimpleCov.start 'rails' do
enable_coverage :branch
add_filter 'lib/linter'
add_group 'Libraries', 'lib'
add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters'
add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators'
end

View file

@ -132,7 +132,7 @@ group :test do
gem 'email_spec' gem 'email_spec'
# Extra RSpec extension methods and helpers for sidekiq # Extra RSpec extension methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0' gem 'rspec-sidekiq', '~> 5.0'
# Browser integration testing # Browser integration testing
gem 'capybara', '~> 3.39' gem 'capybara', '~> 3.39'
@ -178,7 +178,7 @@ group :development do
# Preview mail in the browser # Preview mail in the browser
gem 'letter_opener', '~> 1.8' gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0' gem 'letter_opener_web', '~> 3.0'
# Security analysis CLI tools # Security analysis CLI tools
gem 'brakeman', '~> 6.0', require: false gem 'brakeman', '~> 6.0', require: false

View file

@ -10,35 +10,35 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.2) actioncable (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.2) actionmailbox (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3.2) actionmailer (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
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.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.2) actionpack (7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2) actiontext (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.2) actionview (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
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.1.3.2) activejob (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.2) activemodel (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
activerecord (7.1.3.2) activerecord (7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.2) activestorage (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.2) activesupport (7.1.3.3)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -100,16 +100,16 @@ GEM
attr_required (1.0.2) attr_required (1.0.2)
awrence (1.2.1) awrence (1.2.1)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.922.0) aws-partitions (1.929.0)
aws-sdk-core (3.194.1) aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.80.0) aws-sdk-kms (1.81.0)
aws-sdk-core (~> 3, >= 3.193.0) aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.149.1) aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0) aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.8)
@ -272,7 +272,7 @@ GEM
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
fog-openstack (1.1.0) fog-openstack (1.1.1)
fog-core (~> 2.1) fog-core (~> 2.1)
fog-json (>= 1.0) fog-json (>= 1.0)
formatador (1.1.0) formatador (1.1.0)
@ -389,10 +389,10 @@ GEM
addressable (~> 2.8) addressable (~> 2.8)
letter_opener (1.10.0) letter_opener (1.10.0)
launchy (>= 2.2, < 4) launchy (>= 2.2, < 4)
letter_opener_web (2.0.0) letter_opener_web (3.0.0)
actionmailer (>= 5.2) actionmailer (>= 6.1)
letter_opener (~> 1.7) letter_opener (~> 1.9)
railties (>= 5.2) railties (>= 6.1)
rexml rexml
link_header (0.0.8) link_header (0.0.8)
llhttp-ffi (0.5.0) llhttp-ffi (0.5.0)
@ -422,7 +422,7 @@ GEM
memory_profiler (1.0.1) memory_profiler (1.0.1)
mime-types (3.5.2) mime-types (3.5.2)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2024.0305) mime-types-data (3.2024.0507)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.6) mini_portile2 (2.8.6)
minitest (5.22.3) minitest (5.22.3)
@ -434,7 +434,7 @@ GEM
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.4.10) net-imap (0.4.11)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@ -444,8 +444,8 @@ GEM
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.1) nio4r (2.7.3)
nokogiri (1.16.4) nokogiri (1.16.5)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nsa (0.3.0) nsa (0.3.0)
@ -543,7 +543,7 @@ GEM
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0) opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-pg (0.27.2) opentelemetry-instrumentation-pg (0.27.3)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-helpers-sql-obfuscation opentelemetry-helpers-sql-obfuscation
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
@ -634,20 +634,20 @@ GEM
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.3.2) rails (7.1.3.3)
actioncable (= 7.1.3.2) actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.2) actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.2) actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actiontext (= 7.1.3.2) actiontext (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.2) railties (= 7.1.3.3)
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)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9) rails-i18n (7.0.9)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.3.2) railties (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -686,14 +686,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.9.0) regexp_parser (2.9.0)
reline (0.5.6) reline (0.5.7)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.6.0) request_store (1.6.0)
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.8)
strscan (>= 3.0.9)
rotp (6.3.0) rotp (6.3.0)
rouge (4.2.1) rouge (4.2.1)
rpam2 (4.0.2) rpam2 (4.0.2)
@ -708,7 +709,7 @@ GEM
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-github (2.4.0) rspec-github (2.4.0)
rspec-core (~> 3.0) rspec-core (~> 3.0)
rspec-mocks (3.13.0) rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (6.1.2) rspec-rails (6.1.2)
@ -719,7 +720,7 @@ GEM
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-sidekiq (4.2.0) rspec-sidekiq (5.0.0)
rspec-core (~> 3.0) rspec-core (~> 3.0)
rspec-expectations (~> 3.0) rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
@ -774,7 +775,7 @@ GEM
scenic (1.8.0) scenic (1.8.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
selenium-webdriver (4.20.1) selenium-webdriver (4.21.1)
base64 (~> 0.2) base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@ -815,6 +816,7 @@ GEM
stringio (3.1.0) stringio (3.1.0)
strong_migrations (1.8.0) strong_migrations (1.8.0)
activerecord (>= 5.2) activerecord (>= 5.2)
strscan (3.1.0)
swd (1.3.0) swd (1.3.0)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
@ -893,7 +895,7 @@ GEM
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.13) zeitwerk (2.6.14)
PLATFORMS PLATFORMS
ruby ruby
@ -955,7 +957,7 @@ DEPENDENCIES
kaminari (~> 1.2) kaminari (~> 1.2)
kt-paperclip (~> 7.2) kt-paperclip (~> 7.2)
letter_opener (~> 1.8) letter_opener (~> 1.8)
letter_opener_web (~> 2.0) letter_opener_web (~> 3.0)
link_header (~> 0.0) link_header (~> 0.0)
lograge (~> 0.12) lograge (~> 0.12)
mail (~> 2.8) mail (~> 2.8)
@ -1012,7 +1014,7 @@ DEPENDENCIES
rqrcode (~> 2.2) rqrcode (~> 2.2)
rspec-github (~> 2.4) rspec-github (~> 2.4)
rspec-rails (~> 6.0) rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0) rspec-sidekiq (~> 5.0)
rubocop rubocop
rubocop-capybara rubocop-capybara
rubocop-performance rubocop-performance

View file

@ -25,7 +25,7 @@ class AccountsController < ApplicationController
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
@statuses = filtered_statuses.without_reblogs.limit(limit) @statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status) @statuses = preload_collection(@statuses, Status)
end end
format.json do format.json do

View file

@ -18,7 +18,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items def set_items
case params[:id] case params[:id]
when 'featured' when 'featured'
@items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) } @items = for_signed_account { preload_collection(@account.pinned_statuses.not_local_only, Status) }
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) } @items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
when 'tags' when 'tags'
@items = for_signed_account { @account.featured_tags } @items = for_signed_account { @account.featured_tags }

View file

@ -60,7 +60,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses def set_statuses
return unless page_requested? return unless page_requested?
@statuses = cache_collection_paginated_by_id( @statuses = preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, signed_request_account).results, AccountStatusesFilter.new(@account, signed_request_account).results,
Status, Status,
LIMIT, LIMIT,

View file

@ -19,11 +19,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def load_statuses def load_statuses
@account.unavailable? ? [] : cached_account_statuses @account.unavailable? ? [] : preloaded_account_statuses
end end
def cached_account_statuses def preloaded_account_statuses
cache_collection_paginated_by_id( preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, current_account, params).results, AccountStatusesFilter.new(@account, current_account, params).results,
Status, Status,
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show def show
return doorkeeper_render_error unless valid_doorkeeper_token? return doorkeeper_render_error unless valid_doorkeeper_token?
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes) render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
end end
end end

View file

@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create def create
@app = Doorkeeper::Application.create!(application_options) @app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer render json: @app, serializer: REST::CredentialApplicationSerializer
end end
private private
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
end end
def app_params def app_params
params.permit(:client_name, :redirect_uris, :scopes, :website) params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
end end
end end

View file

@ -13,11 +13,11 @@ class Api::V1::BookmarksController < Api::BaseController
private private
def load_statuses def load_statuses
cached_bookmarks preloaded_bookmarks
end end
def cached_bookmarks def preloaded_bookmarks
cache_collection(results.map(&:status), Status) preload_collection(results.map(&:status), Status)
end end
def results def results

View file

@ -13,11 +13,11 @@ class Api::V1::FavouritesController < Api::BaseController
private private
def load_statuses def load_statuses
cached_favourites preloaded_favourites
end end
def cached_favourites def preloaded_favourites
cache_collection(results.map(&:status), Status) preload_collection(results.map(&:status), Status)
end end
def results def results

View file

@ -41,7 +41,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
) )
NotificationRequest.preload_cache_collection(requests) do |statuses| NotificationRequest.preload_cache_collection(requests) do |statuses|
cache_collection(statuses, Status) preload_collection(statuses, Status)
end end
end end

View file

@ -50,7 +50,7 @@ class Api::V1::NotificationsController < Api::BaseController
) )
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
cache_collection(target_statuses, Status) preload_collection(target_statuses, Status)
end end
end end

View file

@ -26,13 +26,13 @@ class Api::V1::StatusesController < Api::BaseController
DESCENDANTS_DEPTH_LIMIT = 20 DESCENDANTS_DEPTH_LIMIT = 20
def index def index
@statuses = cache_collection(@statuses, Status) @statuses = preload_collection(@statuses, Status)
render json: @statuses, each_serializer: REST::StatusSerializer render json: @statuses, each_serializer: REST::StatusSerializer
end end
def show def show
cache_if_unauthenticated! cache_if_unauthenticated!
@status = cache_collection([@status], Status).first @status = preload_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
@ -51,8 +51,8 @@ class Api::V1::StatusesController < Api::BaseController
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account) ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit) descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
loaded_ancestors = cache_collection(ancestors_results, Status) loaded_ancestors = preload_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status) loaded_descendants = preload_collection(descendants_results, Status)
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants statuses = [@status] + @context.ancestors + @context.descendants

View file

@ -15,11 +15,11 @@ class Api::V1::Timelines::DirectController < Api::BaseController
private private
def load_statuses def load_statuses
cached_direct_statuses preloaded_direct_statuses
end end
def cached_direct_statuses def preloaded_direct_statuses
cache_collection direct_statuses, Status preload_collection direct_statuses, Status
end end
def direct_statuses def direct_statuses

View file

@ -21,11 +21,11 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
private private
def load_statuses def load_statuses
cached_home_statuses preloaded_home_statuses
end end
def cached_home_statuses def preloaded_home_statuses
cache_collection home_statuses, Status preload_collection home_statuses, Status
end end
def home_statuses def home_statuses

View file

@ -21,11 +21,11 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
end end
def set_statuses def set_statuses
@statuses = cached_list_statuses @statuses = preloaded_list_statuses
end end
def cached_list_statuses def preloaded_list_statuses
cache_collection list_statuses, Status preload_collection list_statuses, Status
end end
def list_statuses def list_statuses

View file

@ -18,11 +18,11 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
end end
def load_statuses def load_statuses
cached_public_statuses_page preloaded_public_statuses_page
end end
def cached_public_statuses_page def preloaded_public_statuses_page
cache_collection(public_statuses, Status) preload_collection(public_statuses, Status)
end end
def public_statuses def public_statuses

View file

@ -23,11 +23,11 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
end end
def load_statuses def load_statuses
cached_tagged_statuses preloaded_tagged_statuses
end end
def cached_tagged_statuses def preloaded_tagged_statuses
@tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status) @tag.nil? ? [] : preload_collection(tag_timeline_statuses, Status)
end end
def tag_timeline_statuses def tag_timeline_statuses

View file

@ -20,7 +20,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def set_statuses def set_statuses
@statuses = if enabled? @statuses = if enabled?
cache_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status) preload_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
else else
[] []
end end

View file

@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern include UserTrackingConcern
include SessionTrackingConcern include SessionTrackingConcern
include CacheConcern include CacheConcern
include PreloadingConcern
include DomainControlHelper include DomainControlHelper
include ThemingConcern include ThemingConcern
include DatabaseHelper include DatabaseHelper

View file

@ -45,20 +45,4 @@ module CacheConcern
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true) Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
end end
end end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:preload_cacheable_associations)
records = raw.to_a
klass.preload_cacheable_associations(records)
records
end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.to_a_paginated_by_id(limit, options), klass
end
end end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module PreloadingConcern
extend ActiveSupport::Concern
def preload_collection(scope, klass)
return scope unless klass.respond_to?(:preload_cacheable_associations)
scope.to_a.tap do |records|
klass.preload_cacheable_associations(records)
end
end
def preload_collection_paginated_by_id(scope, klass, limit, options)
preload_collection scope.to_a_paginated_by_id(limit, options), klass
end
end

View file

@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController
def new def new
@application = Doorkeeper::Application.new( @application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri, redirect_uri: Doorkeeper.configuration.native_redirect_uri,
scopes: 'read write follow' scopes: 'read:me'
) )
end end

View file

@ -45,7 +45,7 @@ class TagsController < ApplicationController
end end
def set_statuses def set_statuses
@statuses = cache_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status) @statuses = preload_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status)
end end
def limit_param def limit_param

View file

@ -241,11 +241,20 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end end
def site_icon_path(type, size = '48') def mascot_url
icon = SiteUpload.find_by(var: type) full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
return nil unless icon end
icon.file.url(size) def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
def favicon_path(size = '48')
instance_presenter.favicon&.file&.url(size)
end
def app_icon_path(size = '48')
instance_presenter.app_icon&.file&.url(size)
end end
# glitch-soc addition to handle the multiple flavors # glitch-soc addition to handle the multiple flavors

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
module MascotHelper
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
end

View file

@ -20,7 +20,7 @@ export function changeSetting(path, value) {
} }
const debouncedSave = debounce((dispatch, getState) => { const debouncedSave = debounce((dispatch, getState) => {
if (getState().getIn(['settings', 'saved'])) { if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) {
return; return;
} }

View file

@ -174,7 +174,6 @@ Account.propTypes = {
onBlock: PropTypes.func, onBlock: PropTypes.func,
onMute: PropTypes.func, onMute: PropTypes.func,
onMuteNotifications: PropTypes.func, onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool, hidden: PropTypes.bool,
minimal: PropTypes.bool, minimal: PropTypes.bool,
defaultAction: PropTypes.string, defaultAction: PropTypes.string,

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context'; import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { useAppHistory } from './router'; import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
}; };
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
title: PropTypes.node, title: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
); );
} }
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) { if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = ( collapseButton = (
<button <button
className={collapsibleButtonClassName} className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
} }
export default injectIntl(withRouter(ColumnHeader)); export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import emojify from 'flavours/glitch/features/emoji/emoji'; import emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {}); }, {});
class Poll extends ImmutablePureComponent { class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map, poll: ImmutablePropTypes.map,
lang: PropTypes.string, lang: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul> </ul>
<div className='poll__footer'> <div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} {!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>} {!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>} {showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount} {votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
} }
export default injectIntl(Poll); export default injectIntl(withIdentity(Poll));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
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 { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -70,12 +71,8 @@ const messages = defineMessages({
}); });
class StatusActionBar extends ImmutablePureComponent { class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func, onReply: PropTypes.func,
onFavourite: PropTypes.func, onFavourite: PropTypes.func,
@ -112,7 +109,7 @@ class StatusActionBar extends ImmutablePureComponent {
]; ];
handleReplyClick = () => { handleReplyClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReply(this.props.status, this.props.history); this.props.onReply(this.props.status, this.props.history);
@ -128,7 +125,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleFavouriteClick = (e) => { handleFavouriteClick = (e) => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onFavourite(this.props.status, e); this.props.onFavourite(this.props.status, e);
@ -142,7 +139,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleReblogClick = e => { handleReblogClick = e => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReblog(this.props.status, e); this.props.onReblog(this.props.status, e);
@ -218,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () { render () {
const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
const { permissions, signedIn } = this.context.identity; const { permissions, signedIn } = this.props.identity;
const mutingConversation = status.get('muted'); const mutingConversation = status.get('muted');
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
@ -370,4 +367,4 @@ class StatusActionBar extends ImmutablePureComponent {
} }
export default withRouter(injectIntl(StatusActionBar)); export default withRouter(withIdentity(injectIntl(StatusActionBar)));

View file

@ -15,6 +15,7 @@ import LinkIcon from '@/material-icons/400-24px/link.svg?react';
import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; import MovieIcon from '@/material-icons/400-24px/movie.svg?react';
import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -126,12 +127,8 @@ const mapStateToProps = state => ({
}); });
class StatusContent extends PureComponent { class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string, statusContent: PropTypes.string,
expanded: PropTypes.bool, expanded: PropTypes.bool,
@ -349,7 +346,7 @@ class StatusContent extends PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const contentLocale = intl.locale.replace(/[_-].*/, ''); const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const content = { __html: statusContent ?? getStatusContent(status) }; const content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
@ -503,4 +500,4 @@ class StatusContent extends PureComponent {
} }
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent))); export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@ -15,6 +14,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary'; import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router'; import { Router } from 'flavours/glitch/components/router';
import UI from 'flavours/glitch/features/ui'; import UI from 'flavours/glitch/features/ui';
import { IdentityContext, createIdentityContext } from 'flavours/glitch/identity_context';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'; import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales'; import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store'; import { store } from 'flavours/glitch/store';
@ -33,33 +33,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis()); store.dispatch(fetchCustomEmojis());
} }
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent { export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState); identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() { componentDidMount() {
if (this.identity.signedIn) { if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream()); this.disconnect = store.dispatch(connectUserStream());
@ -79,6 +55,7 @@ export default class Mastodon extends PureComponent {
render () { render () {
return ( return (
<IdentityContext.Provider value={this.identity}>
<IntlProvider> <IntlProvider>
<ReduxProvider store={store}> <ReduxProvider store={store}>
<ErrorBoundary> <ErrorBoundary>
@ -92,6 +69,7 @@ export default class Mastodon extends PureComponent {
</ErrorBoundary> </ErrorBoundary>
</ReduxProvider> </ReduxProvider>
</IntlProvider> </IntlProvider>
</IdentityContext.Provider>
); );
} }

View file

@ -23,6 +23,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state'; import { autoPlayGif, me, domain as localDomain } 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';
@ -94,6 +95,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record, account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list, identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
@ -117,10 +119,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
static contextTypes = {
identity: PropTypes.object,
};
openEditProfile = () => { openEditProfile = () => {
window.open(profileLink, '_blank'); window.open(profileLink, '_blank');
}; };
@ -170,7 +168,7 @@ class Header extends ImmutablePureComponent {
render () { render () {
const { account, hidden, intl } = this.props; const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
if (!account) { if (!account) {
return null; return null;
@ -414,4 +412,4 @@ class Header extends ImmutablePureComponent {
} }
export default withRouter(injectIntl(Header)); export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class CommunityTimeline extends PureComponent { class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -80,7 +77,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia })); dispatch(expandCommunityTimeline({ onlyMedia }));
@ -90,7 +87,7 @@ class CommunityTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) { if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
@ -165,4 +162,4 @@ class CommunityTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
}; };
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet, recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool, submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
} }
_calculateOptions (value) { _calculateOptions (value) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const trimmedValue = value.trim(); const trimmedValue = value.trim();
const options = []; const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () { render () {
const { intl, value, submitted, recent } = this.props; const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state; const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted; const hasValue = value.length > 0 || submitted;
@ -402,4 +399,4 @@ class Search extends PureComponent {
} }
export default withRouter(injectIntl(Search)); export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import Search from 'flavours/glitch/features/compose/containers/search_container'; import Search from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { trendsEnabled } from 'flavours/glitch/initial_state'; import { trendsEnabled } from 'flavours/glitch/initial_state';
import Links from './links'; import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
}); });
class Explore extends PureComponent { class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
isSearching: PropTypes.bool, isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() { render() {
const { intl, multiColumn, isSearching } = this.props; const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(Explore)); export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,6 +6,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/flavours/glitch/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'flavours/glitch/actions/columns'; import { addColumn } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings'; import { changeSetting } from 'flavours/glitch/actions/settings';
@ -13,7 +14,7 @@ import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/act
import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import SettingText from 'flavours/glitch/components/setting_text'; import SettingText from 'flavours/glitch/components/setting_text';
import initialState, { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import Column from '../../components/column'; import Column from '../../components/column';
@ -26,15 +27,6 @@ const messages = defineMessages({
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
}); });
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => { const ColumnSettings = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View file

@ -28,6 +28,7 @@ import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; import LinkFooter from 'flavours/glitch/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -99,12 +100,8 @@ const badgeDisplay = (number, limit) => {
}; };
class GettingStarted extends ImmutablePureComponent { class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record, myAccount: ImmutablePropTypes.record,
columns: ImmutablePropTypes.list, columns: ImmutablePropTypes.list,
@ -123,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () { componentDidMount () {
const { fetchFollowRequests } = this.props; const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -134,7 +131,7 @@ class GettingStarted extends ImmutablePureComponent {
render () { render () {
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const navItems = []; const navItems = [];
let listItems = []; let listItems = [];
@ -219,4 +216,4 @@ class GettingStarted extends ImmutablePureComponent {
} }
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)); export default withIdentity(connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -16,7 +16,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.heading', defaultMessage: 'Misc' }, heading: { id: 'column.heading', defaultMessage: 'Misc' },
@ -32,11 +32,8 @@ const messages = defineMessages({
class GettingStartedMisc extends ImmutablePureComponent { class GettingStartedMisc extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -49,7 +46,7 @@ class GettingStartedMisc extends ImmutablePureComponent {
render () { render () {
const { intl } = this.props; const { intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column icon='ellipsis-h' iconComponent={MoreHorizIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton> <Column icon='ellipsis-h' iconComponent={MoreHorizIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
@ -69,4 +66,4 @@ class GettingStartedMisc extends ImmutablePureComponent {
} }
export default connect()(injectIntl(GettingStartedMisc)); export default connect()(withIdentity(injectIntl(GettingStartedMisc)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/ac
import { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/timelines'; import { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import StatusListContainer from '../ui/containers/status_list_container'; import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
}); });
class HashtagTimeline extends PureComponent { class HashtagTimeline extends PureComponent {
disconnects = []; disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
}; };
_subscribe (dispatch, id, tags = {}, local) { _subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => { handleFollow = () => {
const { dispatch, params, tag } = this.props; const { dispatch, params, tag } = this.props;
const { id } = params; const { id } = params;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props; const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params; const { id, local } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(HashtagTimeline); export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { criticalUpdatesPending } from 'flavours/glitch/initial_state'; import { criticalUpdatesPending } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -41,12 +42,8 @@ const mapStateToProps = state => ({
}); });
class HomeTimeline extends PureComponent { class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
@ -128,7 +125,7 @@ class HomeTimeline extends PureComponent {
render () { render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const banners = []; const banners = [];
let announcementsButton; let announcementsButton;
@ -192,4 +189,4 @@ class HomeTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(HomeTimeline)); export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions';
import { CheckboxWithLabel } from './checkbox_with_label'; import { CheckboxWithLabel } from './checkbox_with_label';
@ -13,13 +14,9 @@ import GrantPermissionButton from './grant_permission_button';
import PillBarButton from './pill_bar_button'; import PillBarButton from './pill_bar_button';
import SettingToggle from './setting_toggle'; import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent { class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -227,7 +224,7 @@ export default class ColumnSettings extends PureComponent {
</div> </div>
</section> </section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && ( {((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
<section role='group' aria-labelledby='notifications-admin-sign-up'> <section role='group' aria-labelledby='notifications-admin-sign-up'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3> <h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
@ -240,7 +237,7 @@ export default class ColumnSettings extends PureComponent {
</section> </section>
)} )}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && ( {((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'> <section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3> <h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -257,3 +254,5 @@ export default class ColumnSettings extends PureComponent {
} }
} }
export default withIdentity(ColumnSettings);

View file

@ -19,6 +19,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'flavours/glitch/compare_id'; import { compareId } from 'flavours/glitch/compare_id';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers'; import { submitMarkers } from '../../actions/markers';
@ -93,12 +94,8 @@ const mapDispatchToProps = dispatch => ({
}); });
class Notifications extends PureComponent { class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string, columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired, showFilterBar: PropTypes.bool.isRequired,
@ -225,7 +222,7 @@ class Notifications extends PureComponent {
const { animatingNCD } = this.state; const { animatingNCD } = this.state;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
let scrollableContent = null; let scrollableContent = null;
@ -373,4 +370,4 @@ class Notifications extends PureComponent {
} }
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Notifications)); export default connect(mapStateToProps, mapDispatchToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'flavours/glitch/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions'; import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { me, boostModal } from 'flavours/glitch/initial_state'; import { me, boostModal } from 'flavours/glitch/initial_state';
import { makeGetStatus } from 'flavours/glitch/selectors'; import { makeGetStatus } from 'flavours/glitch/selectors';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -48,12 +49,8 @@ const makeMapStateToProps = () => {
}; };
class Footer extends ImmutablePureComponent { class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired, statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => { handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props; const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -106,7 +103,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -133,7 +130,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => { handleReblogClick = e => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { if (status.get('reblogged')) {
@ -236,4 +233,4 @@ class Footer extends ImmutablePureComponent {
} }
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer))); export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -44,16 +45,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class PublicTimeline extends PureComponent { class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
@ -86,7 +83,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly })); dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly }));
if (signedIn) { if (signedIn) {
@ -95,7 +92,7 @@ class PublicTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) { if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
@ -170,4 +167,4 @@ class PublicTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(PublicTimeline)); export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
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 { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -62,12 +63,8 @@ const messages = defineMessages({
}); });
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func.isRequired, onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired,
@ -165,7 +162,7 @@ class ActionBar extends PureComponent {
render () { render () {
const { status, intl } = this.props; const { status, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -276,4 +273,4 @@ class ActionBar extends PureComponent {
} }
export default withRouter(injectIntl(ActionBar)); export default withRouter(withIdentity(injectIntl(ActionBar)));

View file

@ -21,6 +21,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -187,12 +188,8 @@ const titleFromStatus = (intl, status) => {
}; };
class Status extends ImmutablePureComponent { class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
@ -279,7 +276,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status, e) => { handleFavouriteClick = (status, e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -332,7 +329,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => { handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props; const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -372,7 +369,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => { handleReblogClick = (status, e) => {
const { settings, dispatch } = this.props; const { settings, dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
@ -810,4 +807,4 @@ class Status extends ImmutablePureComponent {
} }
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status))); export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
import ServerBanner from 'flavours/glitch/components/server_banner'; import ServerBanner from 'flavours/glitch/components/server_banner';
import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import LinkFooter from './link_footer'; import LinkFooter from './link_footer';
class ComposePanel extends PureComponent { class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -31,7 +28,7 @@ class ComposePanel extends PureComponent {
} }
render() { render() {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<div className='compose-panel'> <div className='compose-panel'>
@ -55,4 +52,4 @@ class ComposePanel extends PureComponent {
} }
export default connect()(ComposePanel); export default connect()(withIdentity(ComposePanel));

View file

@ -14,6 +14,7 @@ import { Avatar } from 'flavours/glitch/components/avatar';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo'; import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
import { Permalink } from 'flavours/glitch/components/permalink'; import { Permalink } from 'flavours/glitch/components/permalink';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state'; import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state';
const Account = connect(state => ({ const Account = connect(state => ({
@ -42,12 +43,8 @@ const mapDispatchToProps = (dispatch) => ({
}); });
class Header extends PureComponent { class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func, openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
signupUrl: PropTypes.string.isRequired, signupUrl: PropTypes.string.isRequired,
@ -61,7 +58,7 @@ class Header extends PureComponent {
} }
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content; let content;
@ -122,4 +119,4 @@ class Header extends PureComponent {
} }
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header))); export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state'; import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
import { logOut } from 'flavours/glitch/utils/log_out'; import { logOut } from 'flavours/glitch/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}); });
class LinkFooter extends PureComponent { class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired, onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
}; };
render () { render () {
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props; const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -108,4 +105,4 @@ class LinkFooter extends PureComponent {
} }
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter)); export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -30,6 +30,7 @@ import StarIcon from '@/material-icons/400-24px/star.svg?react';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; import { NavigationPortal } from 'flavours/glitch/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state'; import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { transientSingleColumn } from 'flavours/glitch/is_mobile'; import { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -98,12 +99,8 @@ const FollowRequestsLink = () => {
}; };
class NavigationPanel extends Component { class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
onOpenSettings: PropTypes.func, onOpenSettings: PropTypes.func,
}; };
@ -114,7 +111,7 @@ class NavigationPanel extends Component {
render () { render () {
const { intl, onOpenSettings } = this.props; const { intl, onOpenSettings } = this.props;
const { signedIn, disabledAccountId } = this.context.identity; const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined; let banner = undefined;
@ -188,4 +185,4 @@ class NavigationPanel extends Component {
} }
export default injectIntl(NavigationPanel); export default injectIntl(withIdentity(NavigationPanel));

View file

@ -17,6 +17,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavour
import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { Permalink } from 'flavours/glitch/components/permalink'; import { Permalink } from 'flavours/glitch/components/permalink';
import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture'; import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -129,12 +130,8 @@ const keyMap = {
}; };
class SwitchingColumnsArea extends PureComponent { class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node, children: PropTypes.node,
location: PropTypes.object, location: PropTypes.object,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
@ -169,7 +166,7 @@ class SwitchingColumnsArea extends PureComponent {
render () { render () {
const { children, singleColumn } = this.props; const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname; const pathName = this.props.location.pathname;
let redirect; let redirect;
@ -262,12 +259,8 @@ class SwitchingColumnsArea extends PureComponent {
} }
class UI extends PureComponent { class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
children: PropTypes.node, children: PropTypes.node,
isWide: PropTypes.bool, isWide: PropTypes.bool,
@ -323,7 +316,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target); this.dragTargets.push(e.target);
} }
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true }); this.setState({ draggingOver: true });
} }
}; };
@ -351,7 +344,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
this.dragTargets = []; this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) { if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
}; };
@ -403,7 +396,7 @@ class UI extends PureComponent {
}; };
componentDidMount () { componentDidMount () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('resize', this.handleResize, { passive: true });
@ -649,7 +642,7 @@ class UI extends PureComponent {
<Header /> <Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}> <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children} {children}
</SwitchingColumnsArea> </SwitchingColumnsArea>
@ -665,4 +658,4 @@ class UI extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(withRouter(UI))); export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'flavours/glitch/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
accessToken: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
accessToken: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -52,12 +52,22 @@
* @property {string} default_content_type * @property {string} default_content_type
*/ */
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/** /**
* @typedef InitialState * @typedef InitialState
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts * @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {Role?} role
* @property {object} local_settings * @property {object} local_settings
* @property {number} max_feed_hashtags * @property {number} max_feed_hashtags
* @property {number} poll_limits * @property {number} poll_limits

View file

@ -4621,6 +4621,10 @@ a.status-card {
&:hover { &:hover {
color: $primary-text-color; color: $primary-text-color;
} }
.icon {
transform: rotate(60deg);
}
} }
&:disabled { &:disabled {
@ -4668,6 +4672,10 @@ a.status-card {
padding: 0; padding: 0;
} }
.no-reduce-motion .column-header__button .icon {
transition: transform 150ms ease-in-out;
}
.column-header__collapsible { .column-header__collapsible {
max-height: 70vh; max-height: 70vh;
overflow: hidden; overflow: hidden;

View file

@ -20,7 +20,7 @@ export function changeSetting(path, value) {
} }
const debouncedSave = debounce((dispatch, getState) => { const debouncedSave = debounce((dispatch, getState) => {
if (getState().getIn(['settings', 'saved'])) { if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) {
return; return;
} }

View file

@ -172,7 +172,6 @@ Account.propTypes = {
onBlock: PropTypes.func, onBlock: PropTypes.func,
onMute: PropTypes.func, onMute: PropTypes.func,
onMuteNotifications: PropTypes.func, onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool, hidden: PropTypes.bool,
minimal: PropTypes.bool, minimal: PropTypes.bool,
defaultAction: PropTypes.string, defaultAction: PropTypes.string,

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router'; import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
}; };
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
title: PropTypes.node, title: PropTypes.node,
icon: PropTypes.string, icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
); );
} }
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) { if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = ( collapseButton = (
<button <button
className={collapsibleButtonClassName} className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
} }
export default injectIntl(withRouter(ColumnHeader)); export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import emojify from 'mastodon/features/emoji/emoji'; import emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion'; import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {}); }, {});
class Poll extends ImmutablePureComponent { class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map, poll: ImmutablePropTypes.map,
lang: PropTypes.string, lang: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul> </ul>
<div className='poll__footer'> <div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} {!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>} {!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>} {showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount} {votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
} }
export default injectIntl(Poll); export default injectIntl(withIdentity(Poll));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({
}); });
class StatusActionBar extends ImmutablePureComponent { class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record, relationship: ImmutablePropTypes.record,
onReply: PropTypes.func, onReply: PropTypes.func,
@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent {
]; ];
handleReplyClick = () => { handleReplyClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReply(this.props.status, this.props.history); this.props.onReply(this.props.status, this.props.history);
@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onFavourite(this.props.status); this.props.onFavourite(this.props.status);
@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleReblogClick = e => { handleReblogClick = e => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReblog(this.props.status, e); this.props.onReblog(this.props.status, e);
@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () { render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -410,4 +407,4 @@ class StatusActionBar extends ImmutablePureComponent {
} }
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar))); export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusActionBar))));

View file

@ -12,8 +12,10 @@ import { connect } from 'react-redux';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container'; import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/** /**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
}); });
class StatusContent extends PureComponent { class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string, statusContent: PropTypes.string,
expanded: PropTypes.bool, expanded: PropTypes.bool,
@ -245,7 +243,7 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed'); const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, ''); const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const content = { __html: statusContent ?? getStatusContent(status) }; const content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
@ -328,4 +326,4 @@ class StatusContent extends PureComponent {
} }
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent))); export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary'; import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router'; import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui'; import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state'; import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales'; import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store'; import { store } from 'mastodon/store';
@ -28,33 +28,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis()); store.dispatch(fetchCustomEmojis());
} }
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent { export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState); identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() { componentDidMount() {
if (this.identity.signedIn) { if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream()); this.disconnect = store.dispatch(connectUserStream());
@ -74,6 +50,7 @@ export default class Mastodon extends PureComponent {
render () { render () {
return ( return (
<IdentityContext.Provider value={this.identity}>
<IntlProvider> <IntlProvider>
<ReduxProvider store={store}> <ReduxProvider store={store}>
<ErrorBoundary> <ErrorBoundary>
@ -87,6 +64,7 @@ export default class Mastodon extends PureComponent {
</ErrorBoundary> </ErrorBoundary>
</ReduxProvider> </ReduxProvider>
</IntlProvider> </IntlProvider>
</IdentityContext.Provider>
); );
} }

View file

@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -111,6 +112,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record, account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list, identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => { setRef = c => {
this.node = c; this.node = c;
}; };
@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent {
render () { render () {
const { account, hidden, intl } = this.props; const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
if (!account) { if (!account) {
return null; return null;
@ -516,4 +514,4 @@ class Header extends ImmutablePureComponent {
} }
export default withRouter(injectIntl(Header)); export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class CommunityTimeline extends PureComponent { class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia })); dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) { if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
@ -161,4 +158,4 @@ class CommunityTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state'; import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags'; import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
}; };
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet, recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool, submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
} }
_calculateOptions (value) { _calculateOptions (value) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const trimmedValue = value.trim(); const trimmedValue = value.trim();
const options = []; const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () { render () {
const { intl, value, submitted, recent } = this.props; const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state; const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted; const hasValue = value.length > 0 || submitted;
@ -402,4 +399,4 @@ class Search extends PureComponent {
} }
export default withRouter(injectIntl(Search)); export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container'; import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state'; import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links'; import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
}); });
class Explore extends PureComponent { class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
isSearching: PropTypes.bool, isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() { render() {
const { intl, multiColumn, isSearching } = this.props; const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(Explore)); export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,13 +6,14 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns'; import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings'; import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column'; import Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' }, title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
}); });
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => { const ColumnSettings = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose'])); const settings = useAppSelector((state) => state.getIn(['settings', 'firehose']));

View file

@ -24,6 +24,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer'; import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, showTrends } from '../../initial_state'; import { me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar'; import { NavigationBar } from '../compose/components/navigation_bar';
@ -75,12 +76,8 @@ const badgeDisplay = (number, limit) => {
}; };
class GettingStarted extends ImmutablePureComponent { class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record, myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
@ -91,7 +88,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () { componentDidMount () {
const { fetchFollowRequests } = this.props; const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -102,7 +99,7 @@ class GettingStarted extends ImmutablePureComponent {
render () { render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props; const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const navItems = []; const navItems = [];
@ -167,4 +164,4 @@ class GettingStarted extends ImmutablePureComponent {
} }
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)); export default withIdentity(connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/t
import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines'; import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container'; import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
}); });
class HashtagTimeline extends PureComponent { class HashtagTimeline extends PureComponent {
disconnects = []; disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
}; };
_subscribe (dispatch, id, tags = {}, local) { _subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => { handleFollow = () => {
const { dispatch, params, tag } = this.props; const { dispatch, params, tag } = this.props;
const { id } = params; const { id } = params;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props; const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params; const { id, local } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(HashtagTimeline); export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state'; import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
}); });
class HomeTimeline extends PureComponent { class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () { render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const banners = []; const banners = [];
let announcementsButton; let announcementsButton;
@ -190,4 +187,4 @@ class HomeTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(HomeTimeline)); export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
import { CheckboxWithLabel } from './checkbox_with_label'; import { CheckboxWithLabel } from './checkbox_with_label';
@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button'; import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle'; import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent { class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
</div> </div>
</section> </section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && ( {((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
<section role='group' aria-labelledby='notifications-admin-sign-up'> <section role='group' aria-labelledby='notifications-admin-sign-up'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3> <h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
@ -228,7 +225,7 @@ export default class ColumnSettings extends PureComponent {
</section> </section>
)} )}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && ( {((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'> <section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3> <h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -245,3 +242,5 @@ export default class ColumnSettings extends PureComponent {
} }
} }
export default withIdentity(ColumnSettings);

View file

@ -17,6 +17,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'mastodon/compare_id'; import { compareId } from 'mastodon/compare_id';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers'; import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
}); });
class Notifications extends PureComponent { class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string, columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -190,7 +187,7 @@ class Notifications extends PureComponent {
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
let scrollableContent = null; let scrollableContent = null;
@ -299,4 +296,4 @@ class Notifications extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(Notifications)); export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions'; import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state'; import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors'; import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
}; };
class Footer extends ImmutablePureComponent { class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired, statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => { handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props; const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -131,7 +128,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => { handleReblogClick = e => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { if (status.get('reblogged')) {
@ -209,4 +206,4 @@ class Footer extends ImmutablePureComponent {
} }
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer))); export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class PublicTimeline extends PureComponent { class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props; const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props; const { dispatch, onlyMedia, onlyRemote } = this.props;
@ -164,4 +161,4 @@ class PublicTimeline extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(PublicTimeline)); export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({
}); });
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record, relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired, onReply: PropTypes.func.isRequired,
@ -198,7 +195,7 @@ class ActionBar extends PureComponent {
render () { render () {
const { status, relationship, intl } = this.props; const { status, relationship, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -326,4 +323,4 @@ class ActionBar extends PureComponent {
} }
export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar))); export default withRouter(connect(mapStateToProps)(withIdentity(injectIntl(ActionBar))));

View file

@ -20,6 +20,7 @@ import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { import {
@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => {
}; };
class Status extends ImmutablePureComponent { class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => { handleFavouriteClick = (status) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => { handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props; const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -307,7 +304,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => { handleReblogClick = (status, e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { if (status.get('reblogged')) {
@ -745,4 +742,4 @@ class Status extends ImmutablePureComponent {
} }
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status))); export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/
import ServerBanner from 'mastodon/components/server_banner'; import ServerBanner from 'mastodon/components/server_banner';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container'; import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container'; import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer'; import LinkFooter from './link_footer';
class ComposePanel extends PureComponent { class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
} }
render() { render() {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<div className='compose-panel' onFocus={this.onFocus}> <div className='compose-panel' onFocus={this.onFocus}>
@ -65,4 +62,4 @@ class ComposePanel extends PureComponent {
} }
export default connect()(ComposePanel); export default connect()(withIdentity(ComposePanel));

View file

@ -13,6 +13,7 @@ import { fetchServer } from 'mastodon/actions/server';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo'; import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state'; import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({ const Account = connect(state => ({
@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({
}); });
class Header extends PureComponent { class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func, openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
signupUrl: PropTypes.string.isRequired, signupUrl: PropTypes.string.isRequired,
@ -60,7 +57,7 @@ class Header extends PureComponent {
} }
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content; let content;
@ -121,4 +118,4 @@ class Header extends PureComponent {
} }
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header))); export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state'; import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out'; import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}); });
class LinkFooter extends PureComponent { class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired, onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
}; };
render () { render () {
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props; const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -108,4 +105,4 @@ class LinkFooter extends PureComponent {
} }
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter)); export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -31,6 +31,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo'; import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal'; import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile'; import { transientSingleColumn } from 'mastodon/is_mobile';
@ -97,12 +98,8 @@ const FollowRequestsLink = () => {
}; };
class NavigationPanel extends Component { class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -112,7 +109,7 @@ class NavigationPanel extends Component {
render () { render () {
const { intl } = this.props; const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity; const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined; let banner = undefined;
@ -189,4 +186,4 @@ class NavigationPanel extends Component {
} }
export default injectIntl(NavigationPanel); export default injectIntl(withIdentity(NavigationPanel));

View file

@ -15,6 +15,7 @@ import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture'; import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile'; import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -120,12 +121,8 @@ const keyMap = {
}; };
class SwitchingColumnsArea extends PureComponent { class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node, children: PropTypes.node,
location: PropTypes.object, location: PropTypes.object,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
@ -160,7 +157,7 @@ class SwitchingColumnsArea extends PureComponent {
render () { render () {
const { children, singleColumn } = this.props; const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname; const pathName = this.props.location.pathname;
let redirect; let redirect;
@ -252,12 +249,8 @@ class SwitchingColumnsArea extends PureComponent {
} }
class UI extends PureComponent { class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
children: PropTypes.node, children: PropTypes.node,
isComposing: PropTypes.bool, isComposing: PropTypes.bool,
@ -309,7 +302,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target); this.dragTargets.push(e.target);
} }
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true }); this.setState({ draggingOver: true });
} }
}; };
@ -337,7 +330,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
this.dragTargets = []; this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) { if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
}; };
@ -389,7 +382,7 @@ class UI extends PureComponent {
}; };
componentDidMount () { componentDidMount () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false); window.addEventListener('blur', this.handleWindowBlur, false);
@ -586,7 +579,7 @@ class UI extends PureComponent {
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}> <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header /> <Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}> <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children} {children}
</SwitchingColumnsArea> </SwitchingColumnsArea>
@ -602,4 +595,4 @@ class UI extends PureComponent {
} }
export default connect(mapStateToProps)(injectIntl(withRouter(UI))); export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'mastodon/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
accessToken: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
accessToken: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -44,12 +44,22 @@
* @property {string} sso_redirect * @property {string} sso_redirect
*/ */
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/** /**
* @typedef InitialState * @typedef InitialState
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts * @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {Role?} role
*/ */
const element = document.getElementById('initial-state'); const element = document.getElementById('initial-state');

View file

@ -474,7 +474,7 @@
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn", "notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
"notification.mention": "Crybwyllodd {name} amdanoch chi", "notification.mention": "Crybwyllodd {name} amdanoch chi",
"notification.moderation-warning.learn_more": "Dysgu mwy", "notification.moderation-warning.learn_more": "Dysgu mwy",
"notification.moderation_warning": "Rydych wedi derbyn rhybudd cymedroli", "notification.moderation_warning": "Rydych wedi derbyn rhybudd gan gymedrolwr",
"notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.", "notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.",
"notification.moderation_warning.action_disable": "Mae eich cyfrif wedi'i analluogi.", "notification.moderation_warning.action_disable": "Mae eich cyfrif wedi'i analluogi.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Mae rhai o'ch postiadau wedi'u marcio'n sensitif.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Mae rhai o'ch postiadau wedi'u marcio'n sensitif.",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.", "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"follow_suggestions.curated_suggestion": "Staff pick", "follow_suggestions.curated_suggestion": "Staff pick",
"follow_suggestions.dismiss": "Don't show again", "follow_suggestions.dismiss": "Don't show again",
"follow_suggestions.featured_longer": "Hand-picked by the {domain} team",
"follow_suggestions.friends_of_friends_longer": "Popular among people you follow",
"follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.", "follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.",
"follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.", "follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.",
"follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.", "follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.", "follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.",
"follow_suggestions.personalized_suggestion": "Personalised suggestion", "follow_suggestions.personalized_suggestion": "Personalised suggestion",
"follow_suggestions.popular_suggestion": "Popular suggestion", "follow_suggestions.popular_suggestion": "Popular suggestion",
"follow_suggestions.popular_suggestion_longer": "Popular on {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar to profiles you recently followed",
"follow_suggestions.view_all": "View all", "follow_suggestions.view_all": "View all",
"follow_suggestions.who_to_follow": "Who to follow", "follow_suggestions.who_to_follow": "Who to follow",
"followed_tags": "Followed hashtags", "followed_tags": "Followed hashtags",
@ -469,6 +473,15 @@
"notification.follow": "{name} followed you", "notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you", "notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you", "notification.mention": "{name} mentioned you",
"notification.moderation-warning.learn_more": "Learn more",
"notification.moderation_warning": "You have received a moderation warning",
"notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
"notification.moderation_warning.action_disable": "Your account has been disabled.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.",
"notification.moderation_warning.action_none": "Your account has received a moderation warning.",
"notification.moderation_warning.action_sensitive": "Your posts will be marked as sensitive from now on.",
"notification.moderation_warning.action_silence": "Your account has been limited.",
"notification.moderation_warning.action_suspend": "Your account has been suspended.",
"notification.own_poll": "Your poll has ended", "notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended", "notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status", "notification.reblog": "{name} boosted your status",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más", "notification.moderation-warning.learn_more": "Saber más",
"notification.moderation_warning": "Has recibido una advertencia de moderación", "notification.moderation_warning": "Has recibido una advertencia de moderación",
"notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.",
"notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.", "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.",
"notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.",
"notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.",
"notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.", "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.", "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado", "notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha retooteado tu estado", "notification.reblog": "{name} ha retooteado tu estado",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más", "notification.moderation-warning.learn_more": "Saber más",
"notification.moderation_warning": "Has recibido una advertencia de moderación", "notification.moderation_warning": "Has recibido una advertencia de moderación",
"notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.",
"notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.", "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.",
"notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.", "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.",
"notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.", "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.",
"notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.", "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.", "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado", "notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha impulsado tu publicación", "notification.reblog": "{name} ha impulsado tu publicación",

View file

@ -474,11 +474,11 @@
"notification.follow_request": "{name} biður um at fylgja tær", "notification.follow_request": "{name} biður um at fylgja tær",
"notification.mention": "{name} nevndi teg", "notification.mention": "{name} nevndi teg",
"notification.moderation-warning.learn_more": "Lær meira", "notification.moderation-warning.learn_more": "Lær meira",
"notification.moderation_warning": "Tú hevur móttikið eina umsjónarávarðing", "notification.moderation_warning": "Tú hevur móttikið eina umsjónarávaring",
"notification.moderation_warning.action_delete_statuses": "Onkrir av tínum postum eru strikaðir.", "notification.moderation_warning.action_delete_statuses": "Onkrir av tínum postum eru strikaðir.",
"notification.moderation_warning.action_disable": "Konta tín er gjørd óvirkin.", "notification.moderation_warning.action_disable": "Konta tín er gjørd óvirkin.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Nakrir av postum tínum eru merktir sum viðkvæmir.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nakrir av postum tínum eru merktir sum viðkvæmir.",
"notification.moderation_warning.action_none": "Konta tín hevur móttikið eina umsjónarávarðing.", "notification.moderation_warning.action_none": "Konta tín hevur móttikið eina umsjónarávaring.",
"notification.moderation_warning.action_sensitive": "Postar tínir verða merktir sum viðkvæmir frá nú av.", "notification.moderation_warning.action_sensitive": "Postar tínir verða merktir sum viðkvæmir frá nú av.",
"notification.moderation_warning.action_silence": "Konta tín er avmarkað.", "notification.moderation_warning.action_silence": "Konta tín er avmarkað.",
"notification.moderation_warning.action_suspend": "Konta tín er ógildað.", "notification.moderation_warning.action_suspend": "Konta tín er ógildað.",

View file

@ -92,7 +92,7 @@
"block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.", "block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.",
"block_modal.show_less": "Mostrar menos", "block_modal.show_less": "Mostrar menos",
"block_modal.show_more": "Mostrar máis", "block_modal.show_more": "Mostrar máis",
"block_modal.they_cant_mention": "Non te pode seguir nin mencionar.", "block_modal.they_cant_mention": "Non te poden seguir nin mencionar.",
"block_modal.they_cant_see_posts": "Non pode ver as túas publicacións nin ti as de ela.", "block_modal.they_cant_see_posts": "Non pode ver as túas publicacións nin ti as de ela.",
"block_modal.they_will_know": "Pode ver que a bloqueaches.", "block_modal.they_will_know": "Pode ver que a bloqueaches.",
"block_modal.title": "Bloquear usuaria?", "block_modal.title": "Bloquear usuaria?",

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}", "account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar", "account.block_short": "Blocar",
"account.blocked": "Blocate", "account.blocked": "Blocate",
"account.browse_more_on_origin_server": "Navigar plus sur le profilo original", "account.browse_more_on_origin_server": "Percurrer plus sur le profilo original",
"account.cancel_follow_request": "Cancellar sequimento", "account.cancel_follow_request": "Cancellar sequimento",
"account.copy": "Copiar ligamine a profilo", "account.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}", "account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private", "column.direct": "Mentiones private",
"column.directory": "Navigar profilos", "column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate", "column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos", "column.favourites": "Favorites",
"column.firehose": "Fluxos in directo", "column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento", "column.follow_requests": "Requestas de sequimento",
"column.home": "Initio", "column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto", "disabled_account_banner.account_settings": "Parametros de conto",
"disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.", "disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.",
"dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.", "dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.",
"dismissable_banner.dismiss": "Dimitter", "dismissable_banner.dismiss": "Clauder",
"dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.", "dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
"dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.", "dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
"dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.", "dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
@ -212,8 +212,8 @@
"domain_block_modal.block": "Blocar le servitor", "domain_block_modal.block": "Blocar le servitor",
"domain_block_modal.block_account_instead": "Blocar @{name} in su loco", "domain_block_modal.block_account_instead": "Blocar @{name} in su loco",
"domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.", "domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.",
"domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.", "domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.", "domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?", "domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.", "domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.",
"domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.", "domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.",
@ -307,7 +307,7 @@
"follow_request.reject": "Rejectar", "follow_request.reject": "Rejectar",
"follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.", "follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.",
"follow_suggestions.curated_suggestion": "Selection del equipa", "follow_suggestions.curated_suggestion": "Selection del equipa",
"follow_suggestions.dismiss": "Non monstrar novemente", "follow_suggestions.dismiss": "Non monstrar de novo",
"follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}", "follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque", "follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque",
"follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.", "follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
@ -412,7 +412,7 @@
"lightbox.next": "Sequente", "lightbox.next": "Sequente",
"lightbox.previous": "Precedente", "lightbox.previous": "Precedente",
"limited_account_hint.action": "Monstrar profilo in omne caso", "limited_account_hint.action": "Monstrar profilo in omne caso",
"limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.", "limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
"link_preview.author": "Per {name}", "link_preview.author": "Per {name}",
"lists.account.add": "Adder al lista", "lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista", "lists.account.remove": "Remover del lista",
@ -432,12 +432,12 @@
"loading_indicator.label": "Cargante…", "loading_indicator.label": "Cargante…",
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}", "media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}",
"moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.", "moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.",
"mute_modal.hide_from_notifications": "Celar ab notificationes", "mute_modal.hide_from_notifications": "Celar in notificationes",
"mute_modal.hide_options": "Celar optiones", "mute_modal.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona", "mute_modal.indefinite": "Usque io dissilentia iste persona",
"mute_modal.show_options": "Monstrar optiones", "mute_modal.show_options": "Monstrar optiones",
"mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.", "mute_modal.they_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.",
"mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.", "mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.",
"mute_modal.title": "Silentiar le usator?", "mute_modal.title": "Silentiar le usator?",
"mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.", "mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.",
"mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.", "mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.",
@ -451,13 +451,13 @@
"navigation_bar.discover": "Discoperir", "navigation_bar.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate", "navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar", "navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos", "navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate", "navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento", "navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite", "navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores", "navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session", "navigation_bar.logout": "Clauder session",
"navigation_bar.mutes": "Usatores silentiate", "navigation_bar.mutes": "Usatores silentiate",
"navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.", "navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.",
"navigation_bar.personal": "Personal", "navigation_bar.personal": "Personal",
@ -474,7 +474,7 @@
"notification.follow_request": "{name} ha requestate de sequer te", "notification.follow_request": "{name} ha requestate de sequer te",
"notification.mention": "{name} te ha mentionate", "notification.mention": "{name} te ha mentionate",
"notification.moderation-warning.learn_more": "Apprender plus", "notification.moderation-warning.learn_more": "Apprender plus",
"notification.moderation_warning": "Tu ha recipite un advertimento de moderation", "notification.moderation_warning": "Tu ha recepite un aviso de moderation",
"notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.", "notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.",
"notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.", "notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.",
@ -501,7 +501,7 @@
"notifications.column_settings.admin.report": "Nove signalationes:", "notifications.column_settings.admin.report": "Nove signalationes:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:", "notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio", "notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.favourite": "Favoritos:", "notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias", "notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide", "notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:", "notifications.column_settings.follow": "Nove sequitores:",
@ -518,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:", "notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto", "notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos", "notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos", "notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites", "notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones", "notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage", "notifications.filter.polls": "Resultatos del sondage",
@ -717,7 +717,7 @@
"status.edited": "Ultime modification le {date}", "status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}", "status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar", "status.embed": "Incastrar",
"status.favourite": "Adder al favoritos", "status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message", "status.filter": "Filtrar iste message",
"status.filtered": "Filtrate", "status.filtered": "Filtrate",

View file

@ -89,6 +89,7 @@
"announcement.announcement": "Proclamation", "announcement.announcement": "Proclamation",
"attachments_list.unprocessed": "(íntractat)", "attachments_list.unprocessed": "(íntractat)",
"audio.hide": "Celar audio", "audio.hide": "Celar audio",
"block_modal.remote_users_caveat": "Noi va petir que li servitor {domain} mey respecter tui decision. Támen, obedientie ne es garantit pro que chascun servitor gere bloccas diferentmen. Possibilmen public postas va restar visibil a usatores de inloggat.",
"block_modal.show_less": "Monstrar minu", "block_modal.show_less": "Monstrar minu",
"block_modal.show_more": "Monstrar plu", "block_modal.show_more": "Monstrar plu",
"block_modal.they_cant_mention": "Ne posse mentionar ni sequer te.", "block_modal.they_cant_mention": "Ne posse mentionar ni sequer te.",
@ -224,7 +225,10 @@
"domain_pill.their_username": "Su unic identificator sur su servitor. It es possibil que altri servitores va haver usatores con li sam nómine.", "domain_pill.their_username": "Su unic identificator sur su servitor. It es possibil que altri servitores va haver usatores con li sam nómine.",
"domain_pill.username": "Usator-nómine", "domain_pill.username": "Usator-nómine",
"domain_pill.whats_in_a_handle": "Ex quo consiste un identificator?", "domain_pill.whats_in_a_handle": "Ex quo consiste un identificator?",
"domain_pill.who_they_are": "Pro que identificatores informa qui e u un person is, tu posse interacter con persones tra li rete social de <button>ActivityPub-usant platformes</button>.",
"domain_pill.who_you_are": "Pro que tui identificator informa qui e u tu es, persones posse interacter con te tra li rete social de <button>ActivityPub-usant platformes</button>.",
"domain_pill.your_handle": "Tui identificator:", "domain_pill.your_handle": "Tui identificator:",
"domain_pill.your_server": "Tui digital hem, u trova se omni tui postas. Si it ne plese te, tu posse transferer ad un altri servitor quandecunc e tui sequitores con te.",
"domain_pill.your_username": "Tui unic identificator sur ti-ci servitor. It es possibil que altri servitores va haver usatores con li sam nómine.", "domain_pill.your_username": "Tui unic identificator sur ti-ci servitor. It es possibil que altri servitores va haver usatores con li sam nómine.",
"embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.", "embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.",
"embed.preview": "Vi qualmen it va aspecter:", "embed.preview": "Vi qualmen it va aspecter:",

View file

@ -474,7 +474,7 @@
"notification.follow_request": "{name}さんがあなたにフォローリクエストしました", "notification.follow_request": "{name}さんがあなたにフォローリクエストしました",
"notification.mention": "{name}さんがあなたに返信しました", "notification.mention": "{name}さんがあなたに返信しました",
"notification.moderation-warning.learn_more": "さらに詳しく", "notification.moderation-warning.learn_more": "さらに詳しく",
"notification.moderation_warning": "あなたは管理者からの警告を受けています。", "notification.moderation_warning": "管理者から警告が来ています",
"notification.moderation_warning.action_delete_statuses": "あなたによるいくつかの投稿が削除されました。", "notification.moderation_warning.action_delete_statuses": "あなたによるいくつかの投稿が削除されました。",
"notification.moderation_warning.action_disable": "あなたのアカウントは無効になりました。", "notification.moderation_warning.action_disable": "あなたのアカウントは無効になりました。",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "あなたの投稿のいくつかは閲覧注意として判定されています。", "notification.moderation_warning.action_mark_statuses_as_sensitive": "あなたの投稿のいくつかは閲覧注意として判定されています。",

View file

@ -256,7 +256,7 @@
"empty_column.community": "Vietinė laiko skalė yra tuščia. Parašyk ką nors viešai, kad pradėtum sąveikauti.", "empty_column.community": "Vietinė laiko skalė yra tuščia. Parašyk ką nors viešai, kad pradėtum sąveikauti.",
"empty_column.direct": "Dar neturi jokių privačių paminėjimų. Kai išsiųsi arba gausi vieną iš jų, jis bus rodomas čia.", "empty_column.direct": "Dar neturi jokių privačių paminėjimų. Kai išsiųsi arba gausi vieną iš jų, jis bus rodomas čia.",
"empty_column.domain_blocks": "Dar nėra užblokuotų domenų.", "empty_column.domain_blocks": "Dar nėra užblokuotų domenų.",
"empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrink vėliau.", "empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrink vėliau!",
"empty_column.favourited_statuses": "Dar neturi mėgstamų įrašų. Kai vieną iš jų pamėgsi, jis bus rodomas čia.", "empty_column.favourited_statuses": "Dar neturi mėgstamų įrašų. Kai vieną iš jų pamėgsi, jis bus rodomas čia.",
"empty_column.favourites": "Šio įrašo dar niekas nepamėgo. Kai kas nors tai padarys, jie bus rodomi čia.", "empty_column.favourites": "Šio įrašo dar niekas nepamėgo. Kai kas nors tai padarys, jie bus rodomi čia.",
"empty_column.follow_requests": "Dar neturi jokių sekimo prašymų. Kai gausi tokį prašymą, jis bus rodomas čia.", "empty_column.follow_requests": "Dar neturi jokių sekimo prašymų. Kai gausi tokį prašymą, jis bus rodomas čia.",
@ -466,7 +466,6 @@
"notification.follow_request": "{name} paprašė tave sekti", "notification.follow_request": "{name} paprašė tave sekti",
"notification.mention": "{name} paminėjo tave", "notification.mention": "{name} paminėjo tave",
"notification.moderation-warning.learn_more": "Sužinoti daugiau", "notification.moderation-warning.learn_more": "Sužinoti daugiau",
"notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą",
"notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.", "notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.",
"notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.", "notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.",
@ -536,7 +535,7 @@
"onboarding.follows.lead": "Tavo pagrindinis srautas pagrindinis būdas patirti Mastodon. Kuo daugiau žmonių seksi, tuo jis bus aktyvesnis ir įdomesnis. Norint pradėti, pateikiame keletą pasiūlymų:", "onboarding.follows.lead": "Tavo pagrindinis srautas pagrindinis būdas patirti Mastodon. Kuo daugiau žmonių seksi, tuo jis bus aktyvesnis ir įdomesnis. Norint pradėti, pateikiame keletą pasiūlymų:",
"onboarding.follows.title": "Suasmenink savo pagrindinį srautą", "onboarding.follows.title": "Suasmenink savo pagrindinį srautą",
"onboarding.profile.discoverable": "Padaryti mano profilį atrandamą", "onboarding.profile.discoverable": "Padaryti mano profilį atrandamą",
"onboarding.profile.discoverable_hint": "Kai pasirenki Mastodon atrandamumą, tavo įrašai gali būti rodomi paieškos rezultatuose ir tendencijose, o profilis gali būti siūlomas panašių pomėgių turintiems žmonėms.", "onboarding.profile.discoverable_hint": "Kai sutinki su Mastodon atrandamumu, tavo įrašai gali būti rodomi paieškos rezultatuose ir tendencijose, o profilis gali būti siūlomas panašių pomėgių turintiems žmonėms.",
"onboarding.profile.display_name": "Rodomas vardas", "onboarding.profile.display_name": "Rodomas vardas",
"onboarding.profile.display_name_hint": "Tavo pilnas vardas arba linksmas vardas…", "onboarding.profile.display_name_hint": "Tavo pilnas vardas arba linksmas vardas…",
"onboarding.profile.lead": "Gali visada tai užbaigti vėliau nustatymuose, kur yra dar daugiau pritaikymo parinkčių.", "onboarding.profile.lead": "Gali visada tai užbaigti vėliau nustatymuose, kur yra dar daugiau pritaikymo parinkčių.",

View file

@ -474,7 +474,6 @@
"notification.follow_request": "{name} quer te seguir", "notification.follow_request": "{name} quer te seguir",
"notification.mention": "{name} te mencionou", "notification.mention": "{name} te mencionou",
"notification.moderation-warning.learn_more": "Aprender mais", "notification.moderation-warning.learn_more": "Aprender mais",
"notification.moderation_warning": "Você recebeu um aviso de moderação",
"notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.", "notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.",
"notification.moderation_warning.action_disable": "Sua conta foi desativada.", "notification.moderation_warning.action_disable": "Sua conta foi desativada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.",

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