Compare commits

..

84 commits

Author SHA1 Message Date
Jeremy Kescher
6c28810b39
[Glitch+Emoji reactions] Use modern React context for for identity for emoji reaction code 2024-05-20 21:50:13 -05:00
Essem
48b9837ad8
Fix building on systems with ICU 75
See brianmario/charlock_holmes#172
2024-05-20 21:36:49 -05:00
Essem
9b0c46ebf2
Merge remote-tracking branch 'upstream/main' 2024-05-20 21:35:02 -05:00
Claire
e853355f24
Merge pull request #2714 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 0a2110b9af
2024-05-20 20:23:13 +02:00
Claire
20fdf8e22c Merge commit '0a2110b9af52005798251dc9d245a66dd5dd20fa' into glitch-soc/merge-upstream 2024-05-20 17:47:48 +02:00
Claire
7b3eef4cb1
Merge pull request #2713 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to de4815afda
2024-05-20 17:27:55 +02:00
Matt Jankowski
0a2110b9af
Add coverage for custom filters (#30347) 2024-05-20 15:00:09 +00:00
Claire
00cf8d3748
Change older Paperclip database migrations for consistency (#30204) 2024-05-20 14:59:27 +00:00
Claire
2bcbeed951
Add some error handling to OTP secret migration (#30344) 2024-05-20 14:59:23 +00:00
Renaud Chaput
ca5955ed76 [Glitch] Use a modern React context for identity in the app
Port a178ba7cd5 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-05-20 12:53:52 +02:00
Claire
e46321e63d Merge commit 'de4815afda0809bf999519aabda1cd14c67278da' into glitch-soc/merge-upstream 2024-05-20 12:17:36 +02:00
Claire
de4815afda
Add more tests for self-destruct mode (#30374) 2024-05-20 10:06:51 +00:00
Matt Jankowski
def6b686ff
Fix Rails/WhereRange cop (#30343) 2024-05-20 09:37:36 +00:00
Matt Jankowski
70608f824e
Add coverage for AdminMailer#auto_close_registrations (#30349) 2024-05-20 08:03:39 +00:00
Renaud Chaput
9658d3e580
Use the job class as span name for Sidekiq root spans (#30353) 2024-05-20 08:01:04 +00:00
renovate[bot]
0ce22859a5
fix(deps): update dependency @rails/ujs to v7.1.3 (#30356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 07:56:28 +00:00
github-actions[bot]
8b75d18371
New Crowdin Translations (automated) (#30358)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-05-20 07:41:38 +00:00
renovate[bot]
814d00cf4b
chore(deps): update dependency @formatjs/cli to v6.2.12 (#30370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 07:40:37 +00:00
renovate[bot]
778bd96a52
chore(deps): update dependency @types/lodash to v4.17.4 (#30371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 07:40:20 +00:00
Renaud Chaput
990a0c19a9
Fix a warning when running JS Tests because of FakeIdentityContext using deprecated context API (#30368) 2024-05-20 07:29:27 +00:00
Nick Schonning
0f07e1cd4c
Fix yarn.lock diff (#30366) 2024-05-19 19:37:49 +00:00
renovate[bot]
0a343b9a91
fix(deps): update react monorepo to v18.3.1 (#30074)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 17:15:06 +00:00
Renaud Chaput
a178ba7cd5
Use a modern React context for identity in the app (#30098) 2024-05-19 17:07:32 +00:00
Joshua Byrd
6282b6da77
Fix og:image requests when html in a web page is over 1.megabyte (#30362) 2024-05-19 16:30:05 +00:00
Claire
93a617236e
Merge pull request #2712 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 12472e7f40
2024-05-17 18:05:32 +02:00
Emelia Smith
2da2a1dae9
Support multiple redirect_uris when creating OAuth 2.0 Applications (#29192) 2024-05-17 13:46:12 +00:00
Claire
57fb2cf948 Merge commit '12472e7f407c42bcff6ee204b9f1887b5824734f' into glitch-soc/merge-upstream 2024-05-17 12:33:41 +02:00
Claire
9668ac5acd
Merge pull request #2711 from ClearlyClaire/glitch-soc/merge-upstream
Port upstream changes up to b2388be71e
2024-05-17 12:32:59 +02:00
Claire
12472e7f40
Add emphasis on ActiveRecord Encryption configuration values being secret (#30340) 2024-05-17 09:28:40 +00:00
Jeong Arm
a627219b25
Fix moderation action logs (#30342) 2024-05-17 09:18:54 +00:00
github-actions[bot]
bff7769f5f
New Crowdin Translations (automated) (#30336)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-05-17 08:41:35 +00:00
Matt Jankowski
a6d12299f2
Remove duplicate method def ApplicationHelper#instance_presenter (#30331) 2024-05-17 08:29:13 +00:00
renovate[bot]
fc166d07f0
chore(deps): update dependency rails to v7.1.3.3 (#30334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 08:28:22 +00:00
renovate[bot]
3286ad5226
chore(deps): update dependency selenium-webdriver to v4.21.1 (#30335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 08:28:01 +00:00
renovate[bot]
f7f5b9dadd
fix(deps): update dependency @rails/ujs to v7.1.3-3 (#30337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 08:27:45 +00:00
renovate[bot]
226d7a7bad
fix(deps): update dependency sass to v1.77.2 (#30338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 08:27:03 +00:00
Claire
87b5cfc60e
Merge pull request #2710 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to b2388be71e
2024-05-16 21:30:31 +02:00
Claire
7b078b46a2 [Glitch] Fix Web UI trying to save user settings when logged out
Port 66906a1bc1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-05-16 19:34:50 +02:00
David Lapshin
061464a563 [Glitch] Fix incorrect element selector from #30221
Port 94493cff92 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-05-16 19:34:17 +02:00
Claire
2810231180 Merge commit 'b2388be71eb0031ef9e47c492b1c038231cd8bc0' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/activitypub/collections_controller.rb`:
  Upstream renamed a helper method everywhere.
  There was one glitch-soc line involving changes because of the local-only post
  feature.
  Ported upstream's change.
2024-05-16 19:30:10 +02:00
renovate[bot]
b2388be71e
chore(deps): update dependency selenium-webdriver to v4.21.0 (#30325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 13:45:17 +00:00
Claire
66906a1bc1
Fix Web UI trying to save user settings when logged out (#30324) 2024-05-16 13:01:01 +00:00
Matt Jankowski
1b6eb2c7f0
Enable YJIT when available (#30310) 2024-05-16 09:56:48 +00:00
Renaud Chaput
283a891e92
Allow to customise the OTEL service name prefix (#30322) 2024-05-16 09:28:10 +00:00
Matt Jankowski
65e82211cd
Rename cache_* methods to preload_* in controller concern (#30209) 2024-05-16 08:03:46 +00:00
Emelia Smith
f0d6dc4519
Fix: Mark redirect uris field in Development > Application form as required (#30311) 2024-05-16 07:59:46 +00:00
renovate[bot]
356bbbaa7f
fix(deps): update dependency @reduxjs/toolkit to v2.2.5 (#30320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 07:57:19 +00:00
github-actions[bot]
cdb042ae86
New Crowdin Translations (automated) (#30319)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-05-16 07:50:19 +00:00
Matt Jankowski
c9ee1437c0
Use ruby language constants to build version string in software version dimension (#30309) 2024-05-16 07:43:35 +00:00
renovate[bot]
60b423b3f7
chore(deps): update dependency rspec-sidekiq to v5 (#30314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 07:43:31 +00:00
David Lapshin
94493cff92
Fix incorrect element selector from #30221 (#30307) 2024-05-16 07:33:29 +00:00
Matt Jankowski
ca560c1095
Disable Style/RedundantFetchBlock cop (#30207) 2024-05-15 13:57:13 +00:00
renovate[bot]
d5d3a0fc57
fix(deps): update dependency pino to v9.1.0 (#30283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 13:38:51 +00:00
Emelia Smith
5fd56512de
Improve Report Notes and Account Moderation Notes (#30288) 2024-05-15 13:38:36 +00:00
Claire
c2ca3d152f
Fix off-by-one in tootctl media commands (#30306) 2024-05-15 13:11:13 +00:00
renovate[bot]
4e085dff52
chore(deps): update dependency aws-sdk-s3 to v1.151.0 (#30287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 13:05:05 +00:00
Claire
19ed3d9441
Merge pull request #2709 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 1bf661cddb
2024-05-15 12:10:10 +02:00
Jason Punyon
85c625d319
Fix repetitive database queries from #30040 (#30259) 2024-05-15 09:38:16 +00:00
Matt Jankowski
6beead3867
Move simplecov config into rails_helper (#30302) 2024-05-15 09:33:36 +00:00
github-actions[bot]
aad5e841b5
New Crowdin Translations (automated) (#30290)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-05-15 08:53:57 +00:00
renovate[bot]
78a8263f73
fix(deps): update dependency postcss-preset-env to v9.5.13 (#30286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:34:58 +00:00
renovate[bot]
7f7eba8753
chore(deps): update dependency letter_opener_web to v3 (#30296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:34:50 +00:00
Renaud Chaput
40639510f8
Retain unconfirmed users longer (1 week) (#30285) 2024-05-15 08:27:34 +00:00
renovate[bot]
44e855db78
chore(deps): update dependency nokogiri to v1.16.5 [security] (#30289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:26:22 +00:00
renovate[bot]
508e93eb64
chore(deps): update dependency fog-openstack to v1.1.1 (#30295)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:25:50 +00:00
renovate[bot]
38a330f963
fix(deps): update dependency core-js to v3.37.1 (#30293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 08:25:34 +00:00
Renaud Chaput
47ce5f4ca9 [Glitch] Fix missing prop warning for <Account>
Port 1bf661cddb to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-05-14 20:53:40 +02:00
David Lapshin
160823716a [Glitch] Add active animation to header settings button
Port cb93c1edf0 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2024-05-14 20:53:01 +02:00
Claire
666760f450 Merge commit '1bf661cddbc614d4076e9d9e855575fc29e976c0' into glitch-soc/merge-upstream
Conflicts:
- `package.json`:
  Upstream fixed a command we have modified in glitch-soc.
  Updated as upstream did.
2024-05-14 20:47:14 +02:00
Renaud Chaput
1bf661cddb
Fix missing prop warning for <Account> (#30291) 2024-05-14 18:15:42 +00:00
Claire
b5b84fad65
Fix OpenSearch compatibility issue (#30278) 2024-05-14 17:54:28 +00:00
github-actions[bot]
3a7aec2807
New Crowdin Translations (automated) (#30254)
Co-authored-by: GitHub Actions <noreply@github.com>
2024-05-13 11:30:41 +00:00
Nick Schonning
13fb54920b
Enable Style/IfUnlessModifier RuboCop (#30260) 2024-05-13 09:54:15 +00:00
Nick Schonning
9ec7c1f892
Fix i18n:extract flags (#30261) 2024-05-13 09:42:47 +00:00
renovate[bot]
6e1b8b33f5
Update opentelemetry-ruby (non-major) (#30262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:42:08 +00:00
Jeen Broekstra
b429c9b8a7
fix(bin/dev): makes conditional for launching overmind POSIX-compliant (#30271) 2024-05-13 09:40:14 +00:00
renovate[bot]
c66fdb3dff
Update dependency immutable to v4.3.6 (#30276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:39:06 +00:00
David Lapshin
cb93c1edf0
Add active animation to header settings button (#30221) 2024-05-13 09:19:42 +00:00
renovate[bot]
f66c9faca0
Update dependency sass to v1.77.1 (#30252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:11:55 +00:00
renovate[bot]
123108b1cc
Update dependency postcss-preset-env to v9.5.12 (#30256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:11:39 +00:00
renovate[bot]
5e7d88a85d
Update dependency glob to v10.3.15 (#30263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:10:53 +00:00
renovate[bot]
471728d6dd
Update DefinitelyTyped types (non-major) (#30272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:10:26 +00:00
renovate[bot]
ab4efa3bf8
Update dependency @testing-library/react to v15.0.7 (#30273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:10:09 +00:00
renovate[bot]
807cf354fc
Update dependency eslint-plugin-jsdoc to v48.2.4 (#30274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:09:39 +00:00
223 changed files with 2281 additions and 911 deletions

View file

@ -4,7 +4,8 @@ NODE_ENV=production
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr
# Secret values required by ActiveRecord encryption feature
# Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
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:
- 6379:6379
search:
image: ${{ matrix.search-image }}
elasticsearch:
image: ${{ contains(matrix.search-image, 'elasticsearch') && matrix.search-image || '' }}
env:
discovery.type: single-node
xpack.security.enabled: false
@ -277,6 +277,20 @@ jobs:
ports:
- 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:
DB_HOST: localhost
DB_USER: postgres
@ -300,6 +314,8 @@ jobs:
include:
- ruby-version: '.ruby-version'
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
- ruby-version: '.ruby-version'
search-image: opensearchproject/opensearch:2
steps:
- uses: actions/checkout@v4

View file

@ -211,6 +211,11 @@ Style/PercentLiteralDelimiters:
Style/RedundantBegin:
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
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
Style/RescueStandardError:

View file

@ -1,6 +1,6 @@
# This configuration was generated by
# `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
# one by one as the offenses are removed from the code base.
# 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/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).
Style/MapToHash:
Exclude:
@ -176,16 +169,6 @@ Style/RedundantConstantBase:
- 'config/environments/production.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).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# 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

@ -28,7 +28,7 @@ gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.18.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'charlock_holmes', github: 'TheEssem/charlock_holmes', ref: '226932af4b03eb60d2e31d58b6c3efd72a3ace68'
gem 'chewy', '~> 7.3'
gem 'devise', '~> 4.9'
gem 'devise-two-factor'
@ -132,7 +132,7 @@ group :test do
gem 'email_spec'
# Extra RSpec extension methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0'
gem 'rspec-sidekiq', '~> 5.0'
# Browser integration testing
gem 'capybara', '~> 3.39'
@ -178,7 +178,7 @@ group :development do
# Preview mail in the browser
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'letter_opener_web', '~> 3.0'
# Security analysis CLI tools
gem 'brakeman', '~> 6.0', require: false

View file

@ -7,38 +7,45 @@ GIT
hkdf (~> 0.2)
jwt (~> 2.0)
GIT
remote: https://github.com/TheEssem/charlock_holmes.git
revision: 226932af4b03eb60d2e31d58b6c3efd72a3ace68
ref: 226932af4b03eb60d2e31d58b6c3efd72a3ace68
specs:
charlock_holmes (0.7.7)
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
actioncable (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailbox (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.2)
actionpack (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailer (7.1.3.3)
actionpack (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.3.2)
actionview (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionpack (7.1.3.3)
actionview (= 7.1.3.3)
activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
@ -46,15 +53,15 @@ GEM
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2)
actionpack (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actiontext (7.1.3.3)
actionpack (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.2)
activesupport (= 7.1.3.2)
actionview (7.1.3.3)
activesupport (= 7.1.3.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@ -64,22 +71,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2)
activesupport (= 7.1.3.2)
activejob (7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.3.6)
activemodel (7.1.3.2)
activesupport (= 7.1.3.2)
activerecord (7.1.3.2)
activemodel (= 7.1.3.2)
activesupport (= 7.1.3.2)
activemodel (7.1.3.3)
activesupport (= 7.1.3.3)
activerecord (7.1.3.3)
activemodel (= 7.1.3.3)
activesupport (= 7.1.3.3)
timeout (>= 0.4.0)
activestorage (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activesupport (= 7.1.3.2)
activestorage (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activesupport (= 7.1.3.3)
marcel (~> 1.0)
activesupport (7.1.3.2)
activesupport (7.1.3.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@ -100,16 +107,16 @@ GEM
attr_required (1.0.2)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.922.0)
aws-sdk-core (3.194.1)
aws-partitions (1.929.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
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-sigv4 (~> 1.1)
aws-sdk-s3 (1.149.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
@ -159,7 +166,6 @@ GEM
case_transform (0.2)
activesupport
cbor (0.5.9.8)
charlock_holmes (0.7.7)
chewy (7.6.0)
activesupport (>= 5.2)
elasticsearch (>= 7.14.0, < 8)
@ -272,7 +278,7 @@ GEM
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-openstack (1.1.0)
fog-openstack (1.1.1)
fog-core (~> 2.1)
fog-json (>= 1.0)
formatador (1.1.0)
@ -389,10 +395,10 @@ GEM
addressable (~> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (2.0.0)
actionmailer (>= 5.2)
letter_opener (~> 1.7)
railties (>= 5.2)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
link_header (0.0.8)
llhttp-ffi (0.5.0)
@ -422,7 +428,7 @@ GEM
memory_profiler (1.0.1)
mime-types (3.5.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2024.0305)
mime-types-data (3.2024.0507)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.22.3)
@ -434,7 +440,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.10)
net-imap (0.4.11)
date
net-protocol
net-ldap (0.19.0)
@ -444,8 +450,8 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.1)
nokogiri (1.16.4)
nio4r (2.7.3)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nsa (0.3.0)
@ -543,7 +549,7 @@ GEM
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-pg (0.27.2)
opentelemetry-instrumentation-pg (0.27.3)
opentelemetry-api (~> 1.0)
opentelemetry-helpers-sql-obfuscation
opentelemetry-instrumentation-base (~> 0.22.1)
@ -634,20 +640,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.3.2)
actioncable (= 7.1.3.2)
actionmailbox (= 7.1.3.2)
actionmailer (= 7.1.3.2)
actionpack (= 7.1.3.2)
actiontext (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activemodel (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
rails (7.1.3.3)
actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.3)
actiontext (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activemodel (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
bundler (>= 1.15.0)
railties (= 7.1.3.2)
railties (= 7.1.3.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -662,9 +668,9 @@ GEM
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
railties (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@ -686,14 +692,15 @@ GEM
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0)
reline (0.5.6)
reline (0.5.7)
io-console (~> 0.5)
request_store (1.6.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rotp (6.3.0)
rouge (4.2.1)
rpam2 (4.0.2)
@ -708,7 +715,7 @@ GEM
rspec-support (~> 3.13.0)
rspec-github (2.4.0)
rspec-core (~> 3.0)
rspec-mocks (3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (6.1.2)
@ -719,7 +726,7 @@ GEM
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-sidekiq (4.2.0)
rspec-sidekiq (5.0.0)
rspec-core (~> 3.0)
rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0)
@ -774,7 +781,7 @@ GEM
scenic (1.8.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.20.1)
selenium-webdriver (4.21.1)
base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
@ -815,6 +822,7 @@ GEM
stringio (3.1.0)
strong_migrations (1.8.0)
activerecord (>= 5.2)
strscan (3.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
@ -893,7 +901,7 @@ GEM
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.13)
zeitwerk (2.6.14)
PLATFORMS
ruby
@ -911,7 +919,7 @@ DEPENDENCIES
browser
bundler-audit (~> 0.9)
capybara (~> 3.39)
charlock_holmes (~> 0.7.7)
charlock_holmes!
chewy (~> 7.3)
climate_control
cocoon (~> 1.2)
@ -955,7 +963,7 @@ DEPENDENCIES
kaminari (~> 1.2)
kt-paperclip (~> 7.2)
letter_opener (~> 1.8)
letter_opener_web (~> 2.0)
letter_opener_web (~> 3.0)
link_header (~> 0.0)
lograge (~> 0.12)
mail (~> 2.8)
@ -1012,7 +1020,7 @@ DEPENDENCIES
rqrcode (~> 2.2)
rspec-github (~> 2.4)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0)
rspec-sidekiq (~> 5.0)
rubocop
rubocop-capybara
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
@statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status)
@statuses = preload_collection(@statuses, Status)
end
format.json do

View file

@ -18,7 +18,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items
case params[:id]
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) }
when 'tags'
@items = for_signed_account { @account.featured_tags }

View file

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

View file

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

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def set_statuses
@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
[]
end

View file

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

View file

@ -45,20 +45,4 @@ module CacheConcern
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
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

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
@application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
scopes: 'read write follow'
scopes: 'read:me'
)
end

View file

@ -45,7 +45,7 @@ class TagsController < ApplicationController
end
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
def limit_param

View file

@ -241,11 +241,20 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end
def site_icon_path(type, size = '48')
icon = SiteUpload.find_by(var: type)
return nil unless icon
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
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
# 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) => {
if (getState().getIn(['settings', 'saved'])) {
if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) {
return;
}

View file

@ -174,7 +174,6 @@ Account.propTypes = {
onBlock: PropTypes.func,
onMute: PropTypes.func,
onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
minimal: PropTypes.bool,
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 { Icon } from 'flavours/glitch/components/icon';
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 { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
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 = (
<button
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 emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<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 && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
}
export default injectIntl(Poll);
export default injectIntl(withIdentity(Poll));

View file

@ -12,6 +12,7 @@ import { HotKeys } from 'react-hotkeys';
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
import PollContainer from 'flavours/glitch/containers/poll_container';
import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -20,7 +21,6 @@ import Card from '../features/status/components/card';
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { IdentityConsumer } from '../features/ui/util/identity_consumer';
import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context';
import { displayMedia, visibleReactions } from '../initial_state';
@ -78,6 +78,7 @@ class Status extends ImmutablePureComponent {
static contextType = SensitiveMediaContext;
static propTypes = {
identity: identityContextPropShape,
containerId: PropTypes.string,
id: PropTypes.string,
status: ImmutablePropTypes.map,
@ -545,6 +546,7 @@ class Status extends ImmutablePureComponent {
nextInReplyToId,
rootId,
history,
identity,
...other
} = this.props;
const { isCollapsed } = this.state;
@ -846,18 +848,14 @@ class Status extends ImmutablePureComponent {
{...statusContentProps}
/>
<IdentityConsumer>
{identity => (
<StatusReactions
statusId={status.get('id')}
reactions={status.get('reactions')}
numVisible={visibleReactions}
addReaction={this.props.onReactionAdd}
removeReaction={this.props.onReactionRemove}
canReact={identity.signedIn}
/>
)}
</IdentityConsumer>
<StatusReactions
statusId={status.get('id')}
reactions={status.get('reactions')}
numVisible={visibleReactions}
addReaction={this.props.onReactionAdd}
removeReaction={this.props.onReactionRemove}
canReact={this.props.identity.signedIn}
/>
{(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && (
<StatusActionBar
@ -881,4 +879,4 @@ class Status extends ImmutablePureComponent {
}
export default withOptionalRouter(injectIntl(Status));
export default withOptionalRouter(injectIntl((withIdentity(Status))));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
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 { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -70,12 +71,8 @@ const messages = defineMessages({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
@ -112,7 +109,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -128,7 +125,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = (e) => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status, e);
@ -142,7 +139,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -218,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
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 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 MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react';
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 { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -126,12 +127,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -349,7 +346,7 @@ class StatusContent extends PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const contentLocale = intl.locale.replace(/[_-].*/, '');
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 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 { Helmet } from 'react-helmet';
@ -15,6 +14,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router';
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 { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store';
@ -33,33 +33,9 @@ if (initialState.meta.me) {
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 {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -79,19 +55,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</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 { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
@ -94,6 +95,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
@ -117,10 +119,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
openEditProfile = () => {
window.open(profileLink, '_blank');
};
@ -170,7 +168,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
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 { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -80,7 +77,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -90,7 +87,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
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 SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
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 ColumnHeader from 'flavours/glitch/components/column_header';
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 Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { NavLink } from 'react-router-dom';
import { useIdentity } from '@/flavours/glitch/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'flavours/glitch/actions/columns';
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 { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
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 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' },
});
// 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 intl = useIntl();
const dispatch = useAppDispatch();

View file

@ -28,6 +28,7 @@ import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column';
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';
@ -99,12 +100,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
columns: ImmutablePropTypes.list,
@ -123,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -134,7 +131,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const navItems = [];
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 ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
const messages = defineMessages({
heading: { id: 'column.heading', defaultMessage: 'Misc' },
@ -32,11 +32,8 @@ const messages = defineMessages({
class GettingStartedMisc extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
};
@ -49,7 +46,7 @@ class GettingStartedMisc extends ImmutablePureComponent {
render () {
const { intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
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 { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -41,12 +42,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -128,7 +125,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
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 { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions';
import { CheckboxWithLabel } from './checkbox_with_label';
@ -13,13 +14,9 @@ import GrantPermissionButton from './grant_permission_button';
import PillBarButton from './pill_bar_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -227,7 +224,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</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'>
<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>
)}
{((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'>
<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 { Icon } from 'flavours/glitch/components/icon';
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 { submitMarkers } from '../../actions/markers';
@ -93,12 +94,8 @@ const mapDispatchToProps = dispatch => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired,
@ -225,7 +222,7 @@ class Notifications extends PureComponent {
const { animatingNCD } = this.state;
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 { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
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 { openModal } from 'flavours/glitch/actions/modal';
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 { makeGetStatus } from 'flavours/glitch/selectors';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -48,12 +49,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -106,7 +103,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -133,7 +130,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
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 { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -44,16 +45,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -86,7 +83,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly }));
if (signedIn) {
@ -95,7 +92,7 @@ class PublicTimeline extends PureComponent {
}
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) {
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 RepeatPrivateIcon from '@/svg-icons/repeat_private.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 { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -62,12 +63,8 @@ const messages = defineMessages({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
@ -165,7 +162,7 @@ class ActionBar extends PureComponent {
render () {
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 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

@ -15,6 +15,7 @@ import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
import PollContainer from 'flavours/glitch/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { Avatar } from '../../../components/avatar';
@ -29,12 +30,8 @@ import Video from '../../video';
import Card from './card';
class DetailedStatus extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map,
settings: ImmutablePropTypes.map.isRequired,
onOpenMedia: PropTypes.func.isRequired,
@ -319,7 +316,7 @@ class DetailedStatus extends ImmutablePureComponent {
reactions={status.get('reactions')}
addReaction={this.props.onReactionAdd}
removeReaction={this.props.onReactionRemove}
canReact={this.context.identity.signedIn}
canReact={this.props.identity.signedIn}
/>
<div className='detailed-status__meta'>
@ -348,4 +345,4 @@ class DetailedStatus extends ImmutablePureComponent {
}
export default withRouter(DetailedStatus);
export default withRouter(withIdentity(DetailedStatus));

View file

@ -21,6 +21,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
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 { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -187,12 +188,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -279,7 +276,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status, e) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -310,8 +307,8 @@ class Status extends ImmutablePureComponent {
};
handleReactionAdd = (statusId, name, url) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { dispatch, identity } = this.props;
const { signedIn } = identity;
if (signedIn) {
dispatch(addReaction(statusId, name, url));
@ -332,7 +329,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -372,7 +369,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => {
const { settings, dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
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 ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -31,7 +28,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
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';
const Account = connect(state => ({
@ -42,12 +43,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -61,7 +58,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
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 { 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 { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
import { logOut } from 'flavours/glitch/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
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 { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
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 { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -98,12 +99,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
onOpenSettings: PropTypes.func,
};
@ -114,7 +111,7 @@ class NavigationPanel extends Component {
render () {
const { intl, onOpenSettings } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
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 { Permalink } from 'flavours/glitch/components/permalink';
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 { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -129,12 +130,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -169,7 +166,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -262,12 +259,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isWide: PropTypes.bool,
@ -323,7 +316,7 @@ class UI extends PureComponent {
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 });
}
};
@ -351,7 +344,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
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));
}
};
@ -403,7 +396,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
@ -649,7 +642,7 @@ class UI extends PureComponent {
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</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

@ -1,16 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
export class IdentityConsumer extends PureComponent {
static contextTypes = {
identity: PropTypes.object
};
static propTypes = {
children: PropTypes.func.isRequired
};
render() {
return this.props.children(this.context.identity);
}
}

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

View file

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

View file

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

View file

@ -172,7 +172,6 @@ Account.propTypes = {
onBlock: PropTypes.func,
onMute: PropTypes.func,
onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
minimal: PropTypes.bool,
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 { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
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 = (
<button
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 emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<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 && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{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 RepeatPrivateIcon from '@/svg-icons/repeat_private.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 { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func,
@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status);
@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
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 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 { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -245,7 +243,7 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, '');
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 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 { Helmet } from 'react-helmet';
@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store';
@ -28,33 +28,9 @@ if (initialState.meta.me) {
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 {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -74,19 +50,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
</IdentityContext.Provider>
);
}

View file

@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -111,6 +112,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => {
this.node = c;
};
@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
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 { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
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 SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
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 ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
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 Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
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 dispatch = useAppDispatch();
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 ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar';
@ -75,12 +76,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
@ -91,7 +88,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -102,7 +99,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
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 Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
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 { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
import { CheckboxWithLabel } from './checkbox_with_label';
@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</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'>
<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>
)}
{((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'>
<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 { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.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 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 { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
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 { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -131,7 +128,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
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 { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
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 RepeatPrivateIcon from '@/svg-icons/repeat_private.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 { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired,
@ -198,7 +195,7 @@ class ActionBar extends PureComponent {
render () {
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 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 ScrollContainer from 'mastodon/containers/scroll_container';
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 {
@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -307,7 +304,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
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 ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<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 { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({
@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -60,7 +57,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
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 { 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 { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
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 { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';
@ -97,12 +98,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
};
@ -112,7 +109,7 @@ class NavigationPanel extends Component {
render () {
const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
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 { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -120,12 +121,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -160,7 +157,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -252,12 +249,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isComposing: PropTypes.bool,
@ -309,7 +302,7 @@ class UI extends PureComponent {
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 });
}
};
@ -337,7 +330,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
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));
}
};
@ -389,7 +382,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, 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}>
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</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
*/
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/**
* @typedef InitialState
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
* @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
*/
const element = document.getElementById('initial-state');

View file

@ -474,7 +474,7 @@
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
"notification.mention": "Crybwyllodd {name} amdanoch chi",
"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_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.",

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_suggestions.curated_suggestion": "Staff pick",
"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.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}.",
@ -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.personalized_suggestion": "Personalised 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.who_to_follow": "Who to follow",
"followed_tags": "Followed hashtags",
@ -469,6 +473,15 @@
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow 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.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más",
"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_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_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_silence": "Se ha limitado tu cuenta.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.",
"notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha retooteado tu estado",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más",
"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_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_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_silence": "Se ha limitado tu cuenta.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.",
"notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado",
"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.mention": "{name} nevndi teg",
"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_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_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_silence": "Konta tín er avmarkað.",
"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.show_less": "Mostrar menos",
"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_will_know": "Pode ver que a bloqueaches.",
"block_modal.title": "Bloquear usuaria?",

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar",
"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.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private",
"column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos",
"column.favourites": "Favorites",
"column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento",
"column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto",
"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.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_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.",
@ -212,8 +212,8 @@
"domain_block_modal.block": "Blocar le servitor",
"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_cant_follow": "Nulle persona ab iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.",
"domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?",
"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.",
@ -307,7 +307,7 @@
"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_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.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}.",
@ -412,7 +412,7 @@
"lightbox.next": "Sequente",
"lightbox.previous": "Precedente",
"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}",
"lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista",
@ -432,12 +432,12 @@
"loading_indicator.label": "Cargante…",
"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}.",
"mute_modal.hide_from_notifications": "Celar ab notificationes",
"mute_modal.hide_from_notifications": "Celar in notificationes",
"mute_modal.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona",
"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_wont_know": "Illes non sapera que illes ha essite silentiate.",
"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": "Ille non sapera que ille ha essite silentiate.",
"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_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.",
@ -451,13 +451,13 @@
"navigation_bar.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session",
"navigation_bar.logout": "Clauder session",
"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.personal": "Personal",
@ -474,7 +474,7 @@
"notification.follow_request": "{name} ha requestate de sequer te",
"notification.mention": "{name} te ha mentionate",
"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_disable": "Tu conto ha essite disactivate.",
"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.sign_up": "Nove inscriptiones:",
"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.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:",
@ -518,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos",
"notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage",
@ -717,7 +717,7 @@
"status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar",
"status.favourite": "Adder al favoritos",
"status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message",
"status.filtered": "Filtrate",

View file

@ -89,6 +89,7 @@
"announcement.announcement": "Proclamation",
"attachments_list.unprocessed": "(íntractat)",
"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_more": "Monstrar plu",
"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.username": "Usator-nómine",
"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_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.",
"embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.",
"embed.preview": "Vi qualmen it va aspecter:",

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