diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 000000000..54dd3aaf3 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,7 @@ +[production] +defaults +not IE 11 +not dead + +[development] +supports es6-module diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 628efc8ec..9d9e54d4f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,8 @@ "extensions": [ "EditorConfig.EditorConfig", "dbaeumer.vscode-eslint", - "rebornix.Ruby" + "rebornix.Ruby", + "webben.browserslist" ], // Use 'forwardPorts' to make a list of ports inside the container available locally. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8814d5a4c..ed4cdd881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,105 @@ Changelog All notable changes to this project will be documented in this file. +## [3.5.3] - 2022-05-26 +### Added + +- **Add language dropdown to compose form in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18420), [ykzts](https://github.com/mastodon/mastodon/pull/18460)) +- **Add warning for limited accounts in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18344)) +- Add `limited` attribute to accounts in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/18344)) + +### Changed + +- **Change RSS feeds** ([Gargron](https://github.com/mastodon/mastodon/pull/18356), [tribela](https://github.com/mastodon/mastodon/pull/18406)) + - Titles are now date and time of post + - Bodies now render all content faithfully, including polls and emojis + - All media attachments are included with Media RSS +- Change "dangerous" to "sensitive" in privacy policy and web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18515)) +- Change unconfirmed accounts to not be visible in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17530)) +- Change `tootctl search deploy` to improve performance ([Gargron](https://github.com/mastodon/mastodon/pull/18463), [Gargron](https://github.com/mastodon/mastodon/pull/18514)) +- Change search indexing to use batches to minimize resource usage ([Gargron](https://github.com/mastodon/mastodon/pull/18451)) + +### Fixed + +- Fix follower and other counters being able to go negative ([Gargron](https://github.com/mastodon/mastodon/pull/18517)) +- Fix unnecessary query on when creating a status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17901)) +- Fix warning an account outside of a report closing all reports for that account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18387)) +- Fix error when resolving a link that redirects to a local post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18314)) +- Fix preferred posting language returning unusable value in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/18428)) +- Fix race condition error when external status is reblogged ([ykzts](https://github.com/mastodon/mastodon/pull/18424)) +- Fix missing string for appeal validation error ([Gargron](https://github.com/mastodon/mastodon/pull/18410)) +- Fix block/mute lists showing a follow button in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18364)) +- Fix Redis configuration not being changed by `mastodon:setup` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18383)) +- Fix streaming notifications not using quick filter logic in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18316)) +- Fix ambiguous wording on appeal actions in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18328)) +- Fix floating action button obscuring last element in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18332)) +- Fix account warnings not being recorded in audit log ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18338)) +- Fix leftover icons for direct visibility statuses ([Steffo99](https://github.com/mastodon/mastodon/pull/18305)) +- Fix link verification requiring case sensitivity on links ([sgolemon](https://github.com/mastodon/mastodon/pull/18320)) +- Fix embeds not setting their height correctly ([rinsuki](https://github.com/mastodon/mastodon/pull/18301)) + +### Security + +- Fix concurrent unfollowing decrementing follower count more than once ([Gargron](https://github.com/mastodon/mastodon/pull/18527)) +- Fix being able to appeal a strike unlimited times ([Gargron](https://github.com/mastodon/mastodon/pull/18529)) +- Fix being able to report otherwise inaccessible statuses ([Gargron](https://github.com/mastodon/mastodon/pull/18528)) +- Fix empty votes arbitrarily increasing voters count in polls ([Gargron](https://github.com/mastodon/mastodon/pull/18526)) +- Fix moderator identity leak when approving appeal of sensitive marked statuses ([Gargron](https://github.com/mastodon/mastodon/pull/18525)) +- Fix suspended users being able to access APIs that don't require a user ([Gargron](https://github.com/mastodon/mastodon/pull/18524)) +- Fix confirmation redirect to app without `Location` header ([Gargron](https://github.com/mastodon/mastodon/pull/18523)) + +## [3.5.2] - 2022-05-04 +### Added + +- Add warning on direct messages screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18289)) + - We already had a warning when composing a direct message, it has now been reworded to be more clear + - Same warning is now displayed when viewing sent and received direct messages +- Add ability to set approval-based registration through tootctl ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18248)) +- Add pre-filling of domain from search filter in domain allow/block admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18172)) + +## Changed + +- Change name of “Direct” visibility to “Mentioned people only” in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18146), [Gargron](https://github.com/mastodon/mastodon/pull/18289), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18291)) +- Change trending posts to only show one post from each account ([Gargron](https://github.com/mastodon/mastodon/pull/18181)) +- Change half-life of trending posts from 6 hours to 2 hours ([Gargron](https://github.com/mastodon/mastodon/pull/18182)) +- Change full-text search feature to also include polls you have voted in ([tribela](https://github.com/mastodon/mastodon/pull/18070)) +- Change Redis from using one connection per process, to using a connection pool ([Gargron](https://github.com/mastodon/mastodon/pull/18135), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18160), [Gargron](https://github.com/mastodon/mastodon/pull/18171)) + - Different threads no longer have to wait on a mutex over a single connection + - However, this does increase the number of Redis connections by a fair amount + - We are planning to optimize Redis use so that the pool can be made smaller in the future + +## Removed + +- Remove IP matching from e-mail domain blocks ([Gargron](https://github.com/mastodon/mastodon/pull/18190)) + - The IPs of the blocked e-mail domain or its MX records are no longer checked + - Previously it was too easy to block e-mail providers by mistake + +## Fixed + +- Fix compatibility with Friendica's pinned posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18254), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18260)) +- Fix error when looking up handle with surrounding spaces in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18225)) +- Fix double render error when authorizing interaction ([Gargron](https://github.com/mastodon/mastodon/pull/18203)) +- Fix error when a post references an invalid media attachment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18211)) +- Fix error when trying to revoke OAuth token without supplying a token ([Gargron](https://github.com/mastodon/mastodon/pull/18205)) +- Fix error caused by missing subject in Webfinger response ([Gargron](https://github.com/mastodon/mastodon/pull/18204)) +- Fix error on attempting to delete an account moderation note ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18196)) +- Fix light-mode emoji borders in web UI ([Gaelan](https://github.com/mastodon/mastodon/pull/18131)) +- Fix being able to scroll away from the loading bar in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18170)) +- Fix error when a bookmark or favorite has been reported and deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18174)) +- Fix being offered empty “Server rules violation” report option in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18165)) +- Fix temporary network errors preventing from authorizing interactions with remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18161)) +- Fix incorrect link in "new trending tags" email ([cdzombak](https://github.com/mastodon/mastodon/pull/18156)) +- Fix missing indexes on some foreign keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18157)) +- Fix n+1 query on feed merge and populate operations ([Gargron](https://github.com/mastodon/mastodon/pull/18111)) +- Fix feed unmerge worker being exceptionally slow in some conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18110)) +- Fix PeerTube videos appearing with an erroneous “Edited at” marker ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18100)) +- Fix instance actor being created incorrectly when running through migrations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18109)) +- Fix web push notifications containing HTML entities ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18071)) +- Fix inconsistent parsing of `TRUSTED_PROXY_IP` ([ykzts](https://github.com/mastodon/mastodon/pull/18051)) +- Fix error when fetching pinned posts ([tribela](https://github.com/mastodon/mastodon/pull/18030)) +- Fix wrong optimization in feed populate operation ([dogelover911](https://github.com/mastodon/mastodon/pull/18009)) +- Fix error in alias settings page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18004)) + ## [3.5.1] - 2022-04-08 ### Added diff --git a/Gemfile b/Gemfile index 09dd1c2e2..c8d58b06e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,13 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 2.5.0', '< 3.1.0' +ruby '>= 2.6.0', '< 3.1.0' gem 'pkg-config', '~> 1.4' gem 'rexml', '~> 3.2' gem 'puma', '~> 5.6' -gem 'rails', '~> 6.1.5' +gem 'rails', '~> 6.1.6' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' gem 'rack', '~> 2.2.3' @@ -18,7 +18,7 @@ gem 'makara', '~> 0.5' gem 'pghero', '~> 2.8' gem 'dotenv-rails', '~> 2.7' -gem 'aws-sdk-s3', '~> 1.113', require: false +gem 'aws-sdk-s3', '~> 1.114', require: false gem 'fog-core', '<= 2.1.0' gem 'fog-openstack', '~> 0.3', require: false gem 'kt-paperclip', '~> 7.1' @@ -26,7 +26,7 @@ gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' -gem 'bootsnap', '~> 1.10.3', require: false +gem 'bootsnap', '~> 1.11.1', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.2' @@ -79,13 +79,13 @@ gem 'ruby-progressbar', '~> 1.11' gem 'sanitize', '~> 6.0' gem 'scenic', '~> 1.6' gem 'sidekiq', '~> 6.4' -gem 'sidekiq-scheduler', '~> 3.1' +gem 'sidekiq-scheduler', '~> 4.0' gem 'sidekiq-unique-jobs', '~> 7.1' -gem 'sidekiq-bulk', '~>0.2.0' +gem 'sidekiq-bulk', '~> 0.2.0' gem 'simple-navigation', '~> 4.3' gem 'simple_form', '~> 5.1' gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie' -gem 'stoplight', '~> 2.2.1' +gem 'stoplight', '~> 3.0.0' gem 'strong_migrations', '~> 0.7' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' @@ -114,9 +114,9 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.36' + gem 'capybara', '~> 3.37' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.20' + gem 'faker', '~> 2.21' gem 'microformats', '~> 4.2' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.1' @@ -134,7 +134,7 @@ group :development do gem 'letter_opener', '~> 1.8' gem 'letter_opener_web', '~> 2.0' gem 'memory_profiler' - gem 'rubocop', '~> 1.26', require: false + gem 'rubocop', '~> 1.29', require: false gem 'rubocop-rails', '~> 2.14', require: false gem 'brakeman', '~> 5.2', require: false gem 'bundler-audit', '~> 0.9', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3d815cd7b..3218adcee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,40 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.5) - actionpack (= 6.1.5) - activesupport (= 6.1.5) + actioncable (6.1.6) + actionpack (= 6.1.6) + activesupport (= 6.1.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.5) - actionpack (= 6.1.5) - activejob (= 6.1.5) - activerecord (= 6.1.5) - activestorage (= 6.1.5) - activesupport (= 6.1.5) + actionmailbox (6.1.6) + actionpack (= 6.1.6) + activejob (= 6.1.6) + activerecord (= 6.1.6) + activestorage (= 6.1.6) + activesupport (= 6.1.6) mail (>= 2.7.1) - actionmailer (6.1.5) - actionpack (= 6.1.5) - actionview (= 6.1.5) - activejob (= 6.1.5) - activesupport (= 6.1.5) + actionmailer (6.1.6) + actionpack (= 6.1.6) + actionview (= 6.1.6) + activejob (= 6.1.6) + activesupport (= 6.1.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.5) - actionview (= 6.1.5) - activesupport (= 6.1.5) + actionpack (6.1.6) + actionview (= 6.1.6) + activesupport (= 6.1.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.5) - actionpack (= 6.1.5) - activerecord (= 6.1.5) - activestorage (= 6.1.5) - activesupport (= 6.1.5) + actiontext (6.1.6) + actionpack (= 6.1.6) + activerecord (= 6.1.6) + activestorage (= 6.1.6) + activesupport (= 6.1.6) nokogiri (>= 1.8.5) - actionview (6.1.5) - activesupport (= 6.1.5) + actionview (6.1.6) + activesupport (= 6.1.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -45,22 +45,22 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.8) - activejob (6.1.5) - activesupport (= 6.1.5) + activejob (6.1.6) + activesupport (= 6.1.6) globalid (>= 0.3.6) - activemodel (6.1.5) - activesupport (= 6.1.5) - activerecord (6.1.5) - activemodel (= 6.1.5) - activesupport (= 6.1.5) - activestorage (6.1.5) - actionpack (= 6.1.5) - activejob (= 6.1.5) - activerecord (= 6.1.5) - activesupport (= 6.1.5) + activemodel (6.1.6) + activesupport (= 6.1.6) + activerecord (6.1.6) + activemodel (= 6.1.6) + activesupport (= 6.1.6) + activestorage (6.1.6) + actionpack (= 6.1.6) + activejob (= 6.1.6) + activerecord (= 6.1.6) + activesupport (= 6.1.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.5) + activesupport (6.1.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -81,20 +81,20 @@ GEM attr_required (1.0.1) awrence (1.1.1) aws-eventstream (1.2.0) - aws-partitions (1.558.0) - aws-sdk-core (3.127.0) + aws-partitions (1.587.0) + aws-sdk-core (3.130.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.55.0) + aws-sdk-kms (1.56.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.113.0) + aws-sdk-s3 (1.114.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.0) aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.17) better_errors (2.9.1) @@ -114,9 +114,9 @@ GEM debug_inspector (>= 0.0.1) blurhash (0.1.6) ffi (~> 1.14) - bootsnap (1.10.3) + bootsnap (1.11.1) msgpack (~> 1.2) - brakeman (5.2.1) + brakeman (5.2.3) browser (4.2.0) brpoplpush-redis_script (0.1.2) concurrent-ruby (~> 1.0, >= 1.0.5) @@ -144,7 +144,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.36.0) + capybara (3.37.1) addressable matrix mini_mime (>= 0.1.3) @@ -203,7 +203,6 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - e2mmap (0.1.0) ed25519 (1.3.0) elasticsearch (7.13.3) elasticsearch-api (= 7.13.3) @@ -216,11 +215,11 @@ GEM multi_json encryptor (3.0.0) erubi (1.10.0) - et-orbi (1.2.6) + et-orbi (1.2.7) tzinfo excon (0.76.0) fabrication (2.28.0) - faker (2.20.0) + faker (2.21.0) i18n (>= 1.8.11, < 2) faraday (1.9.3) faraday-em_http (~> 1.0) @@ -264,8 +263,8 @@ GEM fog-json (>= 1.0) ipaddress (>= 0.8) formatador (0.2.5) - fugit (1.5.2) - et-orbi (~> 1.1, >= 1.1.8) + fugit (1.5.3) + et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) fuubar (2.5.1) rspec-core (~> 3.0) @@ -309,7 +308,7 @@ GEM rainbow (>= 2.0.0) i18n (1.10.0) concurrent-ruby (~> 1.0) - i18n-tasks (1.0.8) + i18n-tasks (1.0.10) activesupport (>= 4.0.2) ast (>= 2.1.0) better_html (~> 1.0) @@ -322,7 +321,7 @@ GEM terminal-table (>= 1.5.1) idn-ruby (0.1.4) ipaddress (0.8.3) - jmespath (1.6.0) + jmespath (1.6.1) json (2.5.1) json-canonicalization (0.3.0) json-jwt (1.13.0) @@ -377,7 +376,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.16.0) + loofah (2.18.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -399,7 +398,7 @@ GEM mini_mime (1.1.2) mini_portile2 (2.8.0) minitest (5.15.0) - msgpack (1.4.4) + msgpack (1.5.1) multi_json (1.15.0) multipart-post (2.1.1) net-ldap (0.17.0) @@ -407,7 +406,7 @@ GEM net-ssh (>= 2.6.5, < 7.0.0) net-ssh (6.1.0) nio4r (2.5.8) - nokogiri (1.13.3) + nokogiri (1.13.6) mini_portile2 (~> 2.8.0) racc (~> 1.4) nsa (0.2.8) @@ -444,13 +443,13 @@ GEM orm_adapter (0.5.0) ox (2.14.11) parallel (1.22.1) - parser (3.1.1.0) + parser (3.1.2.0) ast (~> 2.4.1) parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) pg (1.3.5) - pghero (2.8.2) + pghero (2.8.3) activerecord (>= 5) pkg-config (1.4.7) posix-spawn (0.3.15) @@ -470,7 +469,7 @@ GEM pry (~> 0.13.0) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.6) + public_suffix (4.0.7) puma (5.6.4) nio4r (~> 2.0) pundit (2.2.0) @@ -478,7 +477,7 @@ GEM raabro (1.4.0) racc (1.6.0) rack (2.2.3) - rack-attack (6.6.0) + rack-attack (6.6.1) rack (>= 1.0, < 3) rack-cors (1.1.1) rack (>= 2.0.0) @@ -492,20 +491,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.5) - actioncable (= 6.1.5) - actionmailbox (= 6.1.5) - actionmailer (= 6.1.5) - actionpack (= 6.1.5) - actiontext (= 6.1.5) - actionview (= 6.1.5) - activejob (= 6.1.5) - activemodel (= 6.1.5) - activerecord (= 6.1.5) - activestorage (= 6.1.5) - activesupport (= 6.1.5) + rails (6.1.6) + actioncable (= 6.1.6) + actionmailbox (= 6.1.6) + actionmailer (= 6.1.6) + actionpack (= 6.1.6) + actiontext (= 6.1.6) + actionview (= 6.1.6) + activejob (= 6.1.6) + activemodel (= 6.1.6) + activerecord (= 6.1.6) + activestorage (= 6.1.6) + activesupport (= 6.1.6) bundler (>= 1.15.0) - railties (= 6.1.5) + railties (= 6.1.6) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -521,9 +520,9 @@ GEM railties (>= 6.0.0, < 7) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (6.1.5) - actionpack (= 6.1.5) - activesupport (= 6.1.5) + railties (6.1.6) + actionpack (= 6.1.6) + activesupport (= 6.1.6) method_source rake (>= 12.2) thor (~> 1.0) @@ -537,7 +536,7 @@ GEM redis (4.5.1) redis-namespace (1.8.2) redis (>= 3.0.4) - regexp_parser (2.2.1) + regexp_parser (2.4.0) request_store (1.5.1) rack (>= 1.4) responders (3.0.1) @@ -555,10 +554,10 @@ GEM rspec-expectations (3.11.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.11.0) - rspec-mocks (3.11.0) + rspec-mocks (3.11.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.11.0) - rspec-rails (5.1.1) + rspec-rails (5.1.2) actionpack (>= 5.2) activesupport (>= 5.2) railties (>= 5.2) @@ -572,16 +571,16 @@ GEM rspec-support (3.11.0) rspec_junit_formatter (0.5.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.26.1) + rubocop (1.29.1) parallel (~> 1.10) parser (>= 3.1.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.16.0, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.17.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.16.0) + rubocop-ast (1.18.0) parser (>= 3.1.1.0) rubocop-rails (2.14.2) activesupport (>= 4.2.0) @@ -604,20 +603,18 @@ GEM railties (>= 4.0.0) securecompare (1.0.0) semantic_range (3.0.0) - sidekiq (6.4.1) + sidekiq (6.4.2) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (3.1.1) - e2mmap - redis (>= 3, < 5) + sidekiq-scheduler (4.0.0) + redis (>= 4.2.0) rufus-scheduler (~> 3.2) - sidekiq (>= 3) - thwait + sidekiq (>= 4) tilt (>= 1.4.0) - sidekiq-unique-jobs (7.1.16) + sidekiq-unique-jobs (7.1.22) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 5.0, < 8.0) @@ -646,7 +643,7 @@ GEM net-ssh (>= 2.8.0) stackprof (0.2.19) statsd-ruby (1.5.0) - stoplight (2.2.1) + stoplight (3.0.0) strong_migrations (0.7.9) activerecord (>= 5) swd (1.3.0) @@ -659,8 +656,6 @@ GEM terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) thor (1.2.1) - thwait (0.2.0) - e2mmap tilt (2.0.10) tpm-key_attestation (0.9.0) bindata (~> 2.4) @@ -737,11 +732,11 @@ DEPENDENCIES active_record_query_trace (~> 1.8) addressable (~> 2.8) annotate (~> 3.2) - aws-sdk-s3 (~> 1.113) + aws-sdk-s3 (~> 1.114) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) - bootsnap (~> 1.10.3) + bootsnap (~> 1.11.1) brakeman (~> 5.2) browser bullet (~> 7.0) @@ -750,7 +745,7 @@ DEPENDENCIES capistrano-rails (~> 1.6) capistrano-rbenv (~> 2.2) capistrano-yarn (~> 2.0) - capybara (~> 3.36) + capybara (~> 3.37) charlock_holmes (~> 0.7.7) chewy (~> 7.2) climate_control (~> 0.2) @@ -765,7 +760,7 @@ DEPENDENCIES dotenv-rails (~> 2.7) ed25519 (~> 1.3) fabrication (~> 2.28) - faker (~> 2.20) + faker (~> 2.21) fast_blank (~> 1.0) fastimage fog-core (<= 2.1.0) @@ -817,7 +812,7 @@ DEPENDENCIES rack (~> 2.2.3) rack-attack (~> 6.6) rack-cors (~> 1.1) - rails (~> 6.1.5) + rails (~> 6.1.6) rails-controller-testing (~> 1.0) rails-i18n (~> 6.0) rails-settings-cached (~> 0.6) @@ -830,14 +825,14 @@ DEPENDENCIES rspec-rails (~> 5.1) rspec-sidekiq (~> 3.1) rspec_junit_formatter (~> 0.5) - rubocop (~> 1.26) + rubocop (~> 1.29) rubocop-rails (~> 2.14) ruby-progressbar (~> 1.11) sanitize (~> 6.0) scenic (~> 1.6) sidekiq (~> 6.4) sidekiq-bulk (~> 0.2.0) - sidekiq-scheduler (~> 3.1) + sidekiq-scheduler (~> 4.0) sidekiq-unique-jobs (~> 7.1) simple-navigation (~> 4.3) simple_form (~> 5.1) @@ -845,7 +840,7 @@ DEPENDENCIES sprockets (~> 3.7.2) sprockets-rails (~> 3.4) stackprof - stoplight (~> 2.2.1) + stoplight (~> 3.0.0) strong_migrations (~> 0.7) thor (~> 1.2) tty-prompt (~> 0.23) diff --git a/SECURITY.md b/SECURITY.md index 12f50ed88..62e23f736 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,7 +14,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through | ------- | ------------------ | | 3.5.x | Yes | | 3.4.x | Yes | -| 3.3.x | Yes | +| 3.3.x | No | | < 3.3 | No | [bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail diff --git a/app/chewy/accounts_index.rb b/app/chewy/accounts_index.rb index 6f9ea76e9..e38e14a10 100644 --- a/app/chewy/accounts_index.rb +++ b/app/chewy/accounts_index.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AccountsIndex < Chewy::Index - settings index: { refresh_interval: '5m' }, analysis: { + settings index: { refresh_interval: '30s' }, analysis: { analyzer: { content: { tokenizer: 'whitespace', @@ -23,7 +23,7 @@ class AccountsIndex < Chewy::Index }, } - index_scope ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? } + index_scope ::Account.searchable.includes(:account_stat) root date_detection: false do field :id, type: 'long' @@ -36,8 +36,8 @@ class AccountsIndex < Chewy::Index field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content' end - field :following_count, type: 'long', value: ->(account) { account.following.local.count } - field :followers_count, type: 'long', value: ->(account) { account.followers.local.count } + field :following_count, type: 'long', value: ->(account) { account.following_count } + field :followers_count, type: 'long', value: ->(account) { account.followers_count } field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at } end end diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index 1381a96ed..6dd4fb18b 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -3,7 +3,7 @@ class StatusesIndex < Chewy::Index include FormattingHelper - settings index: { refresh_interval: '15m' }, analysis: { + settings index: { refresh_interval: '30s' }, analysis: { filter: { english_stop: { type: 'stop', @@ -33,6 +33,8 @@ class StatusesIndex < Chewy::Index }, } + # We do not use delete_if option here because it would call a method that we + # expect to be called with crutches without crutches, causing n+1 queries index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll) crutch :mentions do |collection| @@ -55,6 +57,11 @@ class StatusesIndex < Chewy::Index data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } end + crutch :votes do |collection| + data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_id, :account_id) + data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } + end + root date_detection: false do field :id, type: 'long' field :account_id, type: 'long' diff --git a/app/chewy/tags_index.rb b/app/chewy/tags_index.rb index f9db2b03a..df3d9e4cc 100644 --- a/app/chewy/tags_index.rb +++ b/app/chewy/tags_index.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class TagsIndex < Chewy::Index - settings index: { refresh_interval: '15m' }, analysis: { + settings index: { refresh_interval: '30s' }, analysis: { analyzer: { content: { tokenizer: 'keyword', @@ -23,7 +23,11 @@ class TagsIndex < Chewy::Index }, } - index_scope ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } + index_scope ::Tag.listable + + crutch :time_period do + 7.days.ago.to_date..0.days.ago.to_date + end root date_detection: false do field :name, type: 'text', analyzer: 'content' do @@ -31,7 +35,7 @@ class TagsIndex < Chewy::Index end field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? } - field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day.accounts } } + field :usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts } field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at } end end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 03c07c50b..9949206cb 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -45,7 +45,6 @@ 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) - render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag]) end format.json do diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb index 196d85a32..b8a7e0ab9 100644 --- a/app/controllers/activitypub/base_controller.rb +++ b/app/controllers/activitypub/base_controller.rb @@ -2,6 +2,7 @@ class ActivityPub::BaseController < Api::BaseController skip_before_action :require_authenticated_user! + skip_before_action :require_not_suspended! skip_around_action :set_locale private diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index e376baab2..da9c6dd16 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -2,6 +2,8 @@ module Admin class DashboardController < BaseController + include Redisable + def index @system_checks = Admin::SystemCheck.perform @time_period = (29.days.ago.to_date...Time.now.utc.to_date) @@ -15,10 +17,10 @@ module Admin def redis_info @redis_info ||= begin - if Redis.current.is_a?(Redis::Namespace) - Redis.current.redis.info + if redis.is_a?(Redis::Namespace) + redis.redis.info else - Redis.current.info + redis.info end end end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 16defc1ea..48e9781d6 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -4,6 +4,17 @@ module Admin class DomainBlocksController < BaseController before_action :set_domain_block, only: [:show, :destroy, :edit, :update] + def batch + @form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.email_domain_blocks.no_domain_block_selected') + rescue Mastodon::NotPermittedError + flash[:alert] = I18n.t('admin.domain_blocks.created_msg') + else + redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') + end + def new authorize :domain_block, :create? @domain_block = DomainBlock.new(domain: params[:_domain]) @@ -76,5 +87,15 @@ module Admin def resource_params params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) end + + def form_domain_block_batch_params + params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate]) + end + + def action_from_button + if params[:save] + 'save' + end + end end end diff --git a/app/controllers/admin/export_domain_allows_controller.rb b/app/controllers/admin/export_domain_allows_controller.rb new file mode 100644 index 000000000..eb2955ac3 --- /dev/null +++ b/app/controllers/admin/export_domain_allows_controller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'csv' + +module Admin + class ExportDomainAllowsController < BaseController + include AdminExportControllerConcern + + before_action :set_dummy_import!, only: [:new] + + ROWS_PROCESSING_LIMIT = 20_000 + + def new + authorize :domain_allow, :create? + end + + def export + authorize :instance, :index? + send_export_file + end + + def import + authorize :domain_allow, :create? + begin + @import = Admin::Import.new(import_params) + parse_import_data!(export_headers) + + @data.take(ROWS_PROCESSING_LIMIT).each do |row| + domain = row['#domain'].strip + next if DomainAllow.allowed?(domain) + + domain_allow = DomainAllow.new(domain: domain) + log_action :create, domain_allow if domain_allow.save + end + flash[:notice] = I18n.t('admin.domain_allows.created_msg') + rescue ActionController::ParameterMissing + flash[:error] = I18n.t('admin.export_domain_allows.no_file') + end + redirect_to admin_instances_path + end + + private + + def export_filename + 'domain_allows.csv' + end + + def export_headers + %w(#domain) + end + + def export_data + CSV.generate(headers: export_headers, write_headers: true) do |content| + DomainAllow.allowed_domains.each do |instance| + content << [instance.domain] + end + end + end + end +end diff --git a/app/controllers/admin/export_domain_blocks_controller.rb b/app/controllers/admin/export_domain_blocks_controller.rb new file mode 100644 index 000000000..db8863551 --- /dev/null +++ b/app/controllers/admin/export_domain_blocks_controller.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'csv' + +module Admin + class ExportDomainBlocksController < BaseController + include AdminExportControllerConcern + + before_action :set_dummy_import!, only: [:new] + + ROWS_PROCESSING_LIMIT = 20_000 + + def new + authorize :domain_block, :create? + end + + def export + authorize :instance, :index? + send_export_file + end + + def import + authorize :domain_block, :create? + + @import = Admin::Import.new(import_params) + parse_import_data!(export_headers) + + @global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc)) + + @form = Form::DomainBlockBatch.new + @domain_blocks = @data.take(ROWS_PROCESSING_LIMIT).filter_map do |row| + domain = row['#domain'].strip + next if DomainBlock.rule_for(domain).present? + + domain_block = DomainBlock.new(domain: domain, + severity: row['#severity'].strip, + reject_media: row['#reject_media'].strip, + reject_reports: row['#reject_reports'].strip, + private_comment: @global_private_comment, + public_comment: row['#public_comment']&.strip, + obfuscate: row['#obfuscate'].strip) + + domain_block if domain_block.valid? + end + + @warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain) + rescue ActionController::ParameterMissing + flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file') + set_dummy_import! + render :new + end + + private + + def export_filename + 'domain_blocks.csv' + end + + def export_headers + %w(#domain #severity #reject_media #reject_reports #public_comment #obfuscate) + end + + def export_data + CSV.generate(headers: export_headers, write_headers: true) do |content| + DomainBlock.with_user_facing_limitations.each do |instance| + content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate] + end + end + end + end +end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index d96285b44..2e393fbb6 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -11,6 +11,7 @@ class Api::BaseController < ApplicationController skip_before_action :require_functional!, unless: :whitelist_mode? before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access? + before_action :require_not_suspended! before_action :set_cache_headers protect_from_forgery with: :null_session @@ -97,6 +98,10 @@ class Api::BaseController < ApplicationController render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user end + def require_not_suspended! + render json: { error: 'Your login is currently disabled' }, status: 403 if current_user&.account&.suspended? + end + def require_user! if !current_user render json: { error: 'This method requires an authenticated user' }, status: 422 diff --git a/app/controllers/api/v1/accounts/lookup_controller.rb b/app/controllers/api/v1/accounts/lookup_controller.rb index aee6be18a..8597f891d 100644 --- a/app/controllers/api/v1/accounts/lookup_controller.rb +++ b/app/controllers/api/v1/accounts/lookup_controller.rb @@ -12,5 +12,7 @@ class Api::V1::Accounts::LookupController < Api::BaseController def set_account @account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound) + rescue Addressable::URI::InvalidURIError + raise(ActiveRecord::RecordNotFound) end end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 5134bfb94..5537cc9b0 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -9,6 +9,8 @@ class Api::V1::AccountsController < Api::BaseController before_action :require_user!, except: [:show, :create] before_action :set_account, except: [:create] + before_action :check_account_approval, except: [:create] + before_action :check_account_confirmation, except: [:create] before_action :check_enabled_registrations, only: [:create] skip_before_action :require_authenticated_user!, only: :create @@ -74,6 +76,14 @@ class Api::V1::AccountsController < Api::BaseController @account = Account.find(params[:id]) end + def check_account_approval + raise(ActiveRecord::RecordNotFound) if @account.local? && @account.user_pending? + end + + def check_account_confirmation + raise(ActiveRecord::RecordNotFound) if @account.local? && !@account.user_confirmed? + end + def relationships(**options) AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options) end diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index aa3fb88f0..0cc231840 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -21,7 +21,7 @@ class Api::V1::BookmarksController < Api::BaseController end def results - @_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id( + @_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id( limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id) ) diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index 21836bc17..2a873696c 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -21,7 +21,7 @@ class Api::V1::FavouritesController < Api::BaseController end def results - @_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id( + @_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id( limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id) ) diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index c47d6ccfd..ac49167cb 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Api::V1::NotificationsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss] - before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss] + before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss, :destroy, :destroy_multiple] + before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss, :destroy, :destroy_multiple] before_action :require_user! after_action :insert_pagination_headers, only: :index diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 17ad56fa8..0817a905c 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -89,7 +89,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController def after_confirmation_path_for(_resource_name, user) if user.created_by_application && truthy_param?(:redirect_to_app) - user.created_by_application.redirect_uri + user.created_by_application.confirmation_redirect_uri else super end diff --git a/app/controllers/authorize_interactions_controller.rb b/app/controllers/authorize_interactions_controller.rb index f0bcac75b..97fe4a9ab 100644 --- a/app/controllers/authorize_interactions_controller.rb +++ b/app/controllers/authorize_interactions_controller.rb @@ -14,7 +14,7 @@ class AuthorizeInteractionsController < ApplicationController if @resource.is_a?(Account) render :show elsif @resource.is_a?(Status) - redirect_to web_url("statuses/#{@resource.id}") + redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}") else render :error end @@ -26,15 +26,17 @@ class AuthorizeInteractionsController < ApplicationController else render :error end - rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + rescue ActiveRecord::RecordNotFound render :error end private def set_resource - @resource = located_resource || render(:error) + @resource = located_resource authorize(@resource, :show?) if @resource.is_a?(Status) + rescue Mastodon::NotPermittedError + not_found end def located_resource diff --git a/app/controllers/concerns/admin_export_controller_concern.rb b/app/controllers/concerns/admin_export_controller_concern.rb new file mode 100644 index 000000000..013915d02 --- /dev/null +++ b/app/controllers/concerns/admin_export_controller_concern.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module AdminExportControllerConcern + extend ActiveSupport::Concern + + private + + def send_export_file + respond_to do |format| + format.csv { send_data export_data, filename: export_filename } + end + end + + def export_data + raise 'Override in controller' + end + + def export_filename + raise 'Override in controller' + end + + def set_dummy_import! + @import = Admin::Import.new + end + + def import_params + params.require(:admin_import).permit(:data) + end + + def import_data + Paperclip.io_adapters.for(@import.data).read + end + + def parse_import_data!(default_headers) + data = CSV.parse(import_data, headers: true) + data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(default_headers[0]) + @data = data.reject(&:blank?) + end +end diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index bc291c962..11c6b6d50 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -22,7 +22,10 @@ class FollowingAccountsController < ApplicationController end format.json do - raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections? + if page_requested? && @account.hide_collections? + forbidden + next + end expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb index 5596e92d1..3b228722f 100644 --- a/app/controllers/media_proxy_controller.rb +++ b/app/controllers/media_proxy_controller.rb @@ -3,6 +3,8 @@ class MediaProxyController < ApplicationController include RoutingHelper include Authorization + include Redisable + include Lockable skip_before_action :store_current_location skip_before_action :require_functional! @@ -15,14 +17,10 @@ class MediaProxyController < ApplicationController rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error def show - RedisLock.acquire(lock_options) do |lock| - if lock.acquired? - @media_attachment = MediaAttachment.remote.attached.find(params[:id]) - authorize @media_attachment.status, :show? - redownload! if @media_attachment.needs_redownload? && !reject_media? - else - raise Mastodon::RaceConditionError - end + with_lock("media_download:#{params[:id]}") do + @media_attachment = MediaAttachment.remote.attached.find(params[:id]) + authorize @media_attachment.status, :show? + redownload! if @media_attachment.needs_redownload? && !reject_media? end redirect_to full_asset_url(@media_attachment.file.url(version)) @@ -44,10 +42,6 @@ class MediaProxyController < ApplicationController end end - def lock_options - { redis: Redis.current, key: "media_download:#{params[:id]}", autorelease: 15.minutes.seconds } - end - def reject_media? DomainBlock.reject_media?(@media_attachment.account.domain) end diff --git a/app/controllers/oauth/tokens_controller.rb b/app/controllers/oauth/tokens_controller.rb index fa6d58f25..34087b20b 100644 --- a/app/controllers/oauth/tokens_controller.rb +++ b/app/controllers/oauth/tokens_controller.rb @@ -2,7 +2,8 @@ class Oauth::TokensController < Doorkeeper::TokensController def revoke - unsubscribe_for_token if authorized? && token.accessible? + unsubscribe_for_token if token.present? && authorized? && token.accessible? + super end diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb index 30138d29e..deaa7940e 100644 --- a/app/controllers/settings/exports_controller.rb +++ b/app/controllers/settings/exports_controller.rb @@ -2,6 +2,8 @@ class Settings::ExportsController < Settings::BaseController include Authorization + include Redisable + include Lockable skip_before_action :require_functional! @@ -13,21 +15,13 @@ class Settings::ExportsController < Settings::BaseController def create backup = nil - RedisLock.acquire(lock_options) do |lock| - if lock.acquired? - authorize :backup, :create? - backup = current_user.backups.create! - else - raise Mastodon::RaceConditionError - end + with_lock("backup:#{current_user.id}") do + authorize :backup, :create? + backup = current_user.backups.create! end BackupWorker.perform_async(backup.id) redirect_to settings_export_path end - - def lock_options - { redis: Redis.current, key: "backup:#{current_user.id}" } - end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 64736e77f..46821a200 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -27,7 +27,6 @@ class TagsController < ApplicationController format.rss do expires_in 0, public: true - render xml: RSS::TagSerializer.render(@tag, @statuses) end format.json do diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ce25e26f9..b26e68c4d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -132,7 +132,7 @@ module ApplicationHelper elsif status.private_visibility? || status.limited_visibility? fa_icon('lock', title: I18n.t('statuses.visibilities.private')) elsif status.direct_visibility? - fa_icon('envelope', title: I18n.t('statuses.visibilities.direct')) + fa_icon('at', title: I18n.t('statuses.visibilities.direct')) end end @@ -244,7 +244,7 @@ module ApplicationHelper end.values end - def prerender_custom_emojis(html, custom_emojis) - EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s + def prerender_custom_emojis(html, custom_emojis, other_options = {}) + EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end end diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index 53e100dd2..448177bec 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -18,6 +18,32 @@ module FormattingHelper html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type) end + def rss_status_content_format(status) + html = status_content_format(status) + + before_html = begin + if status.spoiler_text? + "

#{I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)} #{h(status.spoiler_text)}


" + else + '' + end + end.html_safe # rubocop:disable Rails/OutputSafety + + after_html = begin + if status.preloadable_poll + "

#{status.preloadable_poll.options.map { |o| " #{h(o)}" }.join('
')}

" + else + '' + end + end.html_safe # rubocop:disable Rails/OutputSafety + + prerender_custom_emojis( + safe_join([before_html, html, after_html]), + status.emojis, + style: 'width: 1.1em; height: 1.1em; object-fit: contain; vertical-align: middle; margin: -.2ex .15em .2ex' + ).to_str + end + def account_bio_format(account) html_aware_format(account.note, account.local?) end diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index d39bb6c93..4077e19bd 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -254,4 +254,8 @@ module LanguagesHelper def valid_locale?(locale) locale.present? && SUPPORTED_LOCALES.key?(locale.to_sym) end + + def available_locale_or_nil(locale_name) + locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym) + end end diff --git a/app/helpers/settings/keyword_mutes_helper.rb b/app/helpers/settings/keyword_mutes_helper.rb deleted file mode 100644 index 7b98cd59e..000000000 --- a/app/helpers/settings/keyword_mutes_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Settings::KeywordMutesHelper -end diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index e92b4c839..488eabeec 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -101,7 +101,7 @@ module StatusesHelper when 'private' fa_icon 'lock fw' when 'direct' - fa_icon 'envelope fw' + fa_icon 'at fw' end end diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js index d2db89ca7..c1b9f07a4 100644 --- a/app/javascript/core/admin.js +++ b/app/javascript/core/admin.js @@ -101,4 +101,20 @@ ready(() => { const registrationMode = document.getElementById('form_admin_settings_registrations_mode'); if (registrationMode) onChangeRegistrationMode(registrationMode); + + const checkAllElement = document.querySelector('#batch_checkbox_all'); + if (checkAllElement) { + checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); + checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); + } + + document.querySelector('a#add-instance-button')?.addEventListener('click', (e) => { + const domain = document.getElementById('by_domain')?.value; + + if (domain) { + const url = new URL(event.target.href); + url.searchParams.set('_domain', domain); + e.target.href = url; + } + }); }); diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js index 0cf64e076..f5871beb3 100644 --- a/app/javascript/flavours/glitch/actions/accounts.js +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -88,6 +88,8 @@ export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE = 'PINNED_ACCOUNTS_EDITOR export const PINNED_ACCOUNTS_EDITOR_RESET = 'PINNED_ACCOUNTS_EDITOR_RESET'; +export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL'; + export function fetchAccount(id) { return (dispatch, getState) => { dispatch(fetchRelationships([id])); @@ -798,6 +800,11 @@ export function unpinAccountFail(error) { }; }; +export const revealAccount = id => ({ + type: ACCOUNT_REVEAL, + id, +}); + export function fetchPinnedAccounts() { return (dispatch, getState) => { dispatch(fetchPinnedAccountsRequest()); diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index baa98e98f..ab74fb303 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -48,12 +48,13 @@ export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'; -export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; -export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; +export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; +export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; -export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; -export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; +export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; +export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE'; +export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; @@ -189,6 +190,7 @@ export function submitCompose(routerHistory) { spoiler_text: spoilerText, visibility: getState().getIn(['compose', 'privacy']), poll: getState().getIn(['compose', 'poll'], null), + language: getState().getIn(['compose', 'language']), }, headers: { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), @@ -675,6 +677,11 @@ export function changeComposeSensitivity() { }; }; +export const changeComposeLanguage = language => ({ + type: COMPOSE_LANGUAGE_CHANGE, + language, +}); + export function changeComposeSpoilerness() { return { type: COMPOSE_SPOILERNESS_CHANGE, diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index bda15a9b0..c38af196a 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -1,7 +1,6 @@ import escapeTextContentForBrowser from 'escape-html'; import emojify from 'flavours/glitch/util/emoji'; import { unescapeHTML } from 'flavours/glitch/util/html'; -import { expandSpoilers } from 'flavours/glitch/util/initial_state'; const domParser = new DOMParser(); diff --git a/app/javascript/flavours/glitch/actions/languages.js b/app/javascript/flavours/glitch/actions/languages.js new file mode 100644 index 000000000..ad186ba0c --- /dev/null +++ b/app/javascript/flavours/glitch/actions/languages.js @@ -0,0 +1,12 @@ +import { saveSettings } from './settings'; + +export const LANGUAGE_USE = 'LANGUAGE_USE'; + +export const useLanguage = language => dispatch => { + dispatch({ + type: LANGUAGE_USE, + language, + }); + + dispatch(saveSettings()); +}; diff --git a/app/javascript/flavours/glitch/actions/local_settings.js b/app/javascript/flavours/glitch/actions/local_settings.js index 28660a4e8..856674eb3 100644 --- a/app/javascript/flavours/glitch/actions/local_settings.js +++ b/app/javascript/flavours/glitch/actions/local_settings.js @@ -1,4 +1,46 @@ +import { expandSpoilers, disableSwiping } from 'flavours/glitch/util/initial_state'; +import { openModal } from './modal'; + export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; +export const LOCAL_SETTING_DELETE = 'LOCAL_SETTING_DELETE'; + +export function checkDeprecatedLocalSettings() { + return (dispatch, getState) => { + const local_auto_unfold = getState().getIn(['local_settings', 'content_warnings', 'auto_unfold']); + const local_swipe_to_change_columns = getState().getIn(['local_settings', 'swipe_to_change_columns']); + let changed_settings = []; + + if (local_auto_unfold !== null && local_auto_unfold !== undefined) { + if (local_auto_unfold === expandSpoilers) { + dispatch(deleteLocalSetting(['content_warnings', 'auto_unfold'])); + } else { + changed_settings.push('user_setting_expand_spoilers'); + } + } + + if (local_swipe_to_change_columns !== null && local_swipe_to_change_columns !== undefined) { + if (local_swipe_to_change_columns === !disableSwiping) { + dispatch(deleteLocalSetting(['swipe_to_change_columns'])); + } else { + changed_settings.push('user_setting_disable_swiping'); + } + } + + if (changed_settings.length > 0) { + dispatch(openModal('DEPRECATED_SETTINGS', { + settings: changed_settings, + onConfirm: () => dispatch(clearDeprecatedLocalSettings()), + })); + } + }; +}; + +export function clearDeprecatedLocalSettings() { + return (dispatch) => { + dispatch(deleteLocalSetting(['content_warnings', 'auto_unfold'])); + dispatch(deleteLocalSetting(['swipe_to_change_columns'])); + }; +}; export function changeLocalSetting(key, value) { return dispatch => { @@ -12,6 +54,17 @@ export function changeLocalSetting(key, value) { }; }; +export function deleteLocalSetting(key) { + return dispatch => { + dispatch({ + type: LOCAL_SETTING_DELETE, + key, + }); + + dispatch(saveLocalSettings()); + }; +}; + // __TODO :__ // Right now `saveLocalSettings()` doesn't keep track of which user // is currently signed in, but it might be better to give each user diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 42ad39efa..85938867b 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -70,7 +70,8 @@ export const loadPending = () => ({ export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { - const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); + const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']); + const showInColumn = activeFilter === 'all' ? getState().getIn(['settings', 'notifications', 'shows', notification.type], true) : activeFilter === notification.type; const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); const filters = getFiltersRegex(getState(), { contextType: 'notifications' }); diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js index 396a36ea0..489f60736 100644 --- a/app/javascript/flavours/glitch/components/account.js +++ b/app/javascript/flavours/glitch/components/account.js @@ -16,8 +16,10 @@ const messages = defineMessages({ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, - mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' }, - unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' }, + mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' }, + unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' }, + mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, + block: { id: 'account.block', defaultMessage: 'Block @{name}' }, }); export default @injectIntl @@ -34,6 +36,7 @@ class Account extends ImmutablePureComponent { small: PropTypes.bool, actionIcon: PropTypes.string, actionTitle: PropTypes.string, + defaultAction: PropTypes.string, onActionClick: PropTypes.func, }; @@ -70,6 +73,7 @@ class Account extends ImmutablePureComponent { onActionClick, actionIcon, actionTitle, + defaultAction, } = this.props; if (!account) { @@ -114,6 +118,10 @@ class Account extends ImmutablePureComponent { {hidingNotificationsButton} ); + } else if (defaultAction === 'mute') { + buttons = ; + } else if (defaultAction === 'block') { + buttons = ; } else if (!account.get('moved') || following) { buttons = ; } diff --git a/app/javascript/flavours/glitch/components/avatar.js b/app/javascript/flavours/glitch/components/avatar.js index c5e9072c4..6d53a5298 100644 --- a/app/javascript/flavours/glitch/components/avatar.js +++ b/app/javascript/flavours/glitch/components/avatar.js @@ -1,13 +1,13 @@ -import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import classNames from 'classnames'; export default class Avatar extends React.PureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + account: ImmutablePropTypes.map, className: PropTypes.string, size: PropTypes.number.isRequired, style: PropTypes.object, @@ -45,11 +45,6 @@ export default class Avatar extends React.PureComponent { } = this.props; const { hovering } = this.state; - const src = account.get('avatar'); - const staticSrc = account.get('avatar_static'); - - const computedClass = classNames('account__avatar', { 'account__avatar-inline': inline }, className); - const style = { ...this.props.style, width: `${size}px`, @@ -57,19 +52,24 @@ export default class Avatar extends React.PureComponent { backgroundSize: `${size}px ${size}px`, }; - if (hovering || animate) { - style.backgroundImage = `url(${src})`; - } else { - style.backgroundImage = `url(${staticSrc})`; + if (account) { + const src = account.get('avatar'); + const staticSrc = account.get('avatar_static'); + + if (hovering || animate) { + style.backgroundImage = `url(${src})`; + } else { + style.backgroundImage = `url(${staticSrc})`; + } } return (
); } diff --git a/app/javascript/flavours/glitch/components/common_counter.js b/app/javascript/flavours/glitch/components/common_counter.js index e10cd9b76..dd9b62de9 100644 --- a/app/javascript/flavours/glitch/components/common_counter.js +++ b/app/javascript/flavours/glitch/components/common_counter.js @@ -25,7 +25,7 @@ export function counterRenderer(counterType, isBold = true) { return (displayNumber, pluralReady) => ( ); - mediaIcons.push('tasks'); - } + if (usingPiP) { media.push(); mediaIcons.push('video-camera'); @@ -684,6 +682,11 @@ class Status extends ImmutablePureComponent { mediaIcons.push('link'); } + if (status.get('poll')) { + media.push(); + mediaIcons.push('tasks'); + } + // Here we prepare extra data-* attributes for CSS selectors. // Users can use those for theming, hiding avatars etc via UserStyle const selectorAttribs = { @@ -753,6 +756,7 @@ class Status extends ImmutablePureComponent { collapsed={isCollapsed} setCollapsed={setCollapsed} directMessage={!!otherAccounts} + settings={settings.get('status_icons')} /> { + if (!languages) return null; + + const lang = languages.find((lang) => lang[0] === language); + if (!lang) return null; + + return ( + + ); +}; + +LanguageIcon.propTypes = { + language: PropTypes.string.isRequired, +}; + export default @injectIntl class StatusIcons extends React.PureComponent { @@ -33,6 +51,7 @@ class StatusIcons extends React.PureComponent { directMessage: PropTypes.bool, setCollapsed: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + settings: ImmutablePropTypes.map.isRequired, }; // Handles clicks on collapsed button @@ -82,12 +101,14 @@ class StatusIcons extends React.PureComponent { collapsible, collapsed, directMessage, + settings, intl, } = this.props; return (
- {status.get('in_reply_to_id', null) !== null ? ( + {settings.get('language') && status.get('language') && } + {settings.get('reply') && status.get('in_reply_to_id', null) !== null ? ( ) : null} - {status.get('local_only') && + {settings.get('local_only') && status.get('local_only') &&
); } diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index 1661ca8f5..d85009362 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -38,7 +38,7 @@ export default class StatusPrepend extends React.PureComponent { switch (type) { case 'featured': return ( - + ); case 'reblogged_by': return ( diff --git a/app/javascript/flavours/glitch/components/status_visibility_icon.js b/app/javascript/flavours/glitch/components/status_visibility_icon.js index e2e0f30b8..07d56c7a8 100644 --- a/app/javascript/flavours/glitch/components/status_visibility_icon.js +++ b/app/javascript/flavours/glitch/components/status_visibility_icon.js @@ -9,7 +9,7 @@ const messages = defineMessages({ public: { id: 'privacy.public.short', defaultMessage: 'Public' }, unlisted: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, private: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, - direct: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, + direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); export default @injectIntl diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js index de8ea8ee2..989e37024 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.js +++ b/app/javascript/flavours/glitch/containers/mastodon.js @@ -8,6 +8,7 @@ import UI from 'flavours/glitch/features/ui'; import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis'; import { hydrateStore } from 'flavours/glitch/actions/store'; import { connectUserStream } from 'flavours/glitch/actions/streaming'; +import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from 'locales'; import initialState from 'flavours/glitch/util/initial_state'; @@ -20,6 +21,9 @@ export const store = configureStore(); const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); +// check for deprecated local settings +store.dispatch(checkDeprecatedLocalSettings()); + // load custom emojis store.dispatch(fetchCustomEmojis()); diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 4b0494fff..45aba53f7 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -37,7 +37,7 @@ const messages = defineMessages({ showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' }, disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' }, - pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, + pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, @@ -82,6 +82,7 @@ class Header extends ImmutablePureComponent { onEditAccountNote: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, domain: PropTypes.string.isRequired, + hidden: PropTypes.bool, }; openEditProfile = () => { @@ -115,7 +116,7 @@ class Header extends ImmutablePureComponent { } render () { - const { account, intl, domain, identity_proofs } = this.props; + const { account, hidden, intl, domain } = this.props; if (!account) { return null; @@ -270,23 +271,29 @@ class Header extends ImmutablePureComponent { {info}
- + {!(suspended || hidden) && }
- +
-
- {actionBtn} - {bellBtn} + {!suspended && ( +
+ {!hidden && ( + + {actionBtn} + {bellBtn} + + )} - -
+ +
+ )}
@@ -298,23 +305,11 @@ class Header extends ImmutablePureComponent { - {!suspended && ( + {!(suspended || hidden) && (
- { (fields.size > 0 || identity_proofs.size > 0) && ( + { fields.size > 0 && (
- {identity_proofs.map((proof, i) => ( -
-
- -
- - - - -
-
- ))} {fields.map((pair, i) => (
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index e70f011b7..645ff29ea 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -12,7 +12,6 @@ export default class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, - identity_proofs: ImmutablePropTypes.list, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -26,6 +25,7 @@ export default class Header extends ImmutablePureComponent { onAddToList: PropTypes.func.isRequired, hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, + hidden: PropTypes.bool, }; static contextTypes = { @@ -93,7 +93,7 @@ export default class Header extends ImmutablePureComponent { } render () { - const { account, hideTabs, identity_proofs } = this.props; + const { account, hidden, hideTabs } = this.props; if (account === null) { return null; @@ -101,11 +101,10 @@ export default class Header extends ImmutablePureComponent { return (
- {account.get('moved') && } + {(!hidden && account.get('moved')) && }