diff --git a/Gemfile b/Gemfile
index 4ca62d76b4..c79db13be0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,10 +5,10 @@ ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.4'
-gem 'puma', '~> 4.3'
-gem 'rails', '~> 5.2.4.3'
+gem 'puma', '~> 5.0'
+gem 'rails', '~> 5.2.4.4'
gem 'sprockets', '~> 3.7.2'
-gem 'thor', '~> 0.20'
+gem 'thor', '~> 1.0'
gem 'rack', '~> 2.2.3'
gem 'thwait', '~> 0.2.0'
@@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.7'
gem 'dotenv-rails', '~> 2.7'
-gem 'aws-sdk-s3', '~> 1.79', require: false
+gem 'aws-sdk-s3', '~> 1.81', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
@@ -123,26 +123,26 @@ end
group :test do
gem 'capybara', '~> 3.33'
gem 'climate_control', '~> 0.2'
- gem 'faker', '~> 2.13'
+ gem 'faker', '~> 2.14'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.19', require: false
- gem 'webmock', '~> 3.8'
- gem 'parallel_tests', '~> 3.2'
+ gem 'webmock', '~> 3.9'
+ gem 'parallel_tests', '~> 3.3'
gem 'rspec_junit_formatter', '~> 0.4'
end
group :development do
gem 'active_record_query_trace', '~> 1.7'
gem 'annotate', '~> 3.1'
- gem 'better_errors', '~> 2.7'
+ gem 'better_errors', '~> 2.8'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
- gem 'rubocop', '~> 0.90', require: false
+ gem 'rubocop', '~> 0.91', require: false
gem 'rubocop-rails', '~> 2.8', require: false
gem 'brakeman', '~> 4.9', require: false
gem 'bundler-audit', '~> 0.7', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 581d494ab6..6f9c6a6f90 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -16,25 +16,25 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- actioncable (5.2.4.3)
- actionpack (= 5.2.4.3)
+ actioncable (5.2.4.4)
+ actionpack (= 5.2.4.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailer (5.2.4.3)
- actionpack (= 5.2.4.3)
- actionview (= 5.2.4.3)
- activejob (= 5.2.4.3)
+ actionmailer (5.2.4.4)
+ actionpack (= 5.2.4.4)
+ actionview (= 5.2.4.4)
+ activejob (= 5.2.4.4)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.2.4.3)
- actionview (= 5.2.4.3)
- activesupport (= 5.2.4.3)
+ actionpack (5.2.4.4)
+ actionview (= 5.2.4.4)
+ activesupport (= 5.2.4.4)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.2.4.3)
- activesupport (= 5.2.4.3)
+ actionview (5.2.4.4)
+ activesupport (= 5.2.4.4)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -45,20 +45,20 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.7)
- activejob (5.2.4.3)
- activesupport (= 5.2.4.3)
+ activejob (5.2.4.4)
+ activesupport (= 5.2.4.4)
globalid (>= 0.3.6)
- activemodel (5.2.4.3)
- activesupport (= 5.2.4.3)
- activerecord (5.2.4.3)
- activemodel (= 5.2.4.3)
- activesupport (= 5.2.4.3)
+ activemodel (5.2.4.4)
+ activesupport (= 5.2.4.4)
+ activerecord (5.2.4.4)
+ activemodel (= 5.2.4.4)
+ activesupport (= 5.2.4.4)
arel (>= 9.0)
- activestorage (5.2.4.3)
- actionpack (= 5.2.4.3)
- activerecord (= 5.2.4.3)
+ activestorage (5.2.4.4)
+ actionpack (= 5.2.4.4)
+ activerecord (= 5.2.4.4)
marcel (~> 0.3.1)
- activesupport (5.2.4.3)
+ activesupport (5.2.4.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -79,23 +79,23 @@ GEM
cocaine (~> 0.5.3)
awrence (1.1.1)
aws-eventstream (1.1.0)
- aws-partitions (1.365.0)
- aws-sdk-core (3.105.0)
+ aws-partitions (1.373.0)
+ aws-sdk-core (3.107.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.37.0)
+ aws-sdk-kms (1.38.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.79.1)
+ aws-sdk-s3 (1.81.0)
aws-sdk-core (~> 3, >= 3.104.3)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.16)
- better_errors (2.7.1)
+ better_errors (2.8.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@@ -160,13 +160,12 @@ GEM
cose (1.0.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 0.4.0)
- crack (0.4.3)
- safe_yaml (~> 1.0.0)
+ crack (0.4.4)
crass (1.0.6)
css_parser (1.7.1)
addressable
debug_inspector (0.0.3)
- devise (4.7.2)
+ devise (4.7.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -210,7 +209,7 @@ GEM
tzinfo
excon (0.76.0)
fabrication (2.21.1)
- faker (2.13.0)
+ faker (2.14.0)
i18n (>= 1.6, < 2)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
@@ -233,7 +232,7 @@ GEM
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
- fugit (1.3.8)
+ fugit (1.3.9)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.3)
fuubar (2.5.0)
@@ -363,7 +362,7 @@ GEM
net-scp (3.0.0)
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0)
- nio4r (2.5.3)
+ nio4r (2.5.4)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.2)
@@ -387,7 +386,7 @@ GEM
openssl (2.2.0)
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0)
- ox (2.13.3)
+ ox (2.13.4)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@@ -398,7 +397,7 @@ GEM
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.19.2)
- parallel_tests (3.2.0)
+ parallel_tests (3.3.0)
parallel
parser (2.7.1.4)
ast (~> 2.4.1)
@@ -406,11 +405,11 @@ GEM
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.2.3)
- pghero (2.7.0)
+ pghero (2.7.2)
activerecord (>= 5)
- pkg-config (1.4.2)
+ pkg-config (1.4.3)
posix-spawn (0.3.15)
- premailer (1.13.1)
+ premailer (1.14.2)
addressable
css_parser (>= 1.6.0)
htmlentities (>= 4.0.0)
@@ -427,7 +426,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
- puma (4.3.6)
+ puma (5.0.0)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
@@ -441,18 +440,18 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rails (5.2.4.3)
- actioncable (= 5.2.4.3)
- actionmailer (= 5.2.4.3)
- actionpack (= 5.2.4.3)
- actionview (= 5.2.4.3)
- activejob (= 5.2.4.3)
- activemodel (= 5.2.4.3)
- activerecord (= 5.2.4.3)
- activestorage (= 5.2.4.3)
- activesupport (= 5.2.4.3)
+ rails (5.2.4.4)
+ actioncable (= 5.2.4.4)
+ actionmailer (= 5.2.4.4)
+ actionpack (= 5.2.4.4)
+ actionview (= 5.2.4.4)
+ activejob (= 5.2.4.4)
+ activemodel (= 5.2.4.4)
+ activerecord (= 5.2.4.4)
+ activestorage (= 5.2.4.4)
+ activesupport (= 5.2.4.4)
bundler (>= 1.3.0)
- railties (= 5.2.4.3)
+ railties (= 5.2.4.4)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@@ -468,9 +467,9 @@ GEM
railties (>= 5.0, < 6)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
- railties (5.2.4.3)
- actionpack (= 5.2.4.3)
- activesupport (= 5.2.4.3)
+ railties (5.2.4.4)
+ actionpack (= 5.2.4.4)
+ activesupport (= 5.2.4.4)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
@@ -482,7 +481,7 @@ GEM
rdf-normalize (0.4.0)
rdf (~> 3.1)
redcarpet (3.5.0)
- redis (4.2.1)
+ redis (4.2.2)
redis-actionpack (5.2.0)
actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3)
@@ -501,7 +500,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.9.0)
redis (>= 4, < 5)
- regexp_parser (1.7.1)
+ regexp_parser (1.8.0)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.1)
@@ -536,18 +535,18 @@ GEM
rspec-support (3.9.3)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (0.90.0)
+ rubocop (0.91.0)
parallel (~> 1.10)
parser (>= 2.7.1.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7)
rexml
- rubocop-ast (>= 0.3.0, < 1.0)
+ rubocop-ast (>= 0.4.0, < 1.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
- rubocop-ast (0.3.0)
+ rubocop-ast (0.4.2)
parser (>= 2.7.1.4)
- rubocop-rails (2.8.0)
+ rubocop-rails (2.8.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.87.0)
@@ -556,7 +555,6 @@ GEM
nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
- safe_yaml (1.0.5)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (5.2.1)
@@ -565,7 +563,7 @@ GEM
nokogumbo (~> 2.0)
securecompare (1.0.0)
semantic_range (2.3.0)
- sidekiq (6.1.1)
+ sidekiq (6.1.2)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
@@ -594,7 +592,7 @@ GEM
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.2.1)
+ sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@@ -613,7 +611,7 @@ GEM
unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
- thor (0.20.3)
+ thor (1.0.1)
thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
@@ -654,7 +652,7 @@ GEM
safety_net_attestation (~> 0.4.0)
securecompare (~> 1.0)
tpm-key_attestation (~> 0.9.0)
- webmock (3.8.3)
+ webmock (3.9.1)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -681,8 +679,8 @@ DEPENDENCIES
active_record_query_trace (~> 1.7)
addressable (~> 2.7)
annotate (~> 3.1)
- aws-sdk-s3 (~> 1.79)
- better_errors (~> 2.7)
+ aws-sdk-s3 (~> 1.81)
+ better_errors (~> 2.8)
binding_of_caller (~> 0.7)
blurhash (~> 0.1)
bootsnap (~> 1.4)
@@ -711,7 +709,7 @@ DEPENDENCIES
e2mmap (~> 0.1.0)
ed25519 (~> 1.2)
fabrication (~> 2.21)
- faker (~> 2.13)
+ faker (~> 2.14)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
@@ -752,7 +750,7 @@ DEPENDENCIES
paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6)
parallel (~> 1.19)
- parallel_tests (~> 3.2)
+ parallel_tests (~> 3.3)
parslet
pg (~> 1.2)
pghero (~> 2.7)
@@ -762,12 +760,12 @@ DEPENDENCIES
private_address_check (~> 0.5)
pry-byebug (~> 3.9)
pry-rails (~> 0.3)
- puma (~> 4.3)
+ puma (~> 5.0)
pundit (~> 2.1)
rack (~> 2.2.3)
rack-attack (~> 6.3)
rack-cors (~> 1.1)
- rails (~> 5.2.4.3)
+ rails (~> 5.2.4.4)
rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6)
@@ -780,7 +778,7 @@ DEPENDENCIES
rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
- rubocop (~> 0.90)
+ rubocop (~> 0.91)
rubocop-rails (~> 2.8)
ruby-progressbar (~> 1.10)
sanitize (~> 5.2)
@@ -797,12 +795,12 @@ DEPENDENCIES
stoplight (~> 2.2.1)
streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.7)
- thor (~> 0.20)
+ thor (~> 1.0)
thwait (~> 0.2.0)
tty-prompt (~> 0.22)
twitter-text (~> 1.14)
tzinfo-data (~> 1.2020)
webauthn (~> 3.0.0.alpha1)
- webmock (~> 3.8)
+ webmock (~> 3.9)
webpacker (~> 5.2)
webpush
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 54106933c7..3565427672 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -7,6 +7,7 @@ class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureAuthentication
+ before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
before_action :set_body_classes
@@ -49,7 +50,7 @@ class AccountsController < ApplicationController
format.json do
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
- render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
+ render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
end
end
end
@@ -154,12 +155,4 @@ class AccountsController < ApplicationController
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end
-
- def restrict_fields_to
- if signed_request_account.present? || public_fetch_mode?
- # Return all fields
- else
- %i(id type preferred_username inbox public_key endpoints)
- end
- end
end
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index e066860bfe..5fd735ad6a 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -57,9 +57,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses
return unless page_requested?
- @statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = cache_collection_paginated_by_id(
- @statuses,
+ @account.statuses.permitted_for(@account, signed_request_account),
Status,
LIMIT,
params_slice(:max_id, :min_id, :since_id)
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 7b17835429..b9b75727dd 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,7 +2,7 @@
module Admin
class AccountsController < BaseController
- before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
+ before_action :set_account, except: [:index]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@@ -14,49 +14,58 @@ module Admin
def show
authorize @account, :show?
+ @deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom
+ @domain_block = DomainBlock.rule_for(@account.domain)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
end
def approve
authorize @account.user, :approve?
@account.user.approve!
- redirect_to admin_pending_accounts_path
+ redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
- redirect_to admin_pending_accounts_path
+ DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
+ end
+
+ def destroy
+ authorize @account, :destroy?
+ Admin::AccountDeletionWorker.perform_async(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
+ Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
end
def redownload
@@ -65,7 +74,7 @@ module Admin
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
end
def remove_avatar
@@ -76,7 +85,7 @@ module Admin
log_action :remove_avatar, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
end
def remove_header
@@ -87,7 +96,7 @@ module Admin
log_action :remove_header, @account.user
- redirect_to admin_account_path(@account.id)
+ redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
end
private
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 4672255475..e962c4e97f 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -96,12 +96,12 @@ class Api::BaseController < ApplicationController
def require_user!
if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422
- elsif current_user.disabled?
- render json: { error: 'Your login is currently disabled' }, status: 403
elsif !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403
+ elsif !current_user.functional?
+ render json: { error: 'Your login is currently disabled' }, status: 403
else
set_user_activity
end
diff --git a/app/controllers/api/v1/accounts/featured_tags_controller.rb b/app/controllers/api/v1/accounts/featured_tags_controller.rb
index d6277261d4..014d719567 100644
--- a/app/controllers/api/v1/accounts/featured_tags_controller.rb
+++ b/app/controllers/api/v1/accounts/featured_tags_controller.rb
@@ -17,6 +17,6 @@ class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
end
def set_featured_tags
- @featured_tags = @account.featured_tags
+ @featured_tags = @account.suspended? ? @account.featured_tags : []
end
end
diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
index 2277067c9f..a665863ebf 100644
--- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
@@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end
def hide_results?
- (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
+ @account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts
diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb
index 93d4bd3a4a..7d885a212f 100644
--- a/app/controllers/api/v1/accounts/following_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb
@@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end
def hide_results?
- (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
+ @account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts
diff --git a/app/controllers/api/v1/accounts/identity_proofs_controller.rb b/app/controllers/api/v1/accounts/identity_proofs_controller.rb
index 8dad6fee96..4b5f6902c7 100644
--- a/app/controllers/api/v1/accounts/identity_proofs_controller.rb
+++ b/app/controllers/api/v1/accounts/identity_proofs_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :set_account
def index
- @proofs = @account.identity_proofs.active
+ @proofs = @account.suspended? ? [] : @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer
end
diff --git a/app/controllers/api/v1/accounts/lists_controller.rb b/app/controllers/api/v1/accounts/lists_controller.rb
index ccb751f8f7..c92f1f8a08 100644
--- a/app/controllers/api/v1/accounts/lists_controller.rb
+++ b/app/controllers/api/v1/accounts/lists_controller.rb
@@ -6,7 +6,7 @@ class Api::V1::Accounts::ListsController < Api::BaseController
before_action :set_account
def index
- @lists = @account.lists.where(account: current_account)
+ @lists = @account.suspended? ? [] : @account.lists.where(account: current_account)
render json: @lists, each_serializer: REST::ListSerializer
end
diff --git a/app/controllers/api/v1/accounts/relationships_controller.rb b/app/controllers/api/v1/accounts/relationships_controller.rb
index 1d3992a285..503f85c97d 100644
--- a/app/controllers/api/v1/accounts/relationships_controller.rb
+++ b/app/controllers/api/v1/accounts/relationships_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!
def index
- accounts = Account.where(id: account_ids).select('id')
+ accounts = Account.without_suspended.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 85a9133e3a..92ccb80615 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -18,7 +18,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def load_statuses
- cached_account_statuses
+ @account.suspended? ? [] : cached_account_statuses
end
def cached_account_statuses
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 0080faf330..aef51a6479 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -9,7 +9,6 @@ class Api::V1::AccountsController < Api::BaseController
before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create]
- before_action :check_account_suspension, only: [:show]
before_action :check_enabled_registrations, only: [:create]
skip_before_action :require_authenticated_user!, only: :create
@@ -31,9 +30,8 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
- FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true)
-
- options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
+ follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
+ options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end
@@ -73,10 +71,6 @@ class Api::V1::AccountsController < Api::BaseController
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
end
- def check_account_suspension
- gone if @account.suspended?
- end
-
def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason)
end
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 24c7fbef12..3af572f25e 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -58,7 +58,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject
authorize @account.user, :reject?
- SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ render json: @account, serializer: REST::Admin::AccountSerializer
+ end
+
+ def destroy
+ authorize @account, :destroy?
+ Admin::AccountDeletionWorker.perform_async(@account.id)
render json: @account, serializer: REST::Admin::AccountSerializer
end
@@ -72,6 +78,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
+ Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb
index a2baeef900..586cdfca9d 100644
--- a/app/controllers/api/v1/blocks_controller.rb
+++ b/app/controllers/api/v1/blocks_controller.rb
@@ -18,6 +18,8 @@ class Api::V1::BlocksController < Api::BaseController
def paginated_blocks
@paginated_blocks ||= Block.eager_load(target_account: :account_stat)
+ .joins(:target_account)
+ .merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb
index c87dbc4ce8..9e80f468a7 100644
--- a/app/controllers/api/v1/endorsements_controller.rb
+++ b/app/controllers/api/v1/endorsements_controller.rb
@@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController
end
def endorsed_accounts
- current_account.endorsed_accounts.includes(:account_stat)
+ current_account.endorsed_accounts.includes(:account_stat).without_suspended
end
def insert_pagination_headers
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index 0ee6e531f0..b34c76f29e 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize
AuthorizeFollowService.new.call(account, current_account)
- NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
+ NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account))
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end
@@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end
def default_accounts
- Account.includes(:follow_requests, :account_stat).references(:follow_requests)
+ Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests)
end
def paginated_follow_requests
diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb
index 23078263e7..b66ea9bfe6 100644
--- a/app/controllers/api/v1/lists/accounts_controller.rb
+++ b/app/controllers/api/v1/lists/accounts_controller.rb
@@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts
if unlimited?
- @list.accounts.includes(:account_stat).all
+ @list.accounts.without_suspended.includes(:account_stat).all
else
- @list.accounts.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
+ @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end
end
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index 5dc047b43d..a89f3d7002 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -27,6 +27,8 @@ class Api::V1::MutesController < Api::BaseController
def paginated_mutes
@paginated_mutes ||= Mute.eager_load(:target_account)
+ .joins(:target_account)
+ .merge(Account.without_suspended)
.where(account: current_account)
.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index 9ff1683673..fda3482651 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -14,7 +14,7 @@ class Api::V1::NotificationsController < Api::BaseController
end
def show
- @notification = current_account.notifications.find(params[:id])
+ @notification = current_account.notifications.without_suspended.find(params[:id])
render json: @notification, serializer: REST::NotificationSerializer
end
@@ -49,7 +49,7 @@ class Api::V1::NotificationsController < Api::BaseController
end
def browserable_account_notifications
- current_account.notifications.browserable(exclude_types, from_account)
+ current_account.notifications.without_suspended.browserable(exclude_types, from_account)
end
def target_statuses_from_notifications
diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb
index d34b333eb3..0918c61e97 100644
--- a/app/controllers/api/v1/push/subscriptions_controller.rb
+++ b/app/controllers/api/v1/push/subscriptions_controller.rb
@@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params
return {} if params[:data].blank?
- params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
+ params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end
diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
index 8229786d6c..2b614a8375 100644
--- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
@@ -22,6 +22,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts
Account
+ .without_suspended
.includes(:favourites, :account_stat)
.references(:favourites)
.where(favourites: { status_id: @status.id })
diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
index 6c9e49d903..24db30fcc0 100644
--- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
@@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end
def default_accounts
- Account.includes(:statuses, :account_stat).references(:statuses)
+ Account.without_suspended.includes(:statuses, :account_stat).references(:statuses)
end
def paginated_statuses
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index 7916b82fa0..1dce3e70f2 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
reblog: alerts_enabled,
mention: alerts_enabled,
poll: alerts_enabled,
+ status: alerts_enabled,
},
}
@@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def data_params
- @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
+ @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end
diff --git a/app/controllers/concerns/export_controller_concern.rb b/app/controllers/concerns/export_controller_concern.rb
index bfe990c827..24cfc7a012 100644
--- a/app/controllers/concerns/export_controller_concern.rb
+++ b/app/controllers/concerns/export_controller_concern.rb
@@ -5,7 +5,6 @@ module ExportControllerConcern
included do
before_action :authenticate_user!
- before_action :require_not_suspended!
before_action :load_export
skip_before_action :require_functional!
@@ -30,8 +29,4 @@ module ExportControllerConcern
def export_filename
"#{controller_name}.csv"
end
-
- def require_not_suspended!
- forbidden if current_account.suspended?
- end
end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index c5ccece134..b2564a7915 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -6,6 +6,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location
before_action :authenticate_resource_owner!
before_action :set_pack
+ before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes
skip_before_action :require_functional!
@@ -30,4 +31,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def set_pack
use_pack 'settings'
end
+
+ def require_not_suspended!
+ forbidden if current_account.suspended?
+ end
end
diff --git a/app/controllers/settings/aliases_controller.rb b/app/controllers/settings/aliases_controller.rb
index b7c9a409d1..a421b8ede3 100644
--- a/app/controllers/settings/aliases_controller.rb
+++ b/app/controllers/settings/aliases_controller.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
class Settings::AliasesController < Settings::BaseController
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
+ before_action :require_not_suspended!
before_action :set_aliases, except: :destroy
before_action :set_alias, only: :destroy
diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb
index ed3f82a8e0..d3ac268d86 100644
--- a/app/controllers/settings/applications_controller.rb
+++ b/app/controllers/settings/applications_controller.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::ApplicationsController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update]
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index b97603af6f..dee3922d80 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -2,6 +2,9 @@
class Settings::BaseController < ApplicationController
before_action :set_pack
+ layout 'admin'
+
+ before_action :authenticate_user!
before_action :set_body_classes
before_action :set_cache_headers
@@ -18,4 +21,8 @@ class Settings::BaseController < ApplicationController
def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end
+
+ def require_not_suspended!
+ forbidden if current_account.suspended?
+ end
end
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index 15a59c999d..f96c83b802 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -1,14 +1,11 @@
# frozen_string_literal: true
class Settings::DeletesController < Settings::BaseController
- layout 'admin'
-
- before_action :check_enabled_deletion
- before_action :authenticate_user!
- before_action :require_not_suspended!
-
skip_before_action :require_functional!
+ before_action :require_not_suspended!
+ before_action :check_enabled_deletion
+
def show
@confirmation = Form::DeleteConfirmation.new
end
@@ -46,7 +43,7 @@ class Settings::DeletesController < Settings::BaseController
def destroy_account!
current_account.suspend!
- Admin::SuspensionWorker.perform_async(current_user.account_id, true)
+ AccountDeletionWorker.perform_async(current_user.account_id)
sign_out
end
end
diff --git a/app/controllers/settings/exports/blocked_accounts_controller.rb b/app/controllers/settings/exports/blocked_accounts_controller.rb
index 2092104e01..2190caa361 100644
--- a/app/controllers/settings/exports/blocked_accounts_controller.rb
+++ b/app/controllers/settings/exports/blocked_accounts_controller.rb
@@ -2,7 +2,7 @@
module Settings
module Exports
- class BlockedAccountsController < ApplicationController
+ class BlockedAccountsController < BaseController
include ExportControllerConcern
def index
diff --git a/app/controllers/settings/exports/blocked_domains_controller.rb b/app/controllers/settings/exports/blocked_domains_controller.rb
index 6676ce3401..bee4b2431e 100644
--- a/app/controllers/settings/exports/blocked_domains_controller.rb
+++ b/app/controllers/settings/exports/blocked_domains_controller.rb
@@ -2,7 +2,7 @@
module Settings
module Exports
- class BlockedDomainsController < ApplicationController
+ class BlockedDomainsController < BaseController
include ExportControllerConcern
def index
diff --git a/app/controllers/settings/exports/following_accounts_controller.rb b/app/controllers/settings/exports/following_accounts_controller.rb
index 74281ddca2..acefcb15da 100644
--- a/app/controllers/settings/exports/following_accounts_controller.rb
+++ b/app/controllers/settings/exports/following_accounts_controller.rb
@@ -2,7 +2,7 @@
module Settings
module Exports
- class FollowingAccountsController < ApplicationController
+ class FollowingAccountsController < BaseController
include ExportControllerConcern
def index
diff --git a/app/controllers/settings/exports/lists_controller.rb b/app/controllers/settings/exports/lists_controller.rb
index cf5a9de44b..bc65f56a0e 100644
--- a/app/controllers/settings/exports/lists_controller.rb
+++ b/app/controllers/settings/exports/lists_controller.rb
@@ -2,7 +2,7 @@
module Settings
module Exports
- class ListsController < ApplicationController
+ class ListsController < BaseController
include ExportControllerConcern
def index
diff --git a/app/controllers/settings/exports/muted_accounts_controller.rb b/app/controllers/settings/exports/muted_accounts_controller.rb
index e511619ca6..50b7bf1f79 100644
--- a/app/controllers/settings/exports/muted_accounts_controller.rb
+++ b/app/controllers/settings/exports/muted_accounts_controller.rb
@@ -2,7 +2,7 @@
module Settings
module Exports
- class MutedAccountsController < ApplicationController
+ class MutedAccountsController < BaseController
include ExportControllerConcern
def index
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 0e93d07a9b..30138d29ed 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -3,11 +3,6 @@
class Settings::ExportsController < Settings::BaseController
include Authorization
- layout 'admin'
-
- before_action :authenticate_user!
- before_action :require_not_suspended!
-
skip_before_action :require_functional!
def show
@@ -16,8 +11,6 @@ class Settings::ExportsController < Settings::BaseController
end
def create
- raise Mastodon::NotPermittedError unless user_signed_in?
-
backup = nil
RedisLock.acquire(lock_options) do |lock|
@@ -37,8 +30,4 @@ class Settings::ExportsController < Settings::BaseController
def lock_options
{ redis: Redis.current, key: "backup:#{current_user.id}" }
end
-
- def require_not_suspended!
- forbidden if current_account.suspended?
- end
end
diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb
index e9861da56c..e805527d07 100644
--- a/app/controllers/settings/featured_tags_controller.rb
+++ b/app/controllers/settings/featured_tags_controller.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::FeaturedTagsController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
before_action :set_featured_tags, only: :index
before_action :set_featured_tag, except: [:index, :create]
before_action :set_recently_used_tags, only: :index
diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb
index b217b3c3be..4618c78836 100644
--- a/app/controllers/settings/identity_proofs_controller.rb
+++ b/app/controllers/settings/identity_proofs_controller.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::IdentityProofsController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
before_action :check_required_params, only: :new
before_action :check_enabled, only: :new
diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb
index 7b8c4ae235..d4516526ee 100644
--- a/app/controllers/settings/imports_controller.rb
+++ b/app/controllers/settings/imports_controller.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::ImportsController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
before_action :set_account
def show
diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb
index 97193ade02..6d469f3842 100644
--- a/app/controllers/settings/migration/redirects_controller.rb
+++ b/app/controllers/settings/migration/redirects_controller.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
class Settings::Migration::RedirectsController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
- before_action :require_not_suspended!
-
skip_before_action :require_functional!
+ before_action :require_not_suspended!
+
def new
@redirect = Form::Redirect.new
end
@@ -38,8 +35,4 @@ class Settings::Migration::RedirectsController < Settings::BaseController
def resource_params
params.require(:form_redirect).permit(:acct, :current_password, :current_username)
end
-
- def require_not_suspended!
- forbidden if current_account.suspended?
- end
end
diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb
index 68304bb513..62603aba81 100644
--- a/app/controllers/settings/migrations_controller.rb
+++ b/app/controllers/settings/migrations_controller.rb
@@ -1,15 +1,12 @@
# frozen_string_literal: true
class Settings::MigrationsController < Settings::BaseController
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
before_action :require_not_suspended!
before_action :set_migrations
before_action :set_cooldown
- skip_before_action :require_functional!
-
def show
@migration = current_account.migrations.build
end
@@ -44,8 +41,4 @@ class Settings::MigrationsController < Settings::BaseController
def on_cooldown?
@cooldown.present?
end
-
- def require_not_suspended!
- forbidden if current_account.suspended?
- end
end
diff --git a/app/controllers/settings/pictures_controller.rb b/app/controllers/settings/pictures_controller.rb
index df2a6eed3e..28df65f8ff 100644
--- a/app/controllers/settings/pictures_controller.rb
+++ b/app/controllers/settings/pictures_controller.rb
@@ -2,7 +2,6 @@
module Settings
class PicturesController < BaseController
- before_action :authenticate_user!
before_action :set_account
before_action :set_picture
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 75c3e2495d..87431f8cf2 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
class Settings::PreferencesController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
-
def show; end
def update
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 19a7ce157f..0c15447a6c 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
class Settings::ProfilesController < Settings::BaseController
- layout 'admin'
-
- before_action :authenticate_user!
before_action :set_account
def show
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index f8fb4036ef..ee2fc5dc80 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: true
-# Intentionally does not inherit from BaseController
-class Settings::SessionsController < ApplicationController
- before_action :authenticate_user!
- before_action :set_session, only: :destroy
-
+class Settings::SessionsController < Settings::BaseController
skip_before_action :require_functional!
+ before_action :require_not_suspended!
+ before_action :set_session, only: :destroy
+
def destroy
@session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success')
diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
index 9f23011a7d..1a0afe58b0 100644
--- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
@@ -5,14 +5,11 @@ module Settings
class ConfirmationsController < BaseController
include ChallengableConcern
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
before_action :require_challenge!
before_action :ensure_otp_secret
- skip_before_action :require_functional!
-
def new
prepare_two_factor_form
end
diff --git a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
index 6836f7ef62..cbba842a98 100644
--- a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
@@ -5,14 +5,11 @@ module Settings
class OtpAuthenticationController < BaseController
include ChallengableConcern
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
before_action :verify_otp_not_enabled, only: [:show]
before_action :require_challenge!, only: [:create]
- skip_before_action :require_functional!
-
def show
@confirmation = Form::TwoFactorConfirmation.new
end
diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
index 0c4f5bff76..6ec53224d3 100644
--- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
@@ -5,13 +5,10 @@ module Settings
class RecoveryCodesController < BaseController
include ChallengableConcern
- layout 'admin'
-
- before_action :authenticate_user!
- before_action :require_challenge!, on: :create
-
skip_before_action :require_functional!
+ before_action :require_challenge!, on: :create
+
def create
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!
diff --git a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb
index ee53927851..bd6f83134c 100644
--- a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb
@@ -3,9 +3,8 @@
module Settings
module TwoFactorAuthentication
class WebauthnCredentialsController < BaseController
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
before_action :require_otp_enabled
before_action :require_webauthn_enabled, only: [:index, :destroy]
diff --git a/app/controllers/settings/two_factor_authentication_methods_controller.rb b/app/controllers/settings/two_factor_authentication_methods_controller.rb
index 224d3a45ca..205933ea81 100644
--- a/app/controllers/settings/two_factor_authentication_methods_controller.rb
+++ b/app/controllers/settings/two_factor_authentication_methods_controller.rb
@@ -4,14 +4,11 @@ module Settings
class TwoFactorAuthenticationMethodsController < BaseController
include ChallengableConcern
- layout 'admin'
+ skip_before_action :require_functional!
- before_action :authenticate_user!
before_action :require_challenge!, only: :disable
before_action :require_otp_enabled
- skip_before_action :require_functional!
-
def index; end
def disable
diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js
index d28f7dad88..723c04e554 100644
--- a/app/javascript/mastodon/actions/accounts.js
+++ b/app/javascript/mastodon/actions/accounts.js
@@ -109,14 +109,14 @@ export function fetchAccountFail(id, error) {
};
};
-export function followAccount(id, reblogs = true) {
+export function followAccount(id, options = { reblogs: true }) {
return (dispatch, getState) => {
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
const locked = getState().getIn(['accounts', id, 'locked'], false);
dispatch(followAccountRequest(id, locked));
- api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => {
+ api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess(response.data, alreadyFollowing));
}).catch(error => {
dispatch(followAccountFail(error, locked));
diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js
index 37d1ddccfe..41a95503eb 100644
--- a/app/javascript/mastodon/actions/markers.js
+++ b/app/javascript/mastodon/actions/markers.js
@@ -3,6 +3,9 @@ import { debounce } from 'lodash';
import compareId from '../compare_id';
import { showAlertForError } from './alerts';
+export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
+export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
+export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL';
export const MARKERS_SUBMIT_SUCCESS = 'MARKERS_SUBMIT_SUCCESS';
export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
@@ -57,8 +60,8 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
const _buildParams = (state) => {
const params = {};
- const lastHomeId = state.getIn(['timelines', 'home', 'items', 0]);
- const lastNotificationId = state.getIn(['notifications', 'items', 0, 'id']);
+ const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null);
+ const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
params.home = {
@@ -100,3 +103,39 @@ export function submitMarkersSuccess({ home, notifications }) {
export function submitMarkers() {
return (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
};
+
+export const fetchMarkers = () => (dispatch, getState) => {
+ const params = { timeline: ['notifications'] };
+
+ dispatch(fetchMarkersRequest());
+
+ api(getState).get('/api/v1/markers', { params }).then(response => {
+ dispatch(fetchMarkersSuccess(response.data));
+ }).catch(error => {
+ dispatch(fetchMarkersFail(error));
+ });
+};
+
+export function fetchMarkersRequest() {
+ return {
+ type: MARKERS_FETCH_REQUEST,
+ skipLoading: true,
+ };
+};
+
+export function fetchMarkersSuccess(markers) {
+ return {
+ type: MARKERS_FETCH_SUCCESS,
+ markers,
+ skipLoading: true,
+ };
+};
+
+export function fetchMarkersFail(error) {
+ return {
+ type: MARKERS_FETCH_FAIL,
+ error,
+ skipLoading: true,
+ skipAlert: true,
+ };
+};
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index a26844f848..552def63b6 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -33,6 +33,8 @@ export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
+export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
+
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
@@ -59,7 +61,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
let filtered = false;
- if (notification.type === 'mention') {
+ if (['mention', 'status'].includes(notification.type)) {
const dropRegex = filters[0];
const regex = filters[1];
const searchIndex = searchTextFromRawStatus(notification.status);
diff --git a/app/javascript/mastodon/actions/picture_in_picture.js b/app/javascript/mastodon/actions/picture_in_picture.js
new file mode 100644
index 0000000000..4085cb59e0
--- /dev/null
+++ b/app/javascript/mastodon/actions/picture_in_picture.js
@@ -0,0 +1,38 @@
+// @ts-check
+
+export const PICTURE_IN_PICTURE_DEPLOY = 'PICTURE_IN_PICTURE_DEPLOY';
+export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
+
+/**
+ * @typedef MediaProps
+ * @property {string} src
+ * @property {boolean} muted
+ * @property {number} volume
+ * @property {number} currentTime
+ * @property {string} poster
+ * @property {string} backgroundColor
+ * @property {string} foregroundColor
+ * @property {string} accentColor
+ */
+
+/**
+ * @param {string} statusId
+ * @param {string} accountId
+ * @param {string} playerType
+ * @param {MediaProps} props
+ * @return {object}
+ */
+export const deployPictureInPicture = (statusId, accountId, playerType, props) => ({
+ type: PICTURE_IN_PICTURE_DEPLOY,
+ statusId,
+ accountId,
+ playerType,
+ props,
+});
+
+/*
+ * @return {object}
+ */
+export const removePictureInPicture = () => ({
+ type: PICTURE_IN_PICTURE_REMOVE,
+});
diff --git a/app/javascript/mastodon/components/animated_number.js b/app/javascript/mastodon/components/animated_number.js
index f3127c88ef..fbe948c5b0 100644
--- a/app/javascript/mastodon/components/animated_number.js
+++ b/app/javascript/mastodon/components/animated_number.js
@@ -5,10 +5,21 @@ import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
import { reduceMotion } from 'mastodon/initial_state';
+const obfuscatedCount = count => {
+ if (count < 0) {
+ return 0;
+ } else if (count <= 1) {
+ return count;
+ } else {
+ return '1+';
+ }
+};
+
export default class AnimatedNumber extends React.PureComponent {
static propTypes = {
value: PropTypes.number.isRequired,
+ obfuscate: PropTypes.bool,
};
state = {
@@ -36,11 +47,11 @@ export default class AnimatedNumber extends React.PureComponent {
}
render () {
- const { value } = this.props;
+ const { value, obfuscate } = this.props;
const { direction } = this.state;
if (reduceMotion) {
- return ;
+ return obfuscate ? obfuscatedCount(value) : ;
}
const styles = [{
@@ -54,7 +65,7 @@ export default class AnimatedNumber extends React.PureComponent {
{items => (
{items.map(({ key, data, style }) => (
- 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>
+ 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : }
))}
)}
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index ca3012276b..ca4a2cfe14 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -66,17 +66,31 @@ export default class ErrorBoundary extends React.PureComponent {
}
render() {
- const { hasError, copied } = this.state;
+ const { hasError, copied, errorMessage } = this.state;
if (!hasError) {
return this.props.children;
}
+ const likelyBrowserAddonIssue = errorMessage && errorMessage.includes('NotFoundError');
+
return (
-
-
+
+ { likelyBrowserAddonIssue ? (
+
+ ) : (
+
+ )}
+
+
+ { likelyBrowserAddonIssue ? (
+
+ ) : (
+
+ )}
+
Mastodon v{version} ยท ยท
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index fd715bc3c8..7f83dc1b91 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
+import AnimatedNumber from 'mastodon/components/animated_number';
export default class IconButton extends React.PureComponent {
@@ -24,6 +25,8 @@ export default class IconButton extends React.PureComponent {
animate: PropTypes.bool,
overlay: PropTypes.bool,
tabIndex: PropTypes.string,
+ counter: PropTypes.number,
+ obfuscateCount: PropTypes.bool,
};
static defaultProps = {
@@ -97,6 +100,8 @@ export default class IconButton extends React.PureComponent {
pressed,
tabIndex,
title,
+ counter,
+ obfuscateCount,
} = this.props;
const {
@@ -113,6 +118,10 @@ export default class IconButton extends React.PureComponent {
overlayed: overlay,
});
+ if (typeof counter !== 'undefined') {
+ style.width = 'auto';
+ }
+
return (
);
}
diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js
index 124b34b02f..2d87f19b53 100644
--- a/app/javascript/mastodon/components/intersection_observer_article.js
+++ b/app/javascript/mastodon/components/intersection_observer_article.js
@@ -2,10 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
-import { is } from 'immutable';
-// Diff these props in the "rendered" state
-const updateOnPropsForRendered = ['id', 'index', 'listLength'];
// Diff these props in the "unrendered" state
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
@@ -33,9 +30,12 @@ export default class IntersectionObserverArticle extends React.Component {
// If we're going from rendered to unrendered (or vice versa) then update
return true;
}
- // Otherwise, diff based on props
- const propsToDiff = isUnrendered ? updateOnPropsForUnrendered : updateOnPropsForRendered;
- return !propsToDiff.every(prop => is(nextProps[prop], this.props[prop]));
+ // If we are and remain hidden, diff based on props
+ if (isUnrendered) {
+ return !updateOnPropsForUnrendered.every(prop => nextProps[prop] === this.props[prop]);
+ }
+ // Else, assume the children have changed
+ return true;
}
componentDidMount () {
diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.js b/app/javascript/mastodon/components/picture_in_picture_placeholder.js
new file mode 100644
index 0000000000..19d15c18b1
--- /dev/null
+++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.js
@@ -0,0 +1,69 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Icon from 'mastodon/components/icon';
+import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
+import { connect } from 'react-redux';
+import { debounce } from 'lodash';
+import { FormattedMessage } from 'react-intl';
+
+export default @connect()
+class PictureInPicturePlaceholder extends React.PureComponent {
+
+ static propTypes = {
+ width: PropTypes.number,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ state = {
+ width: this.props.width,
+ height: this.props.width && (this.props.width / (16/9)),
+ };
+
+ handleClick = () => {
+ const { dispatch } = this.props;
+ dispatch(removePictureInPicture());
+ }
+
+ setRef = c => {
+ this.node = c;
+
+ if (this.node) {
+ this._setDimensions();
+ }
+ }
+
+ _setDimensions () {
+ const width = this.node.offsetWidth;
+ const height = width / (16/9);
+
+ this.setState({ width, height });
+ }
+
+ componentDidMount () {
+ window.addEventListener('resize', this.handleResize, { passive: true });
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('resize', this.handleResize);
+ }
+
+ handleResize = debounce(() => {
+ if (this.node) {
+ this._setDimensions();
+ }
+ }, 250, {
+ trailing: true,
+ });
+
+ render () {
+ const { height } = this.state;
+
+ return (
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 174e401b72..c1e1cd172a 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -17,6 +17,7 @@ import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state';
+import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
@@ -95,6 +96,8 @@ class Status extends ImmutablePureComponent {
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
scrollKey: PropTypes.string,
+ deployPictureInPicture: PropTypes.func,
+ usingPiP: PropTypes.bool,
};
// Avoid checking props that are functions (and whose equality will always
@@ -104,6 +107,8 @@ class Status extends ImmutablePureComponent {
'account',
'muted',
'hidden',
+ 'unread',
+ 'usingPiP',
];
state = {
@@ -205,6 +210,13 @@ class Status extends ImmutablePureComponent {
}
}
+ handleDeployPictureInPicture = (type, mediaProps) => {
+ const { deployPictureInPicture } = this.props;
+ const status = this._properStatus();
+
+ deployPictureInPicture(status, type, mediaProps);
+ }
+
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
@@ -265,7 +277,7 @@ class Status extends ImmutablePureComponent {
let media = null;
let statusAvatar, prepend, rebloggedByText;
- const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey } = this.props;
+ const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, usingPiP } = this.props;
let { status, account, ...other } = this.props;
@@ -336,7 +348,9 @@ class Status extends ImmutablePureComponent {
status = status.get('reblog');
}
- if (status.get('media_attachments').size > 0) {
+ if (usingPiP) {
+ media = ;
+ } else if (status.get('media_attachments').size > 0) {
if (this.props.muted) {
media = (
)}
@@ -382,6 +397,7 @@ class Status extends ImmutablePureComponent {
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
cacheWidth={this.props.cacheMediaWidth}
+ deployPictureInPicture={this.handleDeployPictureInPicture}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
@@ -438,10 +454,10 @@ class Status extends ImmutablePureComponent {
return (
-
+
{prepend}
-
+
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index b7babd4adc..66b5a17ac2 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -43,16 +43,6 @@ const messages = defineMessages({
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
});
-const obfuscatedCount = count => {
- if (count < 0) {
- return 0;
- } else if (count <= 1) {
- return count;
- } else {
- return '1+';
- }
-};
-
const mapStateToProps = (state, { status }) => ({
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
});
@@ -329,9 +319,10 @@ class StatusActionBar extends ImmutablePureComponent {
return (
-
{obfuscatedCount(status.get('replies_count'))}
+
+
{shareButton}
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index decf7279f9..7bfd66d3ea 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -37,6 +37,7 @@ import { initMuteModal } from '../actions/mutes';
import { initBlockModal } from '../actions/blocks';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
+import { deployPictureInPicture } from '../actions/picture_in_picture';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { boostModal, deleteModal } from '../initial_state';
import { showAlertForError } from '../actions/alerts';
@@ -56,6 +57,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
status: getStatus(state, props),
+ usingPiP: state.get('picture_in_picture').statusId === props.id,
});
return mapStateToProps;
@@ -207,6 +209,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(unblockDomain(domain));
},
+ deployPictureInPicture (status, type, mediaProps) {
+ dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
+ },
+
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 61ecf045d1..2b97af4e67 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me, isStaff } from 'mastodon/initial_state';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
+import IconButton from 'mastodon/components/icon_button';
import Avatar from 'mastodon/components/avatar';
import { counterRenderer } from 'mastodon/components/common_counter';
import ShortNumber from 'mastodon/components/short_number';
@@ -35,6 +36,8 @@ const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
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' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
@@ -68,8 +71,9 @@ class Header extends ImmutablePureComponent {
onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
- onReport: PropTypes.func.isRequired,
onReblogToggle: PropTypes.func.isRequired,
+ onNotifyToggle: PropTypes.func.isRequired,
+ onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired,
@@ -140,8 +144,11 @@ class Header extends ImmutablePureComponent {
return null;
}
+ const suspended = account.get('suspended');
+
let info = [];
let actionBtn = '';
+ let bellBtn = '';
let lockedIcon = '';
let menu = [];
@@ -171,6 +178,10 @@ class Header extends ImmutablePureComponent {
actionBtn =
;
}
+ if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
+ bellBtn =
;
+ }
+
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
actionBtn = '';
}
@@ -268,7 +279,7 @@ class Header extends ImmutablePureComponent {
- {info}
+ {!suspended && info}
@@ -282,11 +293,14 @@ class Header extends ImmutablePureComponent {
-
- {actionBtn}
+ {!suspended && (
+
+ {actionBtn}
+ {bellBtn}
-
-
+
+
+ )}
@@ -298,7 +312,7 @@ class Header extends ImmutablePureComponent {
- { (fields.size > 0 || identity_proofs.size > 0) && (
+ {(fields.size > 0 || identity_proofs.size > 0) && (
{identity_proofs.map((proof, i) => (
@@ -324,33 +338,35 @@ class Header extends ImmutablePureComponent {
)}
- {account.get('id') !== me &&
}
+ {account.get('id') !== me && !suspended &&
}
{account.get('note').length > 0 && account.get('note') !== '
' &&
}
-
-
-
-
+ {!suspended && (
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+ )}
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index fc5aead486..e5caec0bc2 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -15,12 +15,15 @@ import { ScrollContainer } from 'react-router-scroll-4';
import LoadMore from 'mastodon/components/load_more';
import MissingIndicator from 'mastodon/components/missing_indicator';
import { openModal } from 'mastodon/actions/modal';
+import { FormattedMessage } from 'react-intl';
const mapStateToProps = (state, props) => ({
isAccount: !!state.getIn(['accounts', props.params.accountId]),
attachments: getAccountGallery(state, props.params.accountId),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
+ suspended: state.getIn(['accounts', props.params.accountId, 'suspended'], false),
+ blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
});
class LoadMoreMedia extends ImmutablePureComponent {
@@ -56,6 +59,8 @@ class AccountGallery extends ImmutablePureComponent {
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
+ blockedBy: PropTypes.bool,
+ suspended: PropTypes.bool,
multiColumn: PropTypes.bool,
};
@@ -119,7 +124,7 @@ class AccountGallery extends ImmutablePureComponent {
}
render () {
- const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn } = this.props;
+ const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
const { width } = this.state;
if (!isAccount) {
@@ -152,15 +157,21 @@ class AccountGallery extends ImmutablePureComponent {
-
- {attachments.map((attachment, index) => attachment === null ? (
-
0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
- ) : (
-
- ))}
+ {(suspended || blockedBy) ? (
+
+
+
+ ) : (
+
+ {attachments.map((attachment, index) => attachment === null ? (
+ 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
+ ) : (
+
+ ))}
- {loadOlder}
-
+ {loadOlder}
+
+ )}
{isLoading && attachments.size === 0 && (
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index abb15edcc7..6b52defe4a 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -55,6 +55,10 @@ export default class Header extends ImmutablePureComponent {
this.props.onReblogToggle(this.props.account);
}
+ handleNotifyToggle = () => {
+ this.props.onNotifyToggle(this.props.account);
+ }
+
handleMute = () => {
this.props.onMute(this.props.account);
}
@@ -106,6 +110,7 @@ export default class Header extends ImmutablePureComponent {
onMention={this.handleMention}
onDirect={this.handleDirect}
onReblogToggle={this.handleReblogToggle}
+ onNotifyToggle={this.handleNotifyToggle}
onReport={this.handleReport}
onMute={this.handleMute}
onBlockDomain={this.handleBlockDomain}
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 8728b48068..e12019547e 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -76,9 +76,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onReblogToggle (account) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
- dispatch(followAccount(account.get('id'), false));
+ dispatch(followAccount(account.get('id'), { reblogs: false }));
} else {
- dispatch(followAccount(account.get('id'), true));
+ dispatch(followAccount(account.get('id'), { reblogs: true }));
}
},
@@ -90,6 +90,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
+ onNotifyToggle (account) {
+ if (account.getIn(['relationship', 'notifying'])) {
+ dispatch(followAccount(account.get('id'), { notify: false }));
+ } else {
+ dispatch(followAccount(account.get('id'), { notify: true }));
+ }
+ },
+
onReport (account) {
dispatch(initReport(account));
},
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index b9a616266b..cbc8598051 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -31,6 +31,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
+ suspended: state.getIn(['accounts', accountId, 'suspended'], false),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
};
};
@@ -57,6 +58,7 @@ class AccountTimeline extends ImmutablePureComponent {
withReplies: PropTypes.bool,
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
+ suspended: PropTypes.bool,
remote: PropTypes.bool,
remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool,
@@ -113,7 +115,7 @@ class AccountTimeline extends ImmutablePureComponent {
}
render () {
- const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn, remote, remoteUrl } = this.props;
+ const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl } = this.props;
if (!isAccount) {
return (
@@ -134,7 +136,7 @@ class AccountTimeline extends ImmutablePureComponent {
let emptyMessage;
- if (blockedBy) {
+ if (suspended || blockedBy) {
emptyMessage =
;
} else if (remote && statusIds.isEmpty()) {
emptyMessage =
;
@@ -153,7 +155,7 @@ class AccountTimeline extends ImmutablePureComponent {
alwaysPrepend
append={remoteMessage}
scrollKey='account_timeline'
- statusIds={blockedBy ? emptyList : statusIds}
+ statusIds={(suspended || blockedBy) ? emptyList : statusIds}
featuredStatusIds={featuredStatusIds}
isLoading={isLoading}
hasMore={hasMore}
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index 5b81726945..6954d2a4c1 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -37,7 +37,11 @@ class Audio extends React.PureComponent {
backgroundColor: PropTypes.string,
foregroundColor: PropTypes.string,
accentColor: PropTypes.string,
+ currentTime: PropTypes.number,
autoPlay: PropTypes.bool,
+ volume: PropTypes.number,
+ muted: PropTypes.bool,
+ deployPictureInPicture: PropTypes.func,
};
state = {
@@ -64,6 +68,19 @@ class Audio extends React.PureComponent {
}
}
+ _pack() {
+ return {
+ src: this.props.src,
+ volume: this.audio.volume,
+ muted: this.audio.muted,
+ currentTime: this.audio.currentTime,
+ poster: this.props.poster,
+ backgroundColor: this.props.backgroundColor,
+ foregroundColor: this.props.foregroundColor,
+ accentColor: this.props.accentColor,
+ };
+ }
+
_setDimensions () {
const width = this.player.offsetWidth;
const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16/9));
@@ -112,6 +129,10 @@ class Audio extends React.PureComponent {
componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
+
+ if (!this.state.paused && this.audio && this.props.deployPictureInPicture) {
+ this.props.deployPictureInPicture('audio', this._pack());
+ }
}
togglePlay = () => {
@@ -248,7 +269,13 @@ class Audio extends React.PureComponent {
const inView = (top <= (window.innerHeight || document.documentElement.clientHeight)) && (top + height >= 0);
if (!this.state.paused && !inView) {
- this.setState({ paused: true }, () => this.audio.pause());
+ this.audio.pause();
+
+ if (this.props.deployPictureInPicture) {
+ this.props.deployPictureInPicture('audio', this._pack());
+ }
+
+ this.setState({ paused: true });
}
}, 150, { trailing: true });
@@ -261,10 +288,22 @@ class Audio extends React.PureComponent {
}
handleLoadedData = () => {
- const { autoPlay } = this.props;
+ const { autoPlay, currentTime, volume, muted } = this.props;
+
+ if (currentTime) {
+ this.audio.currentTime = currentTime;
+ }
+
+ if (volume !== undefined) {
+ this.audio.volume = volume;
+ }
+
+ if (muted !== undefined) {
+ this.audio.muted = muted;
+ }
if (autoPlay) {
- this.audio.play();
+ this.togglePlay();
}
}
@@ -350,7 +389,7 @@ class Audio extends React.PureComponent {
render () {
const { src, intl, alt, editable, autoPlay } = this.props;
const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state;
- const progress = (currentTime / duration) * 100;
+ const progress = Math.min((currentTime / duration) * 100, 100);
return (
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index 5237b25f05..5d9dad0978 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -12,7 +12,7 @@ const emojiFilenames = (emojis) => {
};
// Emoji requiring extra borders depending on theme
-const darkEmoji = emojiFilenames(['๐ฑ', '๐', 'โซ', '๐ค', 'โฌ', 'โผ๏ธ', 'โพ', 'โผ๏ธ', 'โ๏ธ', 'โช๏ธ', '๐ฃ', '๐ณ', '๐ท', '๐ธ', 'โฃ๏ธ', '๐ถ๏ธ', 'โด๏ธ', '๐', '๐โโ๏ธ', '๐ฝ๏ธ', '๐ณ', '๐ฆ', '๐', '๐ช', '๐ณ๏ธ', '๐น๏ธ', '๐', '๐๏ธ', '๐๏ธ', '๐โโ๏ธ', '๐ค', '๐', '๐ฅ', '๐ผ', 'โ ๏ธ', '๐ฉ', '๐ฆ', '๐ผ', '๐น', '๐ฎ', '๐', '๐ด', '๐']);
+const darkEmoji = emojiFilenames(['๐ฑ', '๐', 'โซ', '๐ค', 'โฌ', 'โผ๏ธ', 'โพ', 'โผ๏ธ', 'โ๏ธ', 'โช๏ธ', '๐ฃ', '๐ณ', '๐ท', '๐ธ', 'โฃ๏ธ', '๐ถ๏ธ', 'โด๏ธ', '๐', '๐โโ๏ธ', '๐ฝ๏ธ', '๐ณ', '๐ฆ', '๐', '๐ช', '๐ณ๏ธ', '๐น๏ธ', '๐', '๐๏ธ', '๐๏ธ', '๐โโ๏ธ', '๐ค', '๐', '๐ฅ', '๐ผ', 'โ ๏ธ', '๐ฉ', '๐ฆ', '๐ผ', '๐น', '๐ฎ', '๐', '๐ด', '๐', '๐บ']);
const lightEmoji = emojiFilenames(['๐ฝ', 'โพ', '๐', 'โ๏ธ', '๐จ', '๐๏ธ', '๐', '๐ฅ', '๐ป', '๐', 'โ', 'โ', 'โธ๏ธ', '๐ฉ๏ธ', '๐', '๐', '๐', '๐ง๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', 'โ ๏ธ', '๐จ๏ธ', '๐', '๐', '๐ฌ', '๐ญ', '๐', '๐ณ๏ธ', 'โช', 'โฌ', 'โฝ', 'โป๏ธ', 'โซ๏ธ']);
const emojiFilename = (filename) => {
diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.js
index 2fd28d8326..368eb0b7e6 100644
--- a/app/javascript/mastodon/features/notifications/components/filter_bar.js
+++ b/app/javascript/mastodon/features/notifications/components/filter_bar.js
@@ -9,6 +9,7 @@ const tooltips = defineMessages({
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
+ statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
});
export default @injectIntl
@@ -87,6 +88,13 @@ class FilterBar extends React.PureComponent {
>
+