Merge pull request #1662 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
		
						commit
						b209e919bd
					
				
							
								
								
									
										10
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Gemfile
									
									
									
									
									
								
							| @ -9,7 +9,7 @@ gem 'rexml', '~> 3.2' | |||||||
| gem 'puma', '~> 5.5' | gem 'puma', '~> 5.5' | ||||||
| gem 'rails', '~> 6.1.4' | gem 'rails', '~> 6.1.4' | ||||||
| gem 'sprockets', '~> 3.7.2' | gem 'sprockets', '~> 3.7.2' | ||||||
| gem 'thor', '~> 1.1' | gem 'thor', '~> 1.2' | ||||||
| gem 'rack', '~> 2.2.3' | gem 'rack', '~> 2.2.3' | ||||||
| 
 | 
 | ||||||
| gem 'hamlit-rails', '~> 0.2' | gem 'hamlit-rails', '~> 0.2' | ||||||
| @ -18,7 +18,7 @@ gem 'makara', '~> 0.5' | |||||||
| gem 'pghero', '~> 2.8' | gem 'pghero', '~> 2.8' | ||||||
| gem 'dotenv-rails', '~> 2.7' | gem 'dotenv-rails', '~> 2.7' | ||||||
| 
 | 
 | ||||||
| gem 'aws-sdk-s3', '~> 1.109', require: false | gem 'aws-sdk-s3', '~> 1.111', require: false | ||||||
| gem 'fog-core', '<= 2.1.0' | gem 'fog-core', '<= 2.1.0' | ||||||
| gem 'fog-openstack', '~> 0.3', require: false | gem 'fog-openstack', '~> 0.3', require: false | ||||||
| gem 'kt-paperclip', '~> 7.0' | gem 'kt-paperclip', '~> 7.0' | ||||||
| @ -26,7 +26,7 @@ gem 'blurhash', '~> 0.1' | |||||||
| 
 | 
 | ||||||
| gem 'active_model_serializers', '~> 0.10' | gem 'active_model_serializers', '~> 0.10' | ||||||
| gem 'addressable', '~> 2.8' | gem 'addressable', '~> 2.8' | ||||||
| gem 'bootsnap', '~> 1.9.2', require: false | gem 'bootsnap', '~> 1.10.1', require: false | ||||||
| gem 'browser' | gem 'browser' | ||||||
| gem 'charlock_holmes', '~> 0.7.7' | gem 'charlock_holmes', '~> 0.7.7' | ||||||
| gem 'iso-639' | gem 'iso-639' | ||||||
| @ -48,7 +48,7 @@ gem 'omniauth-rails_csrf_protection', '~> 0.1' | |||||||
| gem 'color_diff', '~> 0.1' | gem 'color_diff', '~> 0.1' | ||||||
| gem 'discard', '~> 1.2' | gem 'discard', '~> 1.2' | ||||||
| gem 'doorkeeper', '~> 5.5' | gem 'doorkeeper', '~> 5.5' | ||||||
| gem 'ed25519', '~> 1.2' | gem 'ed25519', '~> 1.3' | ||||||
| gem 'fast_blank', '~> 1.0' | gem 'fast_blank', '~> 1.0' | ||||||
| gem 'fastimage' | gem 'fastimage' | ||||||
| gem 'hiredis', '~> 0.6' | gem 'hiredis', '~> 0.6' | ||||||
| @ -61,7 +61,7 @@ gem 'idn-ruby', require: 'idn' | |||||||
| gem 'kaminari', '~> 1.2' | gem 'kaminari', '~> 1.2' | ||||||
| gem 'link_header', '~> 0.0' | gem 'link_header', '~> 0.0' | ||||||
| gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar' | gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar' | ||||||
| gem 'nokogiri', '~> 1.12' | gem 'nokogiri', '~> 1.13' | ||||||
| gem 'nsa', '~> 0.2' | gem 'nsa', '~> 0.2' | ||||||
| gem 'oj', '~> 3.13' | gem 'oj', '~> 3.13' | ||||||
| gem 'ox', '~> 2.14' | gem 'ox', '~> 2.14' | ||||||
|  | |||||||
							
								
								
									
										73
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								Gemfile.lock
									
									
									
									
									
								
							| @ -39,9 +39,9 @@ GEM | |||||||
|       erubi (~> 1.4) |       erubi (~> 1.4) | ||||||
|       rails-dom-testing (~> 2.0) |       rails-dom-testing (~> 2.0) | ||||||
|       rails-html-sanitizer (~> 1.1, >= 1.2.0) |       rails-html-sanitizer (~> 1.1, >= 1.2.0) | ||||||
|     active_model_serializers (0.10.12) |     active_model_serializers (0.10.13) | ||||||
|       actionpack (>= 4.1, < 6.2) |       actionpack (>= 4.1, < 7.1) | ||||||
|       activemodel (>= 4.1, < 6.2) |       activemodel (>= 4.1, < 7.1) | ||||||
|       case_transform (>= 0.2) |       case_transform (>= 0.2) | ||||||
|       jsonapi-renderer (>= 0.1.1.beta1, < 0.3) |       jsonapi-renderer (>= 0.1.1.beta1, < 0.3) | ||||||
|     active_record_query_trace (1.8) |     active_record_query_trace (1.8) | ||||||
| @ -79,17 +79,17 @@ GEM | |||||||
|       encryptor (~> 3.0.0) |       encryptor (~> 3.0.0) | ||||||
|     awrence (1.1.1) |     awrence (1.1.1) | ||||||
|     aws-eventstream (1.2.0) |     aws-eventstream (1.2.0) | ||||||
|     aws-partitions (1.539.0) |     aws-partitions (1.547.0) | ||||||
|     aws-sdk-core (3.124.0) |     aws-sdk-core (3.125.1) | ||||||
|       aws-eventstream (~> 1, >= 1.0.2) |       aws-eventstream (~> 1, >= 1.0.2) | ||||||
|       aws-partitions (~> 1, >= 1.525.0) |       aws-partitions (~> 1, >= 1.525.0) | ||||||
|       aws-sigv4 (~> 1.1) |       aws-sigv4 (~> 1.1) | ||||||
|       jmespath (~> 1.0) |       jmespath (~> 1.0) | ||||||
|     aws-sdk-kms (1.52.0) |     aws-sdk-kms (1.53.0) | ||||||
|       aws-sdk-core (~> 3, >= 3.122.0) |       aws-sdk-core (~> 3, >= 3.125.0) | ||||||
|       aws-sigv4 (~> 1.1) |       aws-sigv4 (~> 1.1) | ||||||
|     aws-sdk-s3 (1.109.0) |     aws-sdk-s3 (1.111.1) | ||||||
|       aws-sdk-core (~> 3, >= 3.122.0) |       aws-sdk-core (~> 3, >= 3.125.0) | ||||||
|       aws-sdk-kms (~> 1) |       aws-sdk-kms (~> 1) | ||||||
|       aws-sigv4 (~> 1.4) |       aws-sigv4 (~> 1.4) | ||||||
|     aws-sigv4 (1.4.0) |     aws-sigv4 (1.4.0) | ||||||
| @ -104,15 +104,15 @@ GEM | |||||||
|       debug_inspector (>= 0.0.1) |       debug_inspector (>= 0.0.1) | ||||||
|     blurhash (0.1.5) |     blurhash (0.1.5) | ||||||
|       ffi (~> 1.14) |       ffi (~> 1.14) | ||||||
|     bootsnap (1.9.3) |     bootsnap (1.10.1) | ||||||
|       msgpack (~> 1.0) |       msgpack (~> 1.2) | ||||||
|     brakeman (5.2.0) |     brakeman (5.2.0) | ||||||
|     browser (4.2.0) |     browser (4.2.0) | ||||||
|     brpoplpush-redis_script (0.1.2) |     brpoplpush-redis_script (0.1.2) | ||||||
|       concurrent-ruby (~> 1.0, >= 1.0.5) |       concurrent-ruby (~> 1.0, >= 1.0.5) | ||||||
|       redis (>= 1.0, <= 5.0) |       redis (>= 1.0, <= 5.0) | ||||||
|     builder (3.2.4) |     builder (3.2.4) | ||||||
|     bullet (7.0.0) |     bullet (7.0.1) | ||||||
|       activesupport (>= 3.0.0) |       activesupport (>= 3.0.0) | ||||||
|       uniform_notifier (~> 1.11) |       uniform_notifier (~> 1.11) | ||||||
|     bundler-audit (0.9.0.1) |     bundler-audit (0.9.0.1) | ||||||
| @ -196,7 +196,7 @@ GEM | |||||||
|       dotenv (= 2.7.6) |       dotenv (= 2.7.6) | ||||||
|       railties (>= 3.2) |       railties (>= 3.2) | ||||||
|     e2mmap (0.1.0) |     e2mmap (0.1.0) | ||||||
|     ed25519 (1.2.4) |     ed25519 (1.3.0) | ||||||
|     elasticsearch (7.13.3) |     elasticsearch (7.13.3) | ||||||
|       elasticsearch-api (= 7.13.3) |       elasticsearch-api (= 7.13.3) | ||||||
|       elasticsearch-transport (= 7.13.3) |       elasticsearch-transport (= 7.13.3) | ||||||
| @ -269,8 +269,6 @@ GEM | |||||||
|       activesupport (>= 4.0.1) |       activesupport (>= 4.0.1) | ||||||
|       hamlit (>= 1.2.0) |       hamlit (>= 1.2.0) | ||||||
|       railties (>= 4.0.1) |       railties (>= 4.0.1) | ||||||
|     hamster (3.0.0) |  | ||||||
|       concurrent-ruby (~> 1.0) |  | ||||||
|     hashdiff (1.0.1) |     hashdiff (1.0.1) | ||||||
|     hashie (4.1.0) |     hashie (4.1.0) | ||||||
|     highline (2.0.3) |     highline (2.0.3) | ||||||
| @ -304,16 +302,16 @@ GEM | |||||||
|     idn-ruby (0.1.4) |     idn-ruby (0.1.4) | ||||||
|     ipaddress (0.8.3) |     ipaddress (0.8.3) | ||||||
|     iso-639 (0.3.5) |     iso-639 (0.3.5) | ||||||
|     jmespath (1.4.0) |     jmespath (1.5.0) | ||||||
|     json (2.5.1) |     json (2.5.1) | ||||||
|     json-canonicalization (0.2.1) |     json-canonicalization (0.3.0) | ||||||
|     json-ld (3.1.10) |     json-ld (3.2.0) | ||||||
|       htmlentities (~> 4.3) |       htmlentities (~> 4.3) | ||||||
|       json-canonicalization (~> 0.2) |       json-canonicalization (~> 0.3) | ||||||
|       link_header (~> 0.0, >= 0.0.8) |       link_header (~> 0.0, >= 0.0.8) | ||||||
|       multi_json (~> 1.14) |       multi_json (~> 1.15) | ||||||
|       rack (~> 2.0) |       rack (~> 2.2) | ||||||
|       rdf (~> 3.1) |       rdf (~> 3.2) | ||||||
|     json-ld-preloaded (3.1.6) |     json-ld-preloaded (3.1.6) | ||||||
|       json-ld (~> 3.1) |       json-ld (~> 3.1) | ||||||
|       rdf (~> 3.1) |       rdf (~> 3.1) | ||||||
| @ -375,7 +373,7 @@ GEM | |||||||
|       mime-types-data (~> 3.2015) |       mime-types-data (~> 3.2015) | ||||||
|     mime-types-data (3.2021.1115) |     mime-types-data (3.2021.1115) | ||||||
|     mini_mime (1.1.2) |     mini_mime (1.1.2) | ||||||
|     mini_portile2 (2.6.1) |     mini_portile2 (2.7.1) | ||||||
|     minitest (5.15.0) |     minitest (5.15.0) | ||||||
|     msgpack (1.4.2) |     msgpack (1.4.2) | ||||||
|     multi_json (1.15.0) |     multi_json (1.15.0) | ||||||
| @ -385,8 +383,8 @@ GEM | |||||||
|       net-ssh (>= 2.6.5, < 7.0.0) |       net-ssh (>= 2.6.5, < 7.0.0) | ||||||
|     net-ssh (6.1.0) |     net-ssh (6.1.0) | ||||||
|     nio4r (2.5.8) |     nio4r (2.5.8) | ||||||
|     nokogiri (1.12.5) |     nokogiri (1.13.1) | ||||||
|       mini_portile2 (~> 2.6.1) |       mini_portile2 (~> 2.7.0) | ||||||
|       racc (~> 1.4) |       racc (~> 1.4) | ||||||
|     nsa (0.2.8) |     nsa (0.2.8) | ||||||
|       activesupport (>= 4.2, < 7) |       activesupport (>= 4.2, < 7) | ||||||
| @ -420,7 +418,7 @@ GEM | |||||||
|     pg (1.2.3) |     pg (1.2.3) | ||||||
|     pghero (2.8.2) |     pghero (2.8.2) | ||||||
|       activerecord (>= 5) |       activerecord (>= 5) | ||||||
|     pkg-config (1.4.6) |     pkg-config (1.4.7) | ||||||
|     posix-spawn (0.3.15) |     posix-spawn (0.3.15) | ||||||
|     premailer (1.14.2) |     premailer (1.14.2) | ||||||
|       addressable |       addressable | ||||||
| @ -489,10 +487,9 @@ GEM | |||||||
|       method_source |       method_source | ||||||
|       rake (>= 0.13) |       rake (>= 0.13) | ||||||
|       thor (~> 1.0) |       thor (~> 1.0) | ||||||
|     rainbow (3.0.0) |     rainbow (3.1.1) | ||||||
|     rake (13.0.6) |     rake (13.0.6) | ||||||
|     rdf (3.1.15) |     rdf (3.2.3) | ||||||
|       hamster (~> 3.0) |  | ||||||
|       link_header (~> 0.0, >= 0.0.8) |       link_header (~> 0.0, >= 0.0.8) | ||||||
|     rdf-normalize (0.4.0) |     rdf-normalize (0.4.0) | ||||||
|       rdf (~> 3.1) |       rdf (~> 3.1) | ||||||
| @ -533,7 +530,7 @@ GEM | |||||||
|       rspec-core (~> 3.0, >= 3.0.0) |       rspec-core (~> 3.0, >= 3.0.0) | ||||||
|       sidekiq (>= 2.4.0) |       sidekiq (>= 2.4.0) | ||||||
|     rspec-support (3.10.3) |     rspec-support (3.10.3) | ||||||
|     rspec_junit_formatter (0.5.0) |     rspec_junit_formatter (0.5.1) | ||||||
|       rspec-core (>= 2, < 4, != 2.12.0) |       rspec-core (>= 2, < 4, != 2.12.0) | ||||||
|     rubocop (1.24.1) |     rubocop (1.24.1) | ||||||
|       parallel (~> 1.10) |       parallel (~> 1.10) | ||||||
| @ -546,7 +543,7 @@ GEM | |||||||
|       unicode-display_width (>= 1.4.0, < 3.0) |       unicode-display_width (>= 1.4.0, < 3.0) | ||||||
|     rubocop-ast (1.15.1) |     rubocop-ast (1.15.1) | ||||||
|       parser (>= 3.0.1.1) |       parser (>= 3.0.1.1) | ||||||
|     rubocop-rails (2.13.0) |     rubocop-rails (2.13.2) | ||||||
|       activesupport (>= 4.2.0) |       activesupport (>= 4.2.0) | ||||||
|       rack (>= 1.1) |       rack (>= 1.1) | ||||||
|       rubocop (>= 1.7.0, < 2.0) |       rubocop (>= 1.7.0, < 2.0) | ||||||
| @ -562,7 +559,7 @@ GEM | |||||||
|     sanitize (6.0.0) |     sanitize (6.0.0) | ||||||
|       crass (~> 1.0.2) |       crass (~> 1.0.2) | ||||||
|       nokogiri (>= 1.12.0) |       nokogiri (>= 1.12.0) | ||||||
|     scenic (1.5.4) |     scenic (1.5.5) | ||||||
|       activerecord (>= 4.0.0) |       activerecord (>= 4.0.0) | ||||||
|       railties (>= 4.0.0) |       railties (>= 4.0.0) | ||||||
|     securecompare (1.0.0) |     securecompare (1.0.0) | ||||||
| @ -616,7 +613,7 @@ GEM | |||||||
|       unicode-display_width (>= 1.1.1, < 3) |       unicode-display_width (>= 1.1.1, < 3) | ||||||
|     terrapin (0.6.0) |     terrapin (0.6.0) | ||||||
|       climate_control (>= 0.0.3, < 1.0) |       climate_control (>= 0.0.3, < 1.0) | ||||||
|     thor (1.1.0) |     thor (1.2.1) | ||||||
|     thwait (0.2.0) |     thwait (0.2.0) | ||||||
|       e2mmap |       e2mmap | ||||||
|     tilt (2.0.10) |     tilt (2.0.10) | ||||||
| @ -686,11 +683,11 @@ DEPENDENCIES | |||||||
|   active_record_query_trace (~> 1.8) |   active_record_query_trace (~> 1.8) | ||||||
|   addressable (~> 2.8) |   addressable (~> 2.8) | ||||||
|   annotate (~> 3.1) |   annotate (~> 3.1) | ||||||
|   aws-sdk-s3 (~> 1.109) |   aws-sdk-s3 (~> 1.111) | ||||||
|   better_errors (~> 2.9) |   better_errors (~> 2.9) | ||||||
|   binding_of_caller (~> 1.0) |   binding_of_caller (~> 1.0) | ||||||
|   blurhash (~> 0.1) |   blurhash (~> 0.1) | ||||||
|   bootsnap (~> 1.9.2) |   bootsnap (~> 1.10.1) | ||||||
|   brakeman (~> 5.2) |   brakeman (~> 5.2) | ||||||
|   browser |   browser | ||||||
|   bullet (~> 7.0) |   bullet (~> 7.0) | ||||||
| @ -713,7 +710,7 @@ DEPENDENCIES | |||||||
|   discard (~> 1.2) |   discard (~> 1.2) | ||||||
|   doorkeeper (~> 5.5) |   doorkeeper (~> 5.5) | ||||||
|   dotenv-rails (~> 2.7) |   dotenv-rails (~> 2.7) | ||||||
|   ed25519 (~> 1.2) |   ed25519 (~> 1.3) | ||||||
|   fabrication (~> 2.23) |   fabrication (~> 2.23) | ||||||
|   faker (~> 2.19) |   faker (~> 2.19) | ||||||
|   fast_blank (~> 1.0) |   fast_blank (~> 1.0) | ||||||
| @ -744,7 +741,7 @@ DEPENDENCIES | |||||||
|   microformats (~> 4.2) |   microformats (~> 4.2) | ||||||
|   mime-types (~> 3.4.1) |   mime-types (~> 3.4.1) | ||||||
|   net-ldap (~> 0.17) |   net-ldap (~> 0.17) | ||||||
|   nokogiri (~> 1.12) |   nokogiri (~> 1.13) | ||||||
|   nsa (~> 0.2) |   nsa (~> 0.2) | ||||||
|   oj (~> 3.13) |   oj (~> 3.13) | ||||||
|   omniauth (~> 1.9) |   omniauth (~> 1.9) | ||||||
| @ -796,7 +793,7 @@ DEPENDENCIES | |||||||
|   stackprof |   stackprof | ||||||
|   stoplight (~> 2.2.1) |   stoplight (~> 2.2.1) | ||||||
|   strong_migrations (~> 0.7) |   strong_migrations (~> 0.7) | ||||||
|   thor (~> 1.1) |   thor (~> 1.2) | ||||||
|   tty-prompt (~> 0.23) |   tty-prompt (~> 0.23) | ||||||
|   twitter-text (~> 3.1.0) |   twitter-text (~> 3.1.0) | ||||||
|   tzinfo-data (~> 1.2021) |   tzinfo-data (~> 1.2021) | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ module Admin | |||||||
|       else |       else | ||||||
|         @account          = @account_moderation_note.target_account |         @account          = @account_moderation_note.target_account | ||||||
|         @moderation_notes = @account.targeted_moderation_notes.latest |         @moderation_notes = @account.targeted_moderation_notes.latest | ||||||
|         @warnings         = @account.targeted_account_warnings.latest.custom |         @warnings         = @account.strikes.custom.latest | ||||||
| 
 | 
 | ||||||
|         render template: 'admin/accounts/show' |         render template: 'admin/accounts/show' | ||||||
|       end |       end | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ module Admin | |||||||
|       @deletion_request        = @account.deletion_request |       @deletion_request        = @account.deletion_request | ||||||
|       @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) |       @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) | ||||||
|       @moderation_notes        = @account.targeted_moderation_notes.latest |       @moderation_notes        = @account.targeted_moderation_notes.latest | ||||||
|       @warnings                = @account.targeted_account_warnings.latest.custom |       @warnings                = @account.strikes.custom.latest | ||||||
|       @domain_block            = DomainBlock.rule_for(@account.domain) |       @domain_block            = DomainBlock.rule_for(@account.domain) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -14,20 +14,17 @@ module Admin | |||||||
|         if params[:create_and_resolve] |         if params[:create_and_resolve] | ||||||
|           @report.resolve!(current_account) |           @report.resolve!(current_account) | ||||||
|           log_action :resolve, @report |           log_action :resolve, @report | ||||||
| 
 |         elsif params[:create_and_unresolve] | ||||||
|           redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') |  | ||||||
|           return |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         if params[:create_and_unresolve] |  | ||||||
|           @report.unresolve! |           @report.unresolve! | ||||||
|           log_action :reopen, @report |           log_action :reopen, @report | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg') |         redirect_to after_create_redirect_path, notice: I18n.t('admin.report_notes.created_msg') | ||||||
|       else |       else | ||||||
|         @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at) |         @report_notes = @report.notes.includes(:account).order(id: :desc) | ||||||
|         @form         = Form::StatusBatch.new |         @action_logs  = @report.history.includes(:target) | ||||||
|  |         @form         = Admin::StatusBatchAction.new | ||||||
|  |         @statuses     = @report.statuses.with_includes | ||||||
| 
 | 
 | ||||||
|         render template: 'admin/reports/show' |         render template: 'admin/reports/show' | ||||||
|       end |       end | ||||||
| @ -41,6 +38,14 @@ module Admin | |||||||
| 
 | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|  |     def after_create_redirect_path | ||||||
|  |       if params[:create_and_resolve] | ||||||
|  |         admin_reports_path | ||||||
|  |       else | ||||||
|  |         admin_report_path(@report) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def resource_params |     def resource_params | ||||||
|       params.require(:report_note).permit( |       params.require(:report_note).permit( | ||||||
|         :content, |         :content, | ||||||
|  | |||||||
| @ -1,44 +0,0 @@ | |||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module Admin |  | ||||||
|   class ReportedStatusesController < BaseController |  | ||||||
|     before_action :set_report |  | ||||||
| 
 |  | ||||||
|     def create |  | ||||||
|       authorize :status, :update? |  | ||||||
| 
 |  | ||||||
|       @form         = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) |  | ||||||
|       flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save |  | ||||||
| 
 |  | ||||||
|       redirect_to admin_report_path(@report) |  | ||||||
|     rescue ActionController::ParameterMissing |  | ||||||
|       flash[:alert] = I18n.t('admin.statuses.no_status_selected') |  | ||||||
| 
 |  | ||||||
|       redirect_to admin_report_path(@report) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     private |  | ||||||
| 
 |  | ||||||
|     def status_params |  | ||||||
|       params.require(:status).permit(:sensitive) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def form_status_batch_params |  | ||||||
|       params.require(:form_status_batch).permit(status_ids: []) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def action_from_button |  | ||||||
|       if params[:nsfw_on] |  | ||||||
|         'nsfw_on' |  | ||||||
|       elsif params[:nsfw_off] |  | ||||||
|         'nsfw_off' |  | ||||||
|       elsif params[:delete] |  | ||||||
|         'delete' |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def set_report |  | ||||||
|       @report = Report.find(params[:report_id]) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @ -13,8 +13,10 @@ module Admin | |||||||
|       authorize @report, :show? |       authorize @report, :show? | ||||||
| 
 | 
 | ||||||
|       @report_note  = @report.notes.new |       @report_note  = @report.notes.new | ||||||
|       @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at) |       @report_notes = @report.notes.includes(:account).order(id: :desc) | ||||||
|       @form         = Form::StatusBatch.new |       @action_logs  = @report.history.includes(:target) | ||||||
|  |       @form         = Admin::StatusBatchAction.new | ||||||
|  |       @statuses     = @report.statuses.with_includes | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def assign_to_self |     def assign_to_self | ||||||
|  | |||||||
| @ -2,71 +2,57 @@ | |||||||
| 
 | 
 | ||||||
| module Admin | module Admin | ||||||
|   class StatusesController < BaseController |   class StatusesController < BaseController | ||||||
|     helper_method :current_params |  | ||||||
| 
 |  | ||||||
|     before_action :set_account |     before_action :set_account | ||||||
|  |     before_action :set_statuses | ||||||
| 
 | 
 | ||||||
|     PER_PAGE = 20 |     PER_PAGE = 20 | ||||||
| 
 | 
 | ||||||
|     def index |     def index | ||||||
|       authorize :status, :index? |       authorize :status, :index? | ||||||
| 
 | 
 | ||||||
|       @statuses = @account.statuses.where(visibility: [:public, :unlisted]) |       @status_batch_action = Admin::StatusBatchAction.new | ||||||
| 
 |  | ||||||
|       if params[:media] |  | ||||||
|         @statuses = @statuses.merge(Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)).reorder('statuses.id desc') |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) |  | ||||||
|       @form     = Form::StatusBatch.new |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def show |     def batch | ||||||
|       authorize :status, :index? |       @status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button)) | ||||||
| 
 |       @status_batch_action.save! | ||||||
|       @statuses = @account.statuses.where(id: params[:id]) |  | ||||||
|       authorize @statuses.first, :show? |  | ||||||
| 
 |  | ||||||
|       @form = Form::StatusBatch.new |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def create |  | ||||||
|       authorize :status, :update? |  | ||||||
| 
 |  | ||||||
|       @form         = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) |  | ||||||
|       flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save |  | ||||||
| 
 |  | ||||||
|       redirect_to admin_account_statuses_path(@account.id, current_params) |  | ||||||
|     rescue ActionController::ParameterMissing |     rescue ActionController::ParameterMissing | ||||||
|       flash[:alert] = I18n.t('admin.statuses.no_status_selected') |       flash[:alert] = I18n.t('admin.statuses.no_status_selected') | ||||||
| 
 |     ensure | ||||||
|       redirect_to admin_account_statuses_path(@account.id, current_params) |       redirect_to after_create_redirect_path | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|     def form_status_batch_params |     def admin_status_batch_action_params | ||||||
|       params.require(:form_status_batch).permit(:action, status_ids: []) |       params.require(:admin_status_batch_action).permit(status_ids: []) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def after_create_redirect_path | ||||||
|  |       if @status_batch_action.report_id.present? | ||||||
|  |         admin_report_path(@status_batch_action.report_id) | ||||||
|  |       else | ||||||
|  |         admin_account_statuses_path(params[:account_id], current_params) | ||||||
|  |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def set_account |     def set_account | ||||||
|       @account = Account.find(params[:account_id]) |       @account = Account.find(params[:account_id]) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def current_params |     def set_statuses | ||||||
|       page = (params[:page] || 1).to_i |       @statuses = Admin::StatusFilter.new(@account, filter_params).results.preload(:application, :preloadable_poll, :media_attachments, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, active_mentions: :account]).page(params[:page]).per(PER_PAGE) | ||||||
|  |     end | ||||||
| 
 | 
 | ||||||
|       { |     def filter_params | ||||||
|         media: params[:media], |       params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS) | ||||||
|         page: page > 1 && page, |  | ||||||
|       }.select { |_, value| value.present? } |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def action_from_button |     def action_from_button | ||||||
|       if params[:nsfw_on] |       if params[:report] | ||||||
|         'nsfw_on' |         'report' | ||||||
|       elsif params[:nsfw_off] |       elsif params[:remove_from_report] | ||||||
|         'nsfw_off' |         'remove_from_report' | ||||||
|       elsif params[:delete] |       elsif params[:delete] | ||||||
|         'delete' |         'delete' | ||||||
|       end |       end | ||||||
|  | |||||||
| @ -1,7 +1,9 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::V1::Admin::AccountActionsController < Api::BaseController | class Api::V1::Admin::AccountActionsController < Api::BaseController | ||||||
|   before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' } |   protect_from_forgery with: :exception | ||||||
|  | 
 | ||||||
|  |   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' } | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_account |   before_action :set_account | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,15 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::V1::Admin::AccountsController < Api::BaseController | class Api::V1::Admin::AccountsController < Api::BaseController | ||||||
|  |   protect_from_forgery with: :exception | ||||||
|  | 
 | ||||||
|   include Authorization |   include Authorization | ||||||
|   include AccountableConcern |   include AccountableConcern | ||||||
| 
 | 
 | ||||||
|   LIMIT = 100 |   LIMIT = 100 | ||||||
| 
 | 
 | ||||||
|   before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show] |   before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:accounts' }, only: [:index, :show] | ||||||
|   before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show] |   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show] | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_accounts, only: :index |   before_action :set_accounts, only: :index | ||||||
|   before_action :set_account, except: :index |   before_action :set_account, except: :index | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| class Api::V1::Admin::DimensionsController < Api::BaseController | class Api::V1::Admin::DimensionsController < Api::BaseController | ||||||
|   protect_from_forgery with: :exception |   protect_from_forgery with: :exception | ||||||
| 
 | 
 | ||||||
|  |   before_action -> { authorize_if_got_token! :'admin:read' } | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_dimensions |   before_action :set_dimensions | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| class Api::V1::Admin::MeasuresController < Api::BaseController | class Api::V1::Admin::MeasuresController < Api::BaseController | ||||||
|   protect_from_forgery with: :exception |   protect_from_forgery with: :exception | ||||||
| 
 | 
 | ||||||
|  |   before_action -> { authorize_if_got_token! :'admin:read' } | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_measures |   before_action :set_measures | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,15 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::V1::Admin::ReportsController < Api::BaseController | class Api::V1::Admin::ReportsController < Api::BaseController | ||||||
|  |   protect_from_forgery with: :exception | ||||||
|  | 
 | ||||||
|   include Authorization |   include Authorization | ||||||
|   include AccountableConcern |   include AccountableConcern | ||||||
| 
 | 
 | ||||||
|   LIMIT = 100 |   LIMIT = 100 | ||||||
| 
 | 
 | ||||||
|   before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show] |   before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:reports' }, only: [:index, :show] | ||||||
|   before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show] |   before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show] | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_reports, only: :index |   before_action :set_reports, only: :index | ||||||
|   before_action :set_report, except: :index |   before_action :set_report, except: :index | ||||||
| @ -32,6 +34,12 @@ class Api::V1::Admin::ReportsController < Api::BaseController | |||||||
|     render json: @report, serializer: REST::Admin::ReportSerializer |     render json: @report, serializer: REST::Admin::ReportSerializer | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def update | ||||||
|  |     authorize @report, :update? | ||||||
|  |     @report.update!(report_params) | ||||||
|  |     render json: @report, serializer: REST::Admin::ReportSerializer | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def assign_to_self |   def assign_to_self | ||||||
|     authorize @report, :update? |     authorize @report, :update? | ||||||
|     @report.update!(assigned_account_id: current_account.id) |     @report.update!(assigned_account_id: current_account.id) | ||||||
| @ -74,6 +82,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController | |||||||
|     ReportFilter.new(filter_params).results |     ReportFilter.new(filter_params).results | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def report_params | ||||||
|  |     params.permit(:category, rule_ids: []) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def filter_params |   def filter_params | ||||||
|     params.permit(*FILTER_PARAMS) |     params.permit(*FILTER_PARAMS) | ||||||
|   end |   end | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| class Api::V1::Admin::RetentionController < Api::BaseController | class Api::V1::Admin::RetentionController < Api::BaseController | ||||||
|   protect_from_forgery with: :exception |   protect_from_forgery with: :exception | ||||||
| 
 | 
 | ||||||
|  |   before_action -> { authorize_if_got_token! :'admin:read' } | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_cohorts |   before_action :set_cohorts | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::V1::Admin::Trends::TagsController < Api::BaseController | class Api::V1::Admin::Trends::TagsController < Api::BaseController | ||||||
|  |   protect_from_forgery with: :exception | ||||||
|  | 
 | ||||||
|  |   before_action -> { authorize_if_got_token! :'admin:read' } | ||||||
|   before_action :require_staff! |   before_action :require_staff! | ||||||
|   before_action :set_tags |   before_action :set_tags | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ module Admin::FilterHelper | |||||||
|     RelationshipFilter::KEYS, |     RelationshipFilter::KEYS, | ||||||
|     AnnouncementFilter::KEYS, |     AnnouncementFilter::KEYS, | ||||||
|     Admin::ActionLogFilter::KEYS, |     Admin::ActionLogFilter::KEYS, | ||||||
|  |     Admin::StatusFilter::KEYS, | ||||||
|   ].flatten.freeze |   ].flatten.freeze | ||||||
| 
 | 
 | ||||||
|   def filter_link_to(text, link_to_params, link_class_params = link_to_params) |   def filter_link_to(text, link_to_params, link_class_params = link_to_params) | ||||||
|  | |||||||
| @ -0,0 +1,159 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import api from 'flavours/glitch/util/api'; | ||||||
|  | import { injectIntl, defineMessages } from 'react-intl'; | ||||||
|  | import classNames from 'classnames'; | ||||||
|  | 
 | ||||||
|  | const messages = defineMessages({ | ||||||
|  |   other: { id: 'report.categories.other', defaultMessage: 'Other' }, | ||||||
|  |   spam: { id: 'report.categories.spam', defaultMessage: 'Spam' }, | ||||||
|  |   violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Category extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     text: PropTypes.string.isRequired, | ||||||
|  |     selected: PropTypes.bool, | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     onSelect: PropTypes.func, | ||||||
|  |     children: PropTypes.node, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleClick = () => { | ||||||
|  |     const { id, disabled, onSelect } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!disabled) { | ||||||
|  |       onSelect(id); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { id, text, disabled, selected, children } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}> | ||||||
|  |         {selected && <input type='hidden' name='report[category]' value={id} />} | ||||||
|  | 
 | ||||||
|  |         <div className='report-reason-selector__category__label'> | ||||||
|  |           <span className={classNames('poll__input', { active: selected, disabled })} /> | ||||||
|  |           {text} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         {(selected && children) && ( | ||||||
|  |           <div className='report-reason-selector__category__rules'> | ||||||
|  |             {children} | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Rule extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     text: PropTypes.string.isRequired, | ||||||
|  |     selected: PropTypes.bool, | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     onToggle: PropTypes.func, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleClick = () => { | ||||||
|  |     const { id, disabled, onToggle } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!disabled) { | ||||||
|  |       onToggle(id); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { id, text, disabled, selected } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}> | ||||||
|  |         <span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} /> | ||||||
|  |         {selected && <input type='hidden' name='report[rule_ids][]' value={id} />} | ||||||
|  |         {text} | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default @injectIntl | ||||||
|  | class ReportReasonSelector extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     category: PropTypes.string.isRequired, | ||||||
|  |     rule_ids: PropTypes.arrayOf(PropTypes.string), | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     intl: PropTypes.object.isRequired, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   state = { | ||||||
|  |     category: this.props.category, | ||||||
|  |     rule_ids: this.props.rule_ids || [], | ||||||
|  |     rules: [], | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   componentDidMount() { | ||||||
|  |     api().get('/api/v1/instance').then(res => { | ||||||
|  |       this.setState({ | ||||||
|  |         rules: res.data.rules, | ||||||
|  |       }); | ||||||
|  |     }).catch(err => { | ||||||
|  |       console.error(err); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _save = () => { | ||||||
|  |     const { id, disabled } = this.props; | ||||||
|  |     const { category, rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     if (disabled) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     api().put(`/api/v1/admin/reports/${id}`, { | ||||||
|  |       category, | ||||||
|  |       rule_ids, | ||||||
|  |     }).catch(err => { | ||||||
|  |       console.error(err); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleSelect = id => { | ||||||
|  |     this.setState({ category: id }, () => this._save()); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleToggle = id => { | ||||||
|  |     const { rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     if (rule_ids.includes(id)) { | ||||||
|  |       this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save()); | ||||||
|  |     } else { | ||||||
|  |       this.setState({ rule_ids: [...rule_ids, id] }, () => this._save()); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { disabled, intl } = this.props; | ||||||
|  |     const { rules, category, rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div className='report-reason-selector'> | ||||||
|  |         <Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} /> | ||||||
|  |         <Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} /> | ||||||
|  |         <Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}> | ||||||
|  |           {rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)} | ||||||
|  |         </Category> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -595,39 +595,44 @@ body, | |||||||
| 
 | 
 | ||||||
| .log-entry { | .log-entry { | ||||||
|   line-height: 20px; |   line-height: 20px; | ||||||
|   padding: 15px 0; |   padding: 15px; | ||||||
|  |   padding-left: 15px * 2 + 40px; | ||||||
|   background: $ui-base-color; |   background: $ui-base-color; | ||||||
|   border-bottom: 1px solid lighten($ui-base-color, 4%); |   border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  |   position: relative; | ||||||
|  | 
 | ||||||
|  |   &:first-child { | ||||||
|  |     border-top-left-radius: 4px; | ||||||
|  |     border-top-right-radius: 4px; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   &:last-child { |   &:last-child { | ||||||
|  |     border-bottom-left-radius: 4px; | ||||||
|  |     border-bottom-right-radius: 4px; | ||||||
|     border-bottom: 0; |     border-bottom: 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   &:hover { | ||||||
|  |     background: lighten($ui-base-color, 4%); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   &__header { |   &__header { | ||||||
|     display: flex; |  | ||||||
|     justify-content: flex-start; |  | ||||||
|     align-items: center; |  | ||||||
|     color: $darker-text-color; |     color: $darker-text-color; | ||||||
|     font-size: 14px; |     font-size: 14px; | ||||||
|     padding: 0 10px; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__avatar { |   &__avatar { | ||||||
|     margin-right: 10px; |     position: absolute; | ||||||
|  |     left: 15px; | ||||||
|  |     top: 15px; | ||||||
| 
 | 
 | ||||||
|     .avatar { |     .avatar { | ||||||
|       display: block; |       border-radius: 4px; | ||||||
|       margin: 0; |  | ||||||
|       border-radius: 50%; |  | ||||||
|       width: 40px; |       width: 40px; | ||||||
|       height: 40px; |       height: 40px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__content { |  | ||||||
|     max-width: calc(100% - 90px); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   &__title { |   &__title { | ||||||
|     word-wrap: break-word; |     word-wrap: break-word; | ||||||
|   } |   } | ||||||
| @ -643,6 +648,14 @@ body, | |||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   a { | ||||||
|  |     &:hover, | ||||||
|  |     &:focus, | ||||||
|  |     &:active { | ||||||
|  |       text-decoration: underline; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a.name-tag, | a.name-tag, | ||||||
| @ -671,8 +684,9 @@ a.inline-name-tag, | |||||||
| 
 | 
 | ||||||
| a.name-tag, | a.name-tag, | ||||||
| .name-tag { | .name-tag { | ||||||
|   display: flex; |   display: inline-flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|  |   vertical-align: top; | ||||||
| 
 | 
 | ||||||
|   .avatar { |   .avatar { | ||||||
|     display: block; |     display: block; | ||||||
| @ -1130,3 +1144,287 @@ a.sparkline { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .report-reason-selector { | ||||||
|  |   border-radius: 4px; | ||||||
|  |   background: $ui-base-color; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | 
 | ||||||
|  |   &__category { | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__label { | ||||||
|  |       padding: 15px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__rules { | ||||||
|  |       margin-left: 30px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__rule { | ||||||
|  |     cursor: pointer; | ||||||
|  |     padding: 15px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-header { | ||||||
|  |   display: grid; | ||||||
|  |   grid-gap: 15px; | ||||||
|  |   grid-template-columns: minmax(0, 1fr) 300px; | ||||||
|  | 
 | ||||||
|  |   &__details { | ||||||
|  |     &__item { | ||||||
|  |       border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||||
|  |       padding: 15px 0; | ||||||
|  | 
 | ||||||
|  |       &:last-child { | ||||||
|  |         border-bottom: 0; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       &__header { | ||||||
|  |         font-weight: 600; | ||||||
|  |         padding: 4px 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &--horizontal { | ||||||
|  |       display: grid; | ||||||
|  |       grid-auto-columns: minmax(0, 1fr); | ||||||
|  |       grid-auto-flow: column; | ||||||
|  | 
 | ||||||
|  |       .report-header__details__item { | ||||||
|  |         border-bottom: 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .account-card { | ||||||
|  |   background: $ui-base-color; | ||||||
|  |   border-radius: 4px; | ||||||
|  | 
 | ||||||
|  |   &__header { | ||||||
|  |     padding: 4px; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     height: 128px; | ||||||
|  | 
 | ||||||
|  |     img { | ||||||
|  |       display: block; | ||||||
|  |       margin: 0; | ||||||
|  |       width: 100%; | ||||||
|  |       height: 100%; | ||||||
|  |       object-fit: cover; | ||||||
|  |       background: darken($ui-base-color, 8%); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__title { | ||||||
|  |     margin-top: -25px; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: flex-end; | ||||||
|  | 
 | ||||||
|  |     &__avatar { | ||||||
|  |       padding: 15px; | ||||||
|  | 
 | ||||||
|  |       img { | ||||||
|  |         display: block; | ||||||
|  |         margin: 0; | ||||||
|  |         width: 56px; | ||||||
|  |         height: 56px; | ||||||
|  |         background: darken($ui-base-color, 8%); | ||||||
|  |         border-radius: 8px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .display-name { | ||||||
|  |       color: $darker-text-color; | ||||||
|  |       padding-bottom: 15px; | ||||||
|  |       font-size: 15px; | ||||||
|  | 
 | ||||||
|  |       bdi { | ||||||
|  |         display: block; | ||||||
|  |         color: $primary-text-color; | ||||||
|  |         font-weight: 500; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__bio { | ||||||
|  |     padding: 0 15px; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     word-wrap: break-word; | ||||||
|  |     max-height: 18px * 2; | ||||||
|  |     position: relative; | ||||||
|  | 
 | ||||||
|  |     &::after { | ||||||
|  |       display: block; | ||||||
|  |       content: ""; | ||||||
|  |       width: 50px; | ||||||
|  |       height: 18px; | ||||||
|  |       position: absolute; | ||||||
|  |       bottom: 0; | ||||||
|  |       right: 15px; | ||||||
|  |       background: linear-gradient(to left, $ui-base-color, transparent); | ||||||
|  |       pointer-events: none; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__actions { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     padding-top: 10px; | ||||||
|  | 
 | ||||||
|  |     &__button { | ||||||
|  |       flex: 0 0 auto; | ||||||
|  |       padding: 0 15px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__counters { | ||||||
|  |     flex: 1 1 auto; | ||||||
|  |     display: grid; | ||||||
|  |     grid-auto-columns: minmax(0, 1fr); | ||||||
|  |     grid-auto-flow: column; | ||||||
|  | 
 | ||||||
|  |     &__item { | ||||||
|  |       padding: 15px; | ||||||
|  |       text-align: center; | ||||||
|  |       color: $primary-text-color; | ||||||
|  |       font-weight: 600; | ||||||
|  |       font-size: 15px; | ||||||
|  | 
 | ||||||
|  |       small { | ||||||
|  |         display: block; | ||||||
|  |         color: $darker-text-color; | ||||||
|  |         font-weight: 400; | ||||||
|  |         font-size: 13px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-notes { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | 
 | ||||||
|  |   &__item { | ||||||
|  |     background: $ui-base-color; | ||||||
|  |     position: relative; | ||||||
|  |     padding: 15px; | ||||||
|  |     padding-left: 15px * 2 + 40px; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:first-child { | ||||||
|  |       border-top-left-radius: 4px; | ||||||
|  |       border-top-right-radius: 4px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom-left-radius: 4px; | ||||||
|  |       border-bottom-right-radius: 4px; | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:hover { | ||||||
|  |       background-color: lighten($ui-base-color, 4%); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__avatar { | ||||||
|  |       position: absolute; | ||||||
|  |       left: 15px; | ||||||
|  |       top: 15px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |       width: 40px; | ||||||
|  |       height: 40px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__header { | ||||||
|  |       color: $darker-text-color; | ||||||
|  |       font-size: 15px; | ||||||
|  |       line-height: 20px; | ||||||
|  |       margin-bottom: 4px; | ||||||
|  | 
 | ||||||
|  |       .username a { | ||||||
|  |         color: $primary-text-color; | ||||||
|  |         font-weight: 500; | ||||||
|  |         text-decoration: none; | ||||||
|  |         margin-right: 5px; | ||||||
|  | 
 | ||||||
|  |         &:hover, | ||||||
|  |         &:focus, | ||||||
|  |         &:active { | ||||||
|  |           text-decoration: underline; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       time { | ||||||
|  |         margin-left: 5px; | ||||||
|  |         vertical-align: baseline; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__content { | ||||||
|  |       font-size: 15px; | ||||||
|  |       line-height: 20px; | ||||||
|  |       word-wrap: break-word; | ||||||
|  |       font-weight: 400; | ||||||
|  |       color: $primary-text-color; | ||||||
|  | 
 | ||||||
|  |       p { | ||||||
|  |         margin-bottom: 20px; | ||||||
|  |         white-space: pre-wrap; | ||||||
|  |         unicode-bidi: plaintext; | ||||||
|  | 
 | ||||||
|  |         &:last-child { | ||||||
|  |           margin-bottom: 0; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__actions { | ||||||
|  |       position: absolute; | ||||||
|  |       top: 15px; | ||||||
|  |       right: 15px; | ||||||
|  |       text-align: right; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-actions { | ||||||
|  |   border: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |   &__item { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     line-height: 18px; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__button { | ||||||
|  |       flex: 0 0 auto; | ||||||
|  |       width: 100px; | ||||||
|  |       padding: 15px; | ||||||
|  |       padding-right: 0; | ||||||
|  | 
 | ||||||
|  |       .button { | ||||||
|  |         display: block; | ||||||
|  |         width: 100%; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__description { | ||||||
|  |       padding: 15px; | ||||||
|  |       font-size: 14px; | ||||||
|  |       color: $dark-text-color; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -150,6 +150,21 @@ | |||||||
|     &:active { |     &:active { | ||||||
|       outline: 0 !important; |       outline: 0 !important; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     &.disabled { | ||||||
|  |       border-color: $dark-text-color; | ||||||
|  | 
 | ||||||
|  |       &.active { | ||||||
|  |         background: $dark-text-color; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       &:active, | ||||||
|  |       &:focus, | ||||||
|  |       &:hover { | ||||||
|  |         border-color: $dark-text-color; | ||||||
|  |         border-width: 1px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__number { |   &__number { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ export const profileLink = '/settings/profile'; | |||||||
| export const signOutLink = '/auth/sign_out'; | export const signOutLink = '/auth/sign_out'; | ||||||
| export const termsLink = '/terms'; | export const termsLink = '/terms'; | ||||||
| export const accountAdminLink = (id) => `/admin/accounts/${id}`; | export const accountAdminLink = (id) => `/admin/accounts/${id}`; | ||||||
| export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`; | export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_id}`; | ||||||
| export const filterEditLink = (id) => `/filters/${id}/edit`; | export const filterEditLink = (id) => `/filters/${id}/edit`; | ||||||
| export const relationshipsLink = '/relationships'; | export const relationshipsLink = '/relationships'; | ||||||
| export const securityLink = '/auth/edit'; | export const securityLink = '/auth/edit'; | ||||||
|  | |||||||
							
								
								
									
										159
									
								
								app/javascript/mastodon/components/admin/ReportReasonSelector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								app/javascript/mastodon/components/admin/ReportReasonSelector.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import api from 'mastodon/api'; | ||||||
|  | import { injectIntl, defineMessages } from 'react-intl'; | ||||||
|  | import classNames from 'classnames'; | ||||||
|  | 
 | ||||||
|  | const messages = defineMessages({ | ||||||
|  |   other: { id: 'report.categories.other', defaultMessage: 'Other' }, | ||||||
|  |   spam: { id: 'report.categories.spam', defaultMessage: 'Spam' }, | ||||||
|  |   violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Category extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     text: PropTypes.string.isRequired, | ||||||
|  |     selected: PropTypes.bool, | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     onSelect: PropTypes.func, | ||||||
|  |     children: PropTypes.node, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleClick = () => { | ||||||
|  |     const { id, disabled, onSelect } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!disabled) { | ||||||
|  |       onSelect(id); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { id, text, disabled, selected, children } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}> | ||||||
|  |         {selected && <input type='hidden' name='report[category]' value={id} />} | ||||||
|  | 
 | ||||||
|  |         <div className='report-reason-selector__category__label'> | ||||||
|  |           <span className={classNames('poll__input', { active: selected, disabled })} /> | ||||||
|  |           {text} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         {(selected && children) && ( | ||||||
|  |           <div className='report-reason-selector__category__rules'> | ||||||
|  |             {children} | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Rule extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     text: PropTypes.string.isRequired, | ||||||
|  |     selected: PropTypes.bool, | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     onToggle: PropTypes.func, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleClick = () => { | ||||||
|  |     const { id, disabled, onToggle } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!disabled) { | ||||||
|  |       onToggle(id); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { id, text, disabled, selected } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}> | ||||||
|  |         <span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} /> | ||||||
|  |         {selected && <input type='hidden' name='report[rule_ids][]' value={id} />} | ||||||
|  |         {text} | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default @injectIntl | ||||||
|  | class ReportReasonSelector extends React.PureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     id: PropTypes.string.isRequired, | ||||||
|  |     category: PropTypes.string.isRequired, | ||||||
|  |     rule_ids: PropTypes.arrayOf(PropTypes.string), | ||||||
|  |     disabled: PropTypes.bool, | ||||||
|  |     intl: PropTypes.object.isRequired, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   state = { | ||||||
|  |     category: this.props.category, | ||||||
|  |     rule_ids: this.props.rule_ids || [], | ||||||
|  |     rules: [], | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   componentDidMount() { | ||||||
|  |     api().get('/api/v1/instance').then(res => { | ||||||
|  |       this.setState({ | ||||||
|  |         rules: res.data.rules, | ||||||
|  |       }); | ||||||
|  |     }).catch(err => { | ||||||
|  |       console.error(err); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _save = () => { | ||||||
|  |     const { id, disabled } = this.props; | ||||||
|  |     const { category, rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     if (disabled) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     api().put(`/api/v1/admin/reports/${id}`, { | ||||||
|  |       category, | ||||||
|  |       rule_ids, | ||||||
|  |     }).catch(err => { | ||||||
|  |       console.error(err); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleSelect = id => { | ||||||
|  |     this.setState({ category: id }, () => this._save()); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleToggle = id => { | ||||||
|  |     const { rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     if (rule_ids.includes(id)) { | ||||||
|  |       this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save()); | ||||||
|  |     } else { | ||||||
|  |       this.setState({ rule_ids: [...rule_ids, id] }, () => this._save()); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render () { | ||||||
|  |     const { disabled, intl } = this.props; | ||||||
|  |     const { rules, category, rule_ids } = this.state; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div className='report-reason-selector'> | ||||||
|  |         <Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} /> | ||||||
|  |         <Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} /> | ||||||
|  |         <Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}> | ||||||
|  |           {rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)} | ||||||
|  |         </Category> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -291,7 +291,7 @@ class StatusActionBar extends ImmutablePureComponent { | |||||||
|       if (isStaff) { |       if (isStaff) { | ||||||
|         menu.push(null); |         menu.push(null); | ||||||
|         menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); |         menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); | ||||||
|         menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); |         menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -245,7 +245,7 @@ class ActionBar extends React.PureComponent { | |||||||
|       if (isStaff) { |       if (isStaff) { | ||||||
|         menu.push(null); |         menu.push(null); | ||||||
|         menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); |         menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); | ||||||
|         menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); |         menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -533,6 +533,10 @@ ul { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ul.rules-list { | ||||||
|  |   padding-top: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) { | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) { | ||||||
|   body { |   body { | ||||||
|     min-height: 1024px !important; |     min-height: 1024px !important; | ||||||
|  | |||||||
| @ -595,39 +595,44 @@ body, | |||||||
| 
 | 
 | ||||||
| .log-entry { | .log-entry { | ||||||
|   line-height: 20px; |   line-height: 20px; | ||||||
|   padding: 15px 0; |   padding: 15px; | ||||||
|  |   padding-left: 15px * 2 + 40px; | ||||||
|   background: $ui-base-color; |   background: $ui-base-color; | ||||||
|   border-bottom: 1px solid lighten($ui-base-color, 4%); |   border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  |   position: relative; | ||||||
|  | 
 | ||||||
|  |   &:first-child { | ||||||
|  |     border-top-left-radius: 4px; | ||||||
|  |     border-top-right-radius: 4px; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   &:last-child { |   &:last-child { | ||||||
|  |     border-bottom-left-radius: 4px; | ||||||
|  |     border-bottom-right-radius: 4px; | ||||||
|     border-bottom: 0; |     border-bottom: 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   &:hover { | ||||||
|  |     background: lighten($ui-base-color, 4%); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   &__header { |   &__header { | ||||||
|     display: flex; |  | ||||||
|     justify-content: flex-start; |  | ||||||
|     align-items: center; |  | ||||||
|     color: $darker-text-color; |     color: $darker-text-color; | ||||||
|     font-size: 14px; |     font-size: 14px; | ||||||
|     padding: 0 10px; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__avatar { |   &__avatar { | ||||||
|     margin-right: 10px; |     position: absolute; | ||||||
|  |     left: 15px; | ||||||
|  |     top: 15px; | ||||||
| 
 | 
 | ||||||
|     .avatar { |     .avatar { | ||||||
|       display: block; |       border-radius: 4px; | ||||||
|       margin: 0; |  | ||||||
|       border-radius: 50%; |  | ||||||
|       width: 40px; |       width: 40px; | ||||||
|       height: 40px; |       height: 40px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__content { |  | ||||||
|     max-width: calc(100% - 90px); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   &__title { |   &__title { | ||||||
|     word-wrap: break-word; |     word-wrap: break-word; | ||||||
|   } |   } | ||||||
| @ -643,6 +648,14 @@ body, | |||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   a { | ||||||
|  |     &:hover, | ||||||
|  |     &:focus, | ||||||
|  |     &:active { | ||||||
|  |       text-decoration: underline; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a.name-tag, | a.name-tag, | ||||||
| @ -671,8 +684,9 @@ a.inline-name-tag, | |||||||
| 
 | 
 | ||||||
| a.name-tag, | a.name-tag, | ||||||
| .name-tag { | .name-tag { | ||||||
|   display: flex; |   display: inline-flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|  |   vertical-align: top; | ||||||
| 
 | 
 | ||||||
|   .avatar { |   .avatar { | ||||||
|     display: block; |     display: block; | ||||||
| @ -1130,3 +1144,287 @@ a.sparkline { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .report-reason-selector { | ||||||
|  |   border-radius: 4px; | ||||||
|  |   background: $ui-base-color; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | 
 | ||||||
|  |   &__category { | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__label { | ||||||
|  |       padding: 15px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__rules { | ||||||
|  |       margin-left: 30px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__rule { | ||||||
|  |     cursor: pointer; | ||||||
|  |     padding: 15px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-header { | ||||||
|  |   display: grid; | ||||||
|  |   grid-gap: 15px; | ||||||
|  |   grid-template-columns: minmax(0, 1fr) 300px; | ||||||
|  | 
 | ||||||
|  |   &__details { | ||||||
|  |     &__item { | ||||||
|  |       border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||||
|  |       padding: 15px 0; | ||||||
|  | 
 | ||||||
|  |       &:last-child { | ||||||
|  |         border-bottom: 0; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       &__header { | ||||||
|  |         font-weight: 600; | ||||||
|  |         padding: 4px 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &--horizontal { | ||||||
|  |       display: grid; | ||||||
|  |       grid-auto-columns: minmax(0, 1fr); | ||||||
|  |       grid-auto-flow: column; | ||||||
|  | 
 | ||||||
|  |       .report-header__details__item { | ||||||
|  |         border-bottom: 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .account-card { | ||||||
|  |   background: $ui-base-color; | ||||||
|  |   border-radius: 4px; | ||||||
|  | 
 | ||||||
|  |   &__header { | ||||||
|  |     padding: 4px; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     height: 128px; | ||||||
|  | 
 | ||||||
|  |     img { | ||||||
|  |       display: block; | ||||||
|  |       margin: 0; | ||||||
|  |       width: 100%; | ||||||
|  |       height: 100%; | ||||||
|  |       object-fit: cover; | ||||||
|  |       background: darken($ui-base-color, 8%); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__title { | ||||||
|  |     margin-top: -25px; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: flex-end; | ||||||
|  | 
 | ||||||
|  |     &__avatar { | ||||||
|  |       padding: 15px; | ||||||
|  | 
 | ||||||
|  |       img { | ||||||
|  |         display: block; | ||||||
|  |         margin: 0; | ||||||
|  |         width: 56px; | ||||||
|  |         height: 56px; | ||||||
|  |         background: darken($ui-base-color, 8%); | ||||||
|  |         border-radius: 8px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .display-name { | ||||||
|  |       color: $darker-text-color; | ||||||
|  |       padding-bottom: 15px; | ||||||
|  |       font-size: 15px; | ||||||
|  | 
 | ||||||
|  |       bdi { | ||||||
|  |         display: block; | ||||||
|  |         color: $primary-text-color; | ||||||
|  |         font-weight: 500; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__bio { | ||||||
|  |     padding: 0 15px; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     word-wrap: break-word; | ||||||
|  |     max-height: 18px * 2; | ||||||
|  |     position: relative; | ||||||
|  | 
 | ||||||
|  |     &::after { | ||||||
|  |       display: block; | ||||||
|  |       content: ""; | ||||||
|  |       width: 50px; | ||||||
|  |       height: 18px; | ||||||
|  |       position: absolute; | ||||||
|  |       bottom: 0; | ||||||
|  |       right: 15px; | ||||||
|  |       background: linear-gradient(to left, $ui-base-color, transparent); | ||||||
|  |       pointer-events: none; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__actions { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     padding-top: 10px; | ||||||
|  | 
 | ||||||
|  |     &__button { | ||||||
|  |       flex: 0 0 auto; | ||||||
|  |       padding: 0 15px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__counters { | ||||||
|  |     flex: 1 1 auto; | ||||||
|  |     display: grid; | ||||||
|  |     grid-auto-columns: minmax(0, 1fr); | ||||||
|  |     grid-auto-flow: column; | ||||||
|  | 
 | ||||||
|  |     &__item { | ||||||
|  |       padding: 15px; | ||||||
|  |       text-align: center; | ||||||
|  |       color: $primary-text-color; | ||||||
|  |       font-weight: 600; | ||||||
|  |       font-size: 15px; | ||||||
|  | 
 | ||||||
|  |       small { | ||||||
|  |         display: block; | ||||||
|  |         color: $darker-text-color; | ||||||
|  |         font-weight: 400; | ||||||
|  |         font-size: 13px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-notes { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | 
 | ||||||
|  |   &__item { | ||||||
|  |     background: $ui-base-color; | ||||||
|  |     position: relative; | ||||||
|  |     padding: 15px; | ||||||
|  |     padding-left: 15px * 2 + 40px; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:first-child { | ||||||
|  |       border-top-left-radius: 4px; | ||||||
|  |       border-top-right-radius: 4px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom-left-radius: 4px; | ||||||
|  |       border-bottom-right-radius: 4px; | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:hover { | ||||||
|  |       background-color: lighten($ui-base-color, 4%); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__avatar { | ||||||
|  |       position: absolute; | ||||||
|  |       left: 15px; | ||||||
|  |       top: 15px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |       width: 40px; | ||||||
|  |       height: 40px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__header { | ||||||
|  |       color: $darker-text-color; | ||||||
|  |       font-size: 15px; | ||||||
|  |       line-height: 20px; | ||||||
|  |       margin-bottom: 4px; | ||||||
|  | 
 | ||||||
|  |       .username a { | ||||||
|  |         color: $primary-text-color; | ||||||
|  |         font-weight: 500; | ||||||
|  |         text-decoration: none; | ||||||
|  |         margin-right: 5px; | ||||||
|  | 
 | ||||||
|  |         &:hover, | ||||||
|  |         &:focus, | ||||||
|  |         &:active { | ||||||
|  |           text-decoration: underline; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       time { | ||||||
|  |         margin-left: 5px; | ||||||
|  |         vertical-align: baseline; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__content { | ||||||
|  |       font-size: 15px; | ||||||
|  |       line-height: 20px; | ||||||
|  |       word-wrap: break-word; | ||||||
|  |       font-weight: 400; | ||||||
|  |       color: $primary-text-color; | ||||||
|  | 
 | ||||||
|  |       p { | ||||||
|  |         margin-bottom: 20px; | ||||||
|  |         white-space: pre-wrap; | ||||||
|  |         unicode-bidi: plaintext; | ||||||
|  | 
 | ||||||
|  |         &:last-child { | ||||||
|  |           margin-bottom: 0; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__actions { | ||||||
|  |       position: absolute; | ||||||
|  |       top: 15px; | ||||||
|  |       right: 15px; | ||||||
|  |       text-align: right; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .report-actions { | ||||||
|  |   border: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |   &__item { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     line-height: 18px; | ||||||
|  |     border-bottom: 1px solid darken($ui-base-color, 8%); | ||||||
|  | 
 | ||||||
|  |     &:last-child { | ||||||
|  |       border-bottom: 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__button { | ||||||
|  |       flex: 0 0 auto; | ||||||
|  |       width: 100px; | ||||||
|  |       padding: 15px; | ||||||
|  |       padding-right: 0; | ||||||
|  | 
 | ||||||
|  |       .button { | ||||||
|  |         display: block; | ||||||
|  |         width: 100%; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__description { | ||||||
|  |       padding: 15px; | ||||||
|  |       font-size: 14px; | ||||||
|  |       color: $dark-text-color; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -143,6 +143,21 @@ | |||||||
|     &:active { |     &:active { | ||||||
|       outline: 0 !important; |       outline: 0 !important; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     &.disabled { | ||||||
|  |       border-color: $dark-text-color; | ||||||
|  | 
 | ||||||
|  |       &.active { | ||||||
|  |         background: $dark-text-color; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       &:active, | ||||||
|  |       &:focus, | ||||||
|  |       &:hover { | ||||||
|  |         border-color: $dark-text-color; | ||||||
|  |         border-width: 1px; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__number { |   &__number { | ||||||
|  | |||||||
| @ -6,11 +6,11 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure: | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def total |   def total | ||||||
|     Report.resolved.where(updated_at: time_period).count |     Report.resolved.where(action_taken_at: time_period).count | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def previous_total |   def previous_total | ||||||
|     Report.resolved.where(updated_at: previous_time_period).count |     Report.resolved.where(action_taken_at: previous_time_period).count | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def data |   def data | ||||||
| @ -19,8 +19,7 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure: | |||||||
|         WITH resolved_reports AS ( |         WITH resolved_reports AS ( | ||||||
|           SELECT reports.id |           SELECT reports.id | ||||||
|           FROM reports |           FROM reports | ||||||
|           WHERE action_taken |           WHERE date_trunc('day', reports.action_taken_at)::date = axis.period | ||||||
|             AND date_trunc('day', reports.updated_at)::date = axis.period |  | ||||||
|         ) |         ) | ||||||
|         SELECT count(*) FROM resolved_reports |         SELECT count(*) FROM resolved_reports | ||||||
|       ) AS value |       ) AS value | ||||||
|  | |||||||
| @ -160,11 +160,11 @@ class UserMailer < Devise::Mailer | |||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def warning(user, warning, status_ids = nil) |   def warning(user, warning) | ||||||
|     @resource = user |     @resource = user | ||||||
|     @warning  = warning |     @warning  = warning | ||||||
|     @instance = Rails.configuration.x.local_domain |     @instance = Rails.configuration.x.local_domain | ||||||
|     @statuses = Status.where(id: status_ids).includes(:account) if status_ids.is_a?(Array) |     @statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account]) | ||||||
| 
 | 
 | ||||||
|     I18n.with_locale(@resource.locale || I18n.default_locale) do |     I18n.with_locale(@resource.locale || I18n.default_locale) do | ||||||
|       mail to: @resource.email, |       mail to: @resource.email, | ||||||
|  | |||||||
| @ -10,14 +10,30 @@ | |||||||
| #  text              :text             default(""), not null | #  text              :text             default(""), not null | ||||||
| #  created_at        :datetime         not null | #  created_at        :datetime         not null | ||||||
| #  updated_at        :datetime         not null | #  updated_at        :datetime         not null | ||||||
|  | #  report_id         :bigint(8) | ||||||
|  | #  status_ids        :string           is an Array | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| class AccountWarning < ApplicationRecord | class AccountWarning < ApplicationRecord | ||||||
|   enum action: %i(none disable sensitive silence suspend), _suffix: :action |   enum action: { | ||||||
|  |     none:            0, | ||||||
|  |     disable:         1_000, | ||||||
|  |     delete_statuses: 1_500, | ||||||
|  |     sensitive:       2_000, | ||||||
|  |     silence:         3_000, | ||||||
|  |     suspend:         4_000, | ||||||
|  |   }, _suffix: :action | ||||||
| 
 | 
 | ||||||
|   belongs_to :account, inverse_of: :account_warnings |   belongs_to :account, inverse_of: :account_warnings | ||||||
|   belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings |   belongs_to :target_account, class_name: 'Account', inverse_of: :strikes | ||||||
|  |   belongs_to :report, optional: true | ||||||
| 
 | 
 | ||||||
|   scope :latest, -> { order(created_at: :desc) } |   has_one :appeal, dependent: :destroy | ||||||
|  | 
 | ||||||
|  |   scope :latest, -> { order(id: :desc) } | ||||||
|   scope :custom, -> { where.not(text: '') } |   scope :custom, -> { where.not(text: '') } | ||||||
|  | 
 | ||||||
|  |   def statuses | ||||||
|  |     Status.with_discarded.where(id: status_ids || []) | ||||||
|  |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ class Admin::AccountAction | |||||||
|   def save! |   def save! | ||||||
|     ApplicationRecord.transaction do |     ApplicationRecord.transaction do | ||||||
|       process_action! |       process_action! | ||||||
|       process_warning! |       process_strike! | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     process_email! |     process_email! | ||||||
| @ -74,20 +74,14 @@ class Admin::AccountAction | |||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def process_warning! |   def process_strike! | ||||||
|     return unless warnable? |     @warning = target_account.strikes.create!( | ||||||
| 
 |       account: current_account, | ||||||
|     authorize(target_account, :warn?) |       report: report, | ||||||
| 
 |       action: type, | ||||||
|     @warning = AccountWarning.create!(target_account: target_account, |       text: text_for_warning, | ||||||
|                                       account: current_account, |       status_ids: status_ids | ||||||
|                                       action: type, |     ) | ||||||
|                                       text: text_for_warning) |  | ||||||
| 
 |  | ||||||
|     # A log entry is only interesting if the warning contains |  | ||||||
|     # custom text from someone. Otherwise it's just noise. |  | ||||||
| 
 |  | ||||||
|     log_action(:create, warning) if warning.text.present? |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def process_reports! |   def process_reports! | ||||||
| @ -143,7 +137,7 @@ class Admin::AccountAction | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def process_email! |   def process_email! | ||||||
|     UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable? |     UserMailer.warning(target_account.user, warning).deliver_later! if warnable? | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def warnable? |   def warnable? | ||||||
| @ -151,7 +145,7 @@ class Admin::AccountAction | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def status_ids |   def status_ids | ||||||
|     report.status_ids if report && include_statuses |     report.status_ids if with_report? && include_statuses | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def reports |   def reports | ||||||
|  | |||||||
							
								
								
									
										92
									
								
								app/models/admin/status_batch_action.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								app/models/admin/status_batch_action.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class Admin::StatusBatchAction | ||||||
|  |   include ActiveModel::Model | ||||||
|  |   include AccountableConcern | ||||||
|  |   include Authorization | ||||||
|  | 
 | ||||||
|  |   attr_accessor :current_account, :type, | ||||||
|  |                 :status_ids, :report_id | ||||||
|  | 
 | ||||||
|  |   def save! | ||||||
|  |     process_action! | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def statuses | ||||||
|  |     Status.with_discarded.where(id: status_ids) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def process_action! | ||||||
|  |     return if status_ids.empty? | ||||||
|  | 
 | ||||||
|  |     case type | ||||||
|  |     when 'delete' | ||||||
|  |       handle_delete! | ||||||
|  |     when 'report' | ||||||
|  |       handle_report! | ||||||
|  |     when 'remove_from_report' | ||||||
|  |       handle_remove_from_report! | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def handle_delete! | ||||||
|  |     statuses.each { |status| authorize(status, :destroy?) } | ||||||
|  | 
 | ||||||
|  |     ApplicationRecord.transaction do | ||||||
|  |       statuses.each do |status| | ||||||
|  |         status.discard | ||||||
|  |         log_action(:destroy, status) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       if with_report? | ||||||
|  |         report.resolve!(current_account) | ||||||
|  |         log_action(:resolve, report) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       @warning = target_account.strikes.create!( | ||||||
|  |         action: :delete_statuses, | ||||||
|  |         account: current_account, | ||||||
|  |         report: report, | ||||||
|  |         status_ids: status_ids | ||||||
|  |       ) | ||||||
|  | 
 | ||||||
|  |       statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     UserMailer.warning(target_account.user, @warning).deliver_later! if target_account.local? | ||||||
|  |     RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, preserve: target_account.local?, immediate: !target_account.local?] } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def handle_report! | ||||||
|  |     @report = Report.new(report_params) unless with_report? | ||||||
|  |     @report.status_ids = (@report.status_ids + status_ids.map(&:to_i)).uniq | ||||||
|  |     @report.save! | ||||||
|  | 
 | ||||||
|  |     @report_id = @report.id | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def handle_remove_from_report! | ||||||
|  |     return unless with_report? | ||||||
|  | 
 | ||||||
|  |     report.status_ids -= status_ids.map(&:to_i) | ||||||
|  |     report.save! | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def report | ||||||
|  |     @report ||= Report.find(report_id) if report_id.present? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def with_report? | ||||||
|  |     !report.nil? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def target_account | ||||||
|  |     @target_account ||= statuses.first.account | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def report_params | ||||||
|  |     { account: current_account, target_account: target_account } | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										41
									
								
								app/models/admin/status_filter.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								app/models/admin/status_filter.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class Admin::StatusFilter | ||||||
|  |   KEYS = %i( | ||||||
|  |     media | ||||||
|  |     id | ||||||
|  |     report_id | ||||||
|  |   ).freeze | ||||||
|  | 
 | ||||||
|  |   attr_reader :params | ||||||
|  | 
 | ||||||
|  |   def initialize(account, params) | ||||||
|  |     @account = account | ||||||
|  |     @params  = params | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def results | ||||||
|  |     scope = @account.statuses.where(visibility: [:public, :unlisted]) | ||||||
|  | 
 | ||||||
|  |     params.each do |key, value| | ||||||
|  |       next if %w(page report_id).include?(key.to_s) | ||||||
|  | 
 | ||||||
|  |       scope.merge!(scope_for(key, value.to_s.strip)) if value.present? | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     scope | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def scope_for(key, value) | ||||||
|  |     case key.to_s | ||||||
|  |     when 'media' | ||||||
|  |       Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) | ||||||
|  |     when 'id' | ||||||
|  |       Status.where(id: value) | ||||||
|  |     else | ||||||
|  |       raise "Unknown filter: #{key}" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -42,7 +42,7 @@ module AccountAssociations | |||||||
|     has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account |     has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account | ||||||
|     has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account |     has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account | ||||||
|     has_many :account_warnings, dependent: :destroy, inverse_of: :account |     has_many :account_warnings, dependent: :destroy, inverse_of: :account | ||||||
|     has_many :targeted_account_warnings, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account |     has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account | ||||||
| 
 | 
 | ||||||
|     # Lists (that the account is on, not owned by the account) |     # Lists (that the account is on, not owned by the account) | ||||||
|     has_many :list_accounts, inverse_of: :account, dependent: :destroy |     has_many :list_accounts, inverse_of: :account, dependent: :destroy | ||||||
|  | |||||||
| @ -1,45 +0,0 @@ | |||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| class Form::StatusBatch |  | ||||||
|   include ActiveModel::Model |  | ||||||
|   include AccountableConcern |  | ||||||
| 
 |  | ||||||
|   attr_accessor :status_ids, :action, :current_account |  | ||||||
| 
 |  | ||||||
|   def save |  | ||||||
|     case action |  | ||||||
|     when 'nsfw_on', 'nsfw_off' |  | ||||||
|       change_sensitive(action == 'nsfw_on') |  | ||||||
|     when 'delete' |  | ||||||
|       delete_statuses |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   private |  | ||||||
| 
 |  | ||||||
|   def change_sensitive(sensitive) |  | ||||||
|     media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id) |  | ||||||
| 
 |  | ||||||
|     ApplicationRecord.transaction do |  | ||||||
|       Status.where(id: media_attached_status_ids).reorder(nil).find_each do |status| |  | ||||||
|         status.update!(sensitive: sensitive) |  | ||||||
|         log_action :update, status |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     true |  | ||||||
|   rescue ActiveRecord::RecordInvalid |  | ||||||
|     false |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def delete_statuses |  | ||||||
|     Status.where(id: status_ids).reorder(nil).find_each do |status| |  | ||||||
|       status.discard |  | ||||||
|       RemovalWorker.perform_async(status.id, immediate: true) |  | ||||||
|       Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) |  | ||||||
|       log_action :destroy, status |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     true |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @ -6,7 +6,6 @@ | |||||||
| #  id                         :bigint(8)        not null, primary key | #  id                         :bigint(8)        not null, primary key | ||||||
| #  status_ids                 :bigint(8)        default([]), not null, is an Array | #  status_ids                 :bigint(8)        default([]), not null, is an Array | ||||||
| #  comment                    :text             default(""), not null | #  comment                    :text             default(""), not null | ||||||
| #  action_taken               :boolean          default(FALSE), not null |  | ||||||
| #  created_at                 :datetime         not null | #  created_at                 :datetime         not null | ||||||
| #  updated_at                 :datetime         not null | #  updated_at                 :datetime         not null | ||||||
| #  account_id                 :bigint(8)        not null | #  account_id                 :bigint(8)        not null | ||||||
| @ -15,9 +14,14 @@ | |||||||
| #  assigned_account_id        :bigint(8) | #  assigned_account_id        :bigint(8) | ||||||
| #  uri                        :string | #  uri                        :string | ||||||
| #  forwarded                  :boolean | #  forwarded                  :boolean | ||||||
|  | #  category                   :integer          default("other"), not null | ||||||
|  | #  action_taken_at            :datetime | ||||||
|  | #  rule_ids                   :bigint(8)        is an Array | ||||||
| # | # | ||||||
| 
 | 
 | ||||||
| class Report < ApplicationRecord | class Report < ApplicationRecord | ||||||
|  |   self.ignored_columns = %w(action_taken) | ||||||
|  | 
 | ||||||
|   include Paginable |   include Paginable | ||||||
|   include RateLimitable |   include RateLimitable | ||||||
| 
 | 
 | ||||||
| @ -30,11 +34,17 @@ class Report < ApplicationRecord | |||||||
| 
 | 
 | ||||||
|   has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy |   has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy | ||||||
| 
 | 
 | ||||||
|   scope :unresolved, -> { where(action_taken: false) } |   scope :unresolved, -> { where(action_taken_at: nil) } | ||||||
|   scope :resolved,   -> { where(action_taken: true) } |   scope :resolved,   -> { where.not(action_taken_at: nil) } | ||||||
|   scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) } |   scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) } | ||||||
| 
 | 
 | ||||||
|   validates :comment, length: { maximum: 1000 } |   validates :comment, length: { maximum: 1_000 } | ||||||
|  | 
 | ||||||
|  |   enum category: { | ||||||
|  |     other: 0, | ||||||
|  |     spam: 1_000, | ||||||
|  |     violation: 2_000, | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   def local? |   def local? | ||||||
|     false # Force uri_for to use uri attribute |     false # Force uri_for to use uri attribute | ||||||
| @ -47,13 +57,17 @@ class Report < ApplicationRecord | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def statuses |   def statuses | ||||||
|     Status.with_discarded.where(id: status_ids).includes(:account, :media_attachments, :mentions) |     Status.with_discarded.where(id: status_ids) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def media_attachments |   def media_attachments | ||||||
|     MediaAttachment.where(status_id: status_ids) |     MediaAttachment.where(status_id: status_ids) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def rules | ||||||
|  |     Rule.with_discarded.where(id: rule_ids) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def assign_to_self!(current_account) |   def assign_to_self!(current_account) | ||||||
|     update!(assigned_account_id: current_account.id) |     update!(assigned_account_id: current_account.id) | ||||||
|   end |   end | ||||||
| @ -63,22 +77,19 @@ class Report < ApplicationRecord | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def resolve!(acting_account) |   def resolve!(acting_account) | ||||||
|     if account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted] |     update!(action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id) | ||||||
|       # This is an automated report and it is being dismissed, so it's |  | ||||||
|       # a false positive, in which case update the account's trust level |  | ||||||
|       # to prevent further spam checks |  | ||||||
| 
 |  | ||||||
|       target_account.update(trust_level: Account::TRUST_LEVELS[:trusted]) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] } |  | ||||||
|     update!(action_taken: true, action_taken_by_account_id: acting_account.id) |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def unresolve! |   def unresolve! | ||||||
|     update!(action_taken: false, action_taken_by_account_id: nil) |     update!(action_taken_at: nil, action_taken_by_account_id: nil) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def action_taken? | ||||||
|  |     action_taken_at.present? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   alias action_taken action_taken? | ||||||
|  | 
 | ||||||
|   def unresolved? |   def unresolved? | ||||||
|     !action_taken? |     !action_taken? | ||||||
|   end |   end | ||||||
| @ -88,29 +99,24 @@ class Report < ApplicationRecord | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def history |   def history | ||||||
|     time_range = created_at..updated_at |     subquery = [ | ||||||
| 
 |  | ||||||
|     sql = [ |  | ||||||
|       Admin::ActionLog.where( |       Admin::ActionLog.where( | ||||||
|         target_type: 'Report', |         target_type: 'Report', | ||||||
|         target_id: id, |         target_id: id | ||||||
|         created_at: time_range |       ).unscope(:order).arel, | ||||||
|       ).unscope(:order), |  | ||||||
| 
 | 
 | ||||||
|       Admin::ActionLog.where( |       Admin::ActionLog.where( | ||||||
|         target_type: 'Account', |         target_type: 'Account', | ||||||
|         target_id: target_account_id, |         target_id: target_account_id | ||||||
|         created_at: time_range |       ).unscope(:order).arel, | ||||||
|       ).unscope(:order), |  | ||||||
| 
 | 
 | ||||||
|       Admin::ActionLog.where( |       Admin::ActionLog.where( | ||||||
|         target_type: 'Status', |         target_type: 'Status', | ||||||
|         target_id: status_ids, |         target_id: status_ids | ||||||
|         created_at: time_range |       ).unscope(:order).arel, | ||||||
|       ).unscope(:order), |     ].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) } | ||||||
|     ].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ') |  | ||||||
| 
 | 
 | ||||||
|     Admin::ActionLog.from("(#{sql}) AS admin_action_logs") |     Admin::ActionLog.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table)) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def set_uri |   def set_uri | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ class ReportFilter | |||||||
|     scope = Report.unresolved |     scope = Report.unresolved | ||||||
| 
 | 
 | ||||||
|     params.each do |key, value| |     params.each do |key, value| | ||||||
|       scope = scope.merge scope_for(key, value) |       scope = scope.merge scope_for(key, value), rewhere: true | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     scope |     scope | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class REST::Admin::ReportSerializer < ActiveModel::Serializer | class REST::Admin::ReportSerializer < ActiveModel::Serializer | ||||||
|   attributes :id, :action_taken, :comment, :created_at, :updated_at |   attributes :id, :action_taken, :category, :comment, :created_at, :updated_at | ||||||
| 
 | 
 | ||||||
|   has_one :account, serializer: REST::Admin::AccountSerializer |   has_one :account, serializer: REST::Admin::AccountSerializer | ||||||
|   has_one :target_account, serializer: REST::Admin::AccountSerializer |   has_one :target_account, serializer: REST::Admin::AccountSerializer | ||||||
| @ -9,8 +9,13 @@ class REST::Admin::ReportSerializer < ActiveModel::Serializer | |||||||
|   has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer |   has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer | ||||||
| 
 | 
 | ||||||
|   has_many :statuses, serializer: REST::StatusSerializer |   has_many :statuses, serializer: REST::StatusSerializer | ||||||
|  |   has_many :rules, serializer: REST::RuleSerializer | ||||||
| 
 | 
 | ||||||
|   def id |   def id | ||||||
|     object.id.to_s |     object.id.to_s | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def statuses | ||||||
|  |     object.statuses.with_includes | ||||||
|  |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -48,6 +48,6 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def local_follower |   def local_follower | ||||||
|     @local_follower ||= account.followers.local.without_suspended.first |     @local_follower ||= @account.followers.local.without_suspended.first | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ class RemoveStatusService < BaseService | |||||||
|   # @param   [Hash] options |   # @param   [Hash] options | ||||||
|   # @option  [Boolean] :redraft |   # @option  [Boolean] :redraft | ||||||
|   # @option  [Boolean] :immediate |   # @option  [Boolean] :immediate | ||||||
|  |   # @option  [Boolean] :preserve | ||||||
|   # @option  [Boolean] :original_removed |   # @option  [Boolean] :original_removed | ||||||
|   def call(status, **options) |   def call(status, **options) | ||||||
|     @payload  = Oj.dump(event: :delete, payload: status.id.to_s) |     @payload  = Oj.dump(event: :delete, payload: status.id.to_s) | ||||||
| @ -44,7 +45,7 @@ class RemoveStatusService < BaseService | |||||||
|           remove_media |           remove_media | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         @status.destroy! if @options[:immediate] || !@status.reported? |         @status.destroy! if permanently? | ||||||
|       else |       else | ||||||
|         raise Mastodon::RaceConditionError |         raise Mastodon::RaceConditionError | ||||||
|       end |       end | ||||||
| @ -143,11 +144,15 @@ class RemoveStatusService < BaseService | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def remove_media |   def remove_media | ||||||
|     return if @options[:redraft] || (!@options[:immediate] && @status.reported?) |     return if @options[:redraft] || !permanently? | ||||||
| 
 | 
 | ||||||
|     @status.media_attachments.destroy_all |     @status.media_attachments.destroy_all | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def permanently? | ||||||
|  |     @options[:immediate] || !(@options[:preserve] || @status.reported?) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def lock_options |   def lock_options | ||||||
|     { redis: Redis.current, key: "distribute:#{@status.id}", autorelease: 5.minutes.seconds } |     { redis: Redis.current, key: "distribute:#{@status.id}", autorelease: 5.minutes.seconds } | ||||||
|   end |   end | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ | |||||||
|   %div.muted-hint.center-text |   %div.muted-hint.center-text | ||||||
|     = t 'admin.action_logs.empty' |     = t 'admin.action_logs.empty' | ||||||
| - else | - else | ||||||
|   .announcements-list |   .report-notes | ||||||
|     = render partial: 'action_log', collection: @action_logs |     = render partial: 'action_log', collection: @action_logs | ||||||
| 
 | 
 | ||||||
| = paginate @action_logs | = paginate @action_logs | ||||||
|  | |||||||
| @ -1,7 +1,18 @@ | |||||||
| .speech-bubble | .report-notes__item | ||||||
|   .speech-bubble__bubble |   = image_tag report_note.account.avatar.url, class: 'report-notes__item__avatar' | ||||||
|  | 
 | ||||||
|  |   .report-notes__item__header | ||||||
|  |     %span.username | ||||||
|  |       = link_to display_name(report_note.account), admin_account_path(report_note.account_id) | ||||||
|  |     %time{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } | ||||||
|  |       - if report_note.created_at.today? | ||||||
|  |         = t('admin.report_notes.today_at', time: l(report_note.created_at, format: :time)) | ||||||
|  |       - else | ||||||
|  |         = l report_note.created_at.to_date | ||||||
|  | 
 | ||||||
|  |   .report-notes__item__content | ||||||
|     = simple_format(h(report_note.content)) |     = simple_format(h(report_note.content)) | ||||||
|   .speech-bubble__owner | 
 | ||||||
|     = admin_account_link_to report_note.account |   - if can?(:destroy, report_note) | ||||||
|     %time.formatted{ datetime: report_note.created_at.iso8601 }= l report_note.created_at |     .report-notes__item__actions | ||||||
|     = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) |       = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete | ||||||
|  | |||||||
| @ -1,6 +0,0 @@ | |||||||
| .speech-bubble.positive |  | ||||||
|   .speech-bubble__bubble |  | ||||||
|     = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}_html", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')) |  | ||||||
|   .speech-bubble__owner |  | ||||||
|     = admin_account_link_to(action_log.account) |  | ||||||
|     %time.formatted{ datetime: action_log.created_at.iso8601 }= l action_log.created_at |  | ||||||
| @ -22,6 +22,9 @@ | |||||||
|         = react_component :media_gallery, height: 343, sensitive: status.proper.sensitive?, visible: false, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } |         = react_component :media_gallery, height: 343, sensitive: status.proper.sensitive?, visible: false, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } | ||||||
| 
 | 
 | ||||||
|     .detailed-status__meta |     .detailed-status__meta | ||||||
|  |       - if status.application | ||||||
|  |         = status.application.name | ||||||
|  |         · | ||||||
|       = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do |       = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do | ||||||
|         %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) |         %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) | ||||||
|       - if status.discarded? |       - if status.discarded? | ||||||
|  | |||||||
| @ -7,122 +7,199 @@ | |||||||
|   - else |   - else | ||||||
|     = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button' |     = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button' | ||||||
| 
 | 
 | ||||||
| .table-wrapper | .report-header | ||||||
|   %table.table.inline-table |   .report-header__card | ||||||
|     %tbody |     .account-card | ||||||
|       %tr |       .account-card__header | ||||||
|         %th= t('admin.reports.reported_account') |         = image_tag @report.target_account.header.url, alt: '' | ||||||
|         %td= admin_account_link_to @report.target_account |       .account-card__title | ||||||
|         %td= table_link_to 'flag', t('admin.reports.account.reports', count: @report.target_account.targeted_reports.count), admin_reports_path(target_account_id: @report.target_account.id) |         .account-card__title__avatar | ||||||
|         %td= table_link_to 'file', t('admin.reports.account.notes', count: @report.target_account.targeted_moderation_notes.count), admin_reports_path(target_account_id: @report.target_account.id) |           = image_tag @report.target_account.avatar.url, alt: '' | ||||||
|       %tr |         .display-name | ||||||
|         %th= t('admin.reports.reported_by') |           %bdi | ||||||
|  |             %strong.emojify.p-name= display_name(@report.target_account, custom_emojify: true) | ||||||
|  |           %span | ||||||
|  |             = acct(@report.target_account) | ||||||
|  |             = fa_icon('lock') if @report.target_account.locked? | ||||||
|  |       - if @report.target_account.note.present? | ||||||
|  |         .account-card__bio.emojify | ||||||
|  |           = Formatter.instance.simplified_format(@report.target_account, custom_emojify: true) | ||||||
|  |       .account-card__actions | ||||||
|  |         .account-card__counters | ||||||
|  |           .account-card__counters__item | ||||||
|  |             = friendly_number_to_human @report.target_account.statuses_count | ||||||
|  |             %small= t('accounts.posts', count: @report.target_account.statuses_count).downcase | ||||||
|  |           .account-card__counters__item | ||||||
|  |             = friendly_number_to_human @report.target_account.followers_count | ||||||
|  |             %small= t('accounts.followers', count: @report.target_account.followers_count).downcase | ||||||
|  |           .account-card__counters__item | ||||||
|  |             = friendly_number_to_human @report.target_account.following_count | ||||||
|  |             %small= t('accounts.following', count: @report.target_account.following_count).downcase | ||||||
|  |         .account-card__actions__button | ||||||
|  |           = link_to t('admin.reports.view_profile'), admin_account_path(@report.target_account_id), class: 'button' | ||||||
|  |     .report-header__details.report-header__details--horizontal | ||||||
|  |       .report-header__details__item | ||||||
|  |         .report-header__details__item__header | ||||||
|  |           %strong= t('admin.accounts.joined') | ||||||
|  |         .report-header__details__item__content | ||||||
|  |           %time.time-ago{ datetime: @report.target_account.created_at.iso8601, title: l(@report.target_account.created_at) }= l @report.target_account.created_at | ||||||
|  |       .report-header__details__item | ||||||
|  |         .report-header__details__item__header | ||||||
|  |           %strong= t('accounts.last_active') | ||||||
|  |         .report-header__details__item__content | ||||||
|  |           - if @report.target_account.last_status_at.present? | ||||||
|  |             %time.time-ago{ datetime: @report.target_account.last_status_at.to_date.iso8601, title: l(@report.target_account.last_status_at.to_date) }= l @report.target_account.last_status_at | ||||||
|  |       .report-header__details__item | ||||||
|  |         .report-header__details__item__header | ||||||
|  |           %strong= t('admin.accounts.strikes') | ||||||
|  |         .report-header__details__item__content | ||||||
|  |           = @report.target_account.strikes.count | ||||||
|  | 
 | ||||||
|  |   .report-header__details | ||||||
|  |     .report-header__details__item | ||||||
|  |       .report-header__details__item__header | ||||||
|  |         %strong= t('admin.reports.created_at') | ||||||
|  |       .report-header__details__item__content | ||||||
|  |         %time.formatted{ datetime: @report.created_at.iso8601 } | ||||||
|  |     .report-header__details__item | ||||||
|  |       .report-header__details__item__header | ||||||
|  |         %strong= t('admin.reports.reported_by') | ||||||
|  |       .report-header__details__item__content | ||||||
|         - if @report.account.instance_actor? |         - if @report.account.instance_actor? | ||||||
|           %td{ colspan: 3 }= site_hostname |           = site_hostname | ||||||
|         - elsif @report.account.local? |         - elsif @report.account.local? | ||||||
|           %td= admin_account_link_to @report.account |           = admin_account_link_to @report.account | ||||||
|           %td= table_link_to 'flag', t('admin.reports.account.reports', count: @report.account.targeted_reports.count), admin_reports_path(target_account_id: @report.account.id) |  | ||||||
|           %td= table_link_to 'file', t('admin.reports.account.notes', count: @report.account.targeted_moderation_notes.count), admin_reports_path(target_account_id: @report.account.id) |  | ||||||
|         - else |         - else | ||||||
|           %td{ colspan: 3 }= @report.account.domain |           = @report.account.domain | ||||||
|       %tr |     .report-header__details__item | ||||||
|         %th= t('admin.reports.created_at') |       .report-header__details__item__header | ||||||
|         %td{ colspan: 3 } |         %strong= t('admin.reports.status') | ||||||
|           %time.formatted{ datetime: @report.created_at.iso8601 } |       .report-header__details__item__content | ||||||
|       %tr |         - if @report.action_taken? | ||||||
|         %th= t('admin.reports.updated_at') |           = t('admin.reports.resolved') | ||||||
|         %td{ colspan: 3 } |         - else | ||||||
|           %time.formatted{ datetime: @report.updated_at.iso8601 } |           = t('admin.reports.unresolved') | ||||||
|       %tr |     - unless @report.target_account.local? | ||||||
|         %th= t('admin.reports.status') |       .report-header__details__item | ||||||
|         %td |         .report-header__details__item__header | ||||||
|           - if @report.action_taken? |           %strong= t('admin.reports.forwarded') | ||||||
|             = t('admin.reports.resolved') |         .report-header__details__item__content | ||||||
|  |           - if @report.forwarded? | ||||||
|  |             = t('simple_form.yes') | ||||||
|           - else |           - else | ||||||
|             = t('admin.reports.unresolved') |             = t('simple_form.no') | ||||||
|         %td{ colspan: 2 } |     - if !@report.action_taken_by_account.nil? | ||||||
|           - if @report.action_taken? |       .report-header__details__item | ||||||
|             = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put |         .report-header__details__item__header | ||||||
|       - unless @report.target_account.local? |           %strong= t('admin.reports.action_taken_by') | ||||||
|         %tr |         .report-header__details__item__content | ||||||
|           %th= t('admin.reports.forwarded') |           = admin_account_link_to @report.action_taken_by_account | ||||||
|           %td{ colspan: 3 } |  | ||||||
|             - if @report.forwarded.nil? |  | ||||||
|               \- |  | ||||||
|             - elsif @report.forwarded? |  | ||||||
|               = t('simple_form.yes') |  | ||||||
|             - else |  | ||||||
|               = t('simple_form.no') |  | ||||||
|       - if !@report.action_taken_by_account.nil? |  | ||||||
|         %tr |  | ||||||
|           %th= t('admin.reports.action_taken_by') |  | ||||||
|           %td{ colspan: 3 } |  | ||||||
|             = admin_account_link_to @report.action_taken_by_account |  | ||||||
|       - else |  | ||||||
|         %tr |  | ||||||
|           %th= t('admin.reports.assigned') |  | ||||||
|           %td |  | ||||||
|             - if @report.assigned_account.nil? |  | ||||||
|               \- |  | ||||||
|             - else |  | ||||||
|               = admin_account_link_to @report.assigned_account |  | ||||||
|           %td |  | ||||||
|             - if @report.assigned_account != current_user.account |  | ||||||
|               = table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post |  | ||||||
|           %td |  | ||||||
|             - if !@report.assigned_account.nil? |  | ||||||
|               = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post |  | ||||||
| 
 |  | ||||||
| %hr.spacer |  | ||||||
| 
 |  | ||||||
| %div.action-buttons |  | ||||||
|   %div |  | ||||||
| 
 |  | ||||||
|   - if @report.unresolved? |  | ||||||
|     %div |  | ||||||
|       - if @report.target_account.local? |  | ||||||
|         = link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button' |  | ||||||
|         = link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive' |  | ||||||
|       = link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive' |  | ||||||
|       = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive' |  | ||||||
| 
 |  | ||||||
| %hr.spacer |  | ||||||
| 
 |  | ||||||
| .speech-bubble |  | ||||||
|   .speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none')) |  | ||||||
|   .speech-bubble__owner |  | ||||||
|     - if @report.account.local? |  | ||||||
|       = admin_account_link_to @report.account |  | ||||||
|     - else |     - else | ||||||
|       = @report.account.domain |       .report-header__details__item | ||||||
|       %br/ |         .report-header__details__item__header | ||||||
|     %time.formatted{ datetime: @report.created_at.iso8601 } |           %strong= t('admin.reports.assigned') | ||||||
|  |         .report-header__details__item__content | ||||||
|  |           - if @report.assigned_account.nil? | ||||||
|  |             = t 'admin.reports.no_one_assigned' | ||||||
|  |           - else | ||||||
|  |             = admin_account_link_to @report.assigned_account | ||||||
|  |           — | ||||||
|  |           - if @report.assigned_account != current_user.account | ||||||
|  |             = table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post | ||||||
|  |           - elsif !@report.assigned_account.nil? | ||||||
|  |             = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post | ||||||
| 
 | 
 | ||||||
| - unless @report.statuses.empty? | %hr.spacer | ||||||
|   %hr.spacer/ |  | ||||||
| 
 | 
 | ||||||
|   = form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f| | %h3= t 'admin.reports.category' | ||||||
|     .batch-table | 
 | ||||||
|       .batch-table__toolbar | %p= t 'admin.reports.category_description_html' | ||||||
|         %label.batch-table__toolbar__select.batch-checkbox-all | 
 | ||||||
|           = check_box_tag :batch_checkbox_all, nil, false | = react_admin_component :report_reason_selector, id: @report.id, category: @report.category, rule_ids: @report.rule_ids&.map(&:to_s), disabled: @report.action_taken? | ||||||
|         .batch-table__toolbar__actions | 
 | ||||||
|           = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } | - if @report.comment.present? | ||||||
|           = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |   %p= t('admin.reports.comment_description_html', name: content_tag(:strong, @report.account.username, class: 'username')) | ||||||
|           = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } | 
 | ||||||
|       .batch-table__body |   .report-notes__item | ||||||
|         = render partial: 'admin/reports/status', collection: @report.statuses, locals: { f: f } |     = image_tag @report.account.avatar.url, class: 'report-notes__item__avatar' | ||||||
|  | 
 | ||||||
|  |     .report-notes__item__header | ||||||
|  |       %span.username | ||||||
|  |         = link_to display_name(@report.account), admin_account_path(@report.account_id) | ||||||
|  |       %time{ datetime: @report.created_at.iso8601, title: l(@report.created_at) } | ||||||
|  |         - if @report.created_at.today? | ||||||
|  |           = t('admin.report_notes.today_at', time: l(@report.created_at, format: :time)) | ||||||
|  |         - else | ||||||
|  |           = l @report.created_at.to_date | ||||||
|  | 
 | ||||||
|  |     .report-notes__item__content | ||||||
|  |       = simple_format(h(@report.comment)) | ||||||
| 
 | 
 | ||||||
| %hr.spacer/ | %hr.spacer/ | ||||||
| 
 | 
 | ||||||
| - @report_notes.each do |item| | %h3= t 'admin.reports.statuses' | ||||||
|   - if item.is_a?(Admin::ActionLog) | 
 | ||||||
|     = render partial: 'action_log', locals: { action_log: item } | %p | ||||||
|   - else |   = t 'admin.reports.statuses_description_html' | ||||||
|     = render item |   — | ||||||
|  |   = link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), admin_account_statuses_path(@report.target_account_id, report_id: @report.id), class: 'table-action-link' | ||||||
|  | 
 | ||||||
|  | = form_for(@form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id)) do |f| | ||||||
|  |   .batch-table | ||||||
|  |     .batch-table__toolbar | ||||||
|  |       %label.batch-table__toolbar__select.batch-checkbox-all | ||||||
|  |         = check_box_tag :batch_checkbox_all, nil, false | ||||||
|  |       .batch-table__toolbar__actions | ||||||
|  |         - if !@statuses.empty? && @report.unresolved? | ||||||
|  |           = f.button safe_join([fa_icon('times'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit | ||||||
|  |           = f.button safe_join([fa_icon('trash'), t('admin.reports.delete_and_resolve')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } | ||||||
|  |         - else | ||||||
|  |     .batch-table__body | ||||||
|  |       - if @statuses.empty? | ||||||
|  |         = nothing_here 'nothing-here--under-tabs' | ||||||
|  |       - else | ||||||
|  |         = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } | ||||||
|  | 
 | ||||||
|  | - if @report.unresolved? | ||||||
|  |   %hr.spacer/ | ||||||
|  | 
 | ||||||
|  |   %p= t 'admin.reports.actions_description_html' | ||||||
|  | 
 | ||||||
|  |   .report-actions | ||||||
|  |     .report-actions__item | ||||||
|  |       .report-actions__item__button | ||||||
|  |         = link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive' | ||||||
|  |       .report-actions__item__description | ||||||
|  |         = t('admin.reports.actions.silence_description_html') | ||||||
|  |     .report-actions__item | ||||||
|  |       .report-actions__item__button | ||||||
|  |         = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, report_id: @report.id, type: 'suspend'), class: 'button button--destructive' | ||||||
|  |       .report-actions__item__description | ||||||
|  |         = t('admin.reports.actions.suspend_description_html') | ||||||
|  |     .report-actions__item | ||||||
|  |       .report-actions__item__button | ||||||
|  |         = link_to t('admin.accounts.custom'), new_admin_account_action_path(@report.target_account_id, report_id: @report.id), class: 'button' | ||||||
|  |       .report-actions__item__description | ||||||
|  |         = t('admin.reports.actions.other_description_html') | ||||||
|  | 
 | ||||||
|  | - unless @action_logs.empty? | ||||||
|  |   %hr.spacer/ | ||||||
|  | 
 | ||||||
|  |   %h3= t 'admin.reports.action_log' | ||||||
|  | 
 | ||||||
|  |   .report-notes | ||||||
|  |     = render @action_logs | ||||||
|  | 
 | ||||||
|  | %hr.spacer/ | ||||||
|  | 
 | ||||||
|  | %h3= t 'admin.reports.notes.title' | ||||||
|  | 
 | ||||||
|  | %p= t 'admin.reports.notes_description_html' | ||||||
|  | 
 | ||||||
|  | .report-notes | ||||||
|  |   = render @report_notes | ||||||
| 
 | 
 | ||||||
| = simple_form_for @report_note, url: admin_report_notes_path do |f| | = simple_form_for @report_note, url: admin_report_notes_path do |f| | ||||||
|   = render 'shared/error_messages', object: @report_note |  | ||||||
|   = f.input :report_id, as: :hidden |   = f.input :report_id, as: :hidden | ||||||
| 
 | 
 | ||||||
|   .field-group |   .field-group | ||||||
|  | |||||||
| @ -7,28 +7,37 @@ | |||||||
|   .filter-subset |   .filter-subset | ||||||
|     %strong= t('admin.statuses.media.title') |     %strong= t('admin.statuses.media.title') | ||||||
|     %ul |     %ul | ||||||
|       %li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected' |       %li= filter_link_to t('generic.all'), media: nil, id: nil | ||||||
|       %li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected' |       %li= filter_link_to t('admin.statuses.with_media'), media: '1' | ||||||
|   .back-link |   .back-link | ||||||
|     = link_to admin_account_path(@account.id) do |     - if params[:report_id] | ||||||
|       = fa_icon 'chevron-left fw' |       = link_to admin_report_path(params[:report_id].to_i) do | ||||||
|       = t('admin.statuses.back_to_account') |         = fa_icon 'chevron-left fw' | ||||||
|  |         = t('admin.statuses.back_to_report') | ||||||
|  |     - else | ||||||
|  |       = link_to admin_account_path(@account.id) do | ||||||
|  |         = fa_icon 'chevron-left fw' | ||||||
|  |         = t('admin.statuses.back_to_account') | ||||||
| 
 | 
 | ||||||
| %hr.spacer/ | %hr.spacer/ | ||||||
| 
 | 
 | ||||||
| = form_for(@form, url: admin_account_statuses_path(@account.id)) do |f| | = form_for(@status_batch_action, url: batch_admin_account_statuses_path(@account.id)) do |f| | ||||||
|   = hidden_field_tag :page, params[:page] |   = hidden_field_tag :page, params[:page] || 1 | ||||||
|   = hidden_field_tag :media, params[:media] | 
 | ||||||
|  |   - Admin::StatusFilter::KEYS.each do |key| | ||||||
|  |     = hidden_field_tag key, params[key] if params[key].present? | ||||||
| 
 | 
 | ||||||
|   .batch-table |   .batch-table | ||||||
|     .batch-table__toolbar |     .batch-table__toolbar | ||||||
|       %label.batch-table__toolbar__select.batch-checkbox-all |       %label.batch-table__toolbar__select.batch-checkbox-all | ||||||
|         = check_box_tag :batch_checkbox_all, nil, false |         = check_box_tag :batch_checkbox_all, nil, false | ||||||
|       .batch-table__toolbar__actions |       .batch-table__toolbar__actions | ||||||
|         = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |         - unless @statuses.empty? | ||||||
|         = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |           = f.button safe_join([fa_icon('flag'), t('admin.statuses.batch.report')]), name: :report, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } | ||||||
|         = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |  | ||||||
|     .batch-table__body |     .batch-table__body | ||||||
|       = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } |       - if @statuses.empty? | ||||||
|  |         = nothing_here 'nothing-here--under-tabs' | ||||||
|  |       - else | ||||||
|  |         = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } | ||||||
| 
 | 
 | ||||||
| = paginate @statuses | = paginate @statuses | ||||||
|  | |||||||
| @ -1,27 +0,0 @@ | |||||||
| - content_for :page_title do |  | ||||||
|   = t('admin.statuses.title') |  | ||||||
|   \- |  | ||||||
|   = "@#{@account.acct}" |  | ||||||
| 
 |  | ||||||
| .filters |  | ||||||
|   .back-link |  | ||||||
|     = link_to admin_account_path(@account.id) do |  | ||||||
|       %i.fa.fa-chevron-left.fa-fw |  | ||||||
|       = t('admin.statuses.back_to_account') |  | ||||||
| 
 |  | ||||||
| %hr.spacer/ |  | ||||||
| 
 |  | ||||||
| = form_for(@form, url: admin_account_statuses_path(@account.id)) do |f| |  | ||||||
|   = hidden_field_tag :page, params[:page] |  | ||||||
|   = hidden_field_tag :media, params[:media] |  | ||||||
| 
 |  | ||||||
|   .batch-table |  | ||||||
|     .batch-table__toolbar |  | ||||||
|       %label.batch-table__toolbar__select.batch-checkbox-all |  | ||||||
|         = check_box_tag :batch_checkbox_all, nil, false |  | ||||||
|       .batch-table__toolbar__actions |  | ||||||
|         = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |  | ||||||
|         = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |  | ||||||
|         = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } |  | ||||||
|     .batch-table__body |  | ||||||
|       = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } |  | ||||||
| @ -1,8 +1,8 @@ | |||||||
| <% if status.spoiler_text? %> | <% if status.spoiler_text? %> | ||||||
| <%= raw status.spoiler_text %> | > <%= raw word_wrap(status.spoiler_text, break_sequence: "\n> ") %> | ||||||
| ---- | > ---- | ||||||
| 
 | > | ||||||
| <% end %> | <% end %> | ||||||
| <%= raw Formatter.instance.plaintext(status) %> | > <%= raw word_wrap(Formatter.instance.plaintext(status), break_sequence: "\n> ") %> | ||||||
| 
 | 
 | ||||||
| <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %> | <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %> | ||||||
|  | |||||||
| @ -37,16 +37,26 @@ | |||||||
|                           %tr |                           %tr | ||||||
|                             %td.column-cell.text-center |                             %td.column-cell.text-center | ||||||
|                               - unless @warning.none_action? |                               - unless @warning.none_action? | ||||||
|                                 %p= t "user_mailer.warning.explanation.#{@warning.action}" |                                 %p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance | ||||||
| 
 | 
 | ||||||
|                               - unless @warning.text.blank? |                               - unless @warning.text.blank? | ||||||
|                                 = Formatter.instance.linkify(@warning.text) |                                 = Formatter.instance.linkify(@warning.text) | ||||||
| 
 | 
 | ||||||
|                               - if !@statuses.nil? && !@statuses.empty? |                               - if @warning.report && !@warning.report.other? | ||||||
|  |                                 %p | ||||||
|  |                                   %strong= t('user_mailer.warning.reason') | ||||||
|  |                                   = t("user_mailer.warning.categories.#{@warning.report.category}") | ||||||
|  | 
 | ||||||
|  |                                 - if @warning.report.violation? && @warning.report.rule_ids.present? | ||||||
|  |                                   %ul.rules-list | ||||||
|  |                                     - @warning.report.rules.each do |rule| | ||||||
|  |                                       %li= rule.text | ||||||
|  | 
 | ||||||
|  |                               - unless @statuses.empty? | ||||||
|                                 %p |                                 %p | ||||||
|                                   %strong= t('user_mailer.warning.statuses') |                                   %strong= t('user_mailer.warning.statuses') | ||||||
| 
 | 
 | ||||||
| - if !@statuses.nil? && !@statuses.empty? | - unless @statuses.empty? | ||||||
|   - @statuses.each_with_index do |status, i| |   - @statuses.each_with_index do |status, i| | ||||||
|     = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true |     = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,11 +3,24 @@ | |||||||
| === | === | ||||||
| 
 | 
 | ||||||
| <% unless @warning.none_action? %> | <% unless @warning.none_action? %> | ||||||
| <%= t "user_mailer.warning.explanation.#{@warning.action}" %> | <%= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance %> | ||||||
| 
 | 
 | ||||||
| <% end %> | <% end %> | ||||||
|  | <% if @warning.text.present? %> | ||||||
| <%= @warning.text %> | <%= @warning.text %> | ||||||
| <% if !@statuses.nil? && !@statuses.empty? %> | 
 | ||||||
|  | <% end %> | ||||||
|  | <% if @warning.report && !@warning.report.other? %> | ||||||
|  | **<%= t('user_mailer.warning.reason') %>** <%= t("user_mailer.warning.categories.#{@warning.report.category}") %> | ||||||
|  | 
 | ||||||
|  | <% if @warning.report.violation? && @warning.report.rule_ids.present? %> | ||||||
|  | <% @warning.report.rules.each do |rule| %> | ||||||
|  | - <%= rule.text %> | ||||||
|  | <% end %> | ||||||
|  | 
 | ||||||
|  | <% end %> | ||||||
|  | <% end %> | ||||||
|  | <% if !@statuses.empty? %> | ||||||
| <%= t('user_mailer.warning.statuses') %> | <%= t('user_mailer.warning.statuses') %> | ||||||
| 
 | 
 | ||||||
| <% @statuses.each do |status| %> | <% @statuses.each do |status| %> | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ class Scheduler::UserCleanupScheduler | |||||||
|   def perform |   def perform | ||||||
|     clean_unconfirmed_accounts! |     clean_unconfirmed_accounts! | ||||||
|     clean_suspended_accounts! |     clean_suspended_accounts! | ||||||
|  |     clean_discarded_statuses! | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
| @ -24,4 +25,12 @@ class Scheduler::UserCleanupScheduler | |||||||
|       Admin::AccountDeletionWorker.perform_async(deletion_request.account_id) |       Admin::AccountDeletionWorker.perform_async(deletion_request.account_id) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def clean_discarded_statuses! | ||||||
|  |     Status.discarded.where('deleted_at <= ?', 30.days.ago).find_in_batches do |statuses| | ||||||
|  |       RemovalWorker.push_bulk(statuses) do |status| | ||||||
|  |         [status.id, { immediate: true }] | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -113,6 +113,7 @@ en: | |||||||
|       confirm: Confirm |       confirm: Confirm | ||||||
|       confirmed: Confirmed |       confirmed: Confirmed | ||||||
|       confirming: Confirming |       confirming: Confirming | ||||||
|  |       custom: Custom | ||||||
|       delete: Delete data |       delete: Delete data | ||||||
|       deleted: Deleted |       deleted: Deleted | ||||||
|       demote: Demote |       demote: Demote | ||||||
| @ -203,6 +204,7 @@ en: | |||||||
|       silence: Limit |       silence: Limit | ||||||
|       silenced: Limited |       silenced: Limited | ||||||
|       statuses: Posts |       statuses: Posts | ||||||
|  |       strikes: Previous strikes | ||||||
|       subscribe: Subscribe |       subscribe: Subscribe | ||||||
|       suspended: Suspended |       suspended: Suspended | ||||||
|       suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had. |       suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had. | ||||||
| @ -549,32 +551,44 @@ en: | |||||||
|     report_notes: |     report_notes: | ||||||
|       created_msg: Report note successfully created! |       created_msg: Report note successfully created! | ||||||
|       destroyed_msg: Report note successfully deleted! |       destroyed_msg: Report note successfully deleted! | ||||||
|  |       today_at: Today at %{time} | ||||||
|     reports: |     reports: | ||||||
|       account: |       account: | ||||||
|         notes: |         notes: | ||||||
|           one: "%{count} note" |           one: "%{count} note" | ||||||
|           other: "%{count} notes" |           other: "%{count} notes" | ||||||
|         reports: |       action_log: Audit log | ||||||
|           one: "%{count} report" |  | ||||||
|           other: "%{count} reports" |  | ||||||
|       action_taken_by: Action taken by |       action_taken_by: Action taken by | ||||||
|  |       actions: | ||||||
|  |         other_description_html: See more options for controlling the account's behaviour and customize communication to the reported account. | ||||||
|  |         silence_description_html: The profile will be visible only to those who already follow it or manually look it up, severely limiting its reach. Can always be reverted. | ||||||
|  |         suspend_description_html: The profile and all its contents will become inaccessible until it is eventually deleted. Interacting with the account will be impossible. Reversible within 30 days. | ||||||
|  |       actions_description_html: 'If removing the offending content above is insufficient:' | ||||||
|  |       add_to_report: Add more to report | ||||||
|       are_you_sure: Are you sure? |       are_you_sure: Are you sure? | ||||||
|       assign_to_self: Assign to me |       assign_to_self: Assign to me | ||||||
|       assigned: Assigned moderator |       assigned: Assigned moderator | ||||||
|       by_target_domain: Domain of reported account |       by_target_domain: Domain of reported account | ||||||
|  |       category: Category | ||||||
|  |       category_description_html: The reason this account and/or content was reported will be cited in communication with the reported account | ||||||
|       comment: |       comment: | ||||||
|         none: None |         none: None | ||||||
|  |       comment_description_html: 'To provide more information, %{name} wrote:' | ||||||
|       created_at: Reported |       created_at: Reported | ||||||
|  |       delete_and_resolve: Delete and resolve | ||||||
|       forwarded: Forwarded |       forwarded: Forwarded | ||||||
|       forwarded_to: Forwarded to %{domain} |       forwarded_to: Forwarded to %{domain} | ||||||
|       mark_as_resolved: Mark as resolved |       mark_as_resolved: Mark as resolved | ||||||
|       mark_as_unresolved: Mark as unresolved |       mark_as_unresolved: Mark as unresolved | ||||||
|  |       no_one_assigned: No one | ||||||
|       notes: |       notes: | ||||||
|         create: Add note |         create: Add note | ||||||
|         create_and_resolve: Resolve with note |         create_and_resolve: Resolve with note | ||||||
|         create_and_unresolve: Reopen with note |         create_and_unresolve: Reopen with note | ||||||
|         delete: Delete |         delete: Delete | ||||||
|         placeholder: Describe what actions have been taken, or any other related updates... |         placeholder: Describe what actions have been taken, or any other related updates... | ||||||
|  |         title: Notes | ||||||
|  |       notes_description_html: View and leave notes to other moderators and your future self | ||||||
|       reopen: Reopen report |       reopen: Reopen report | ||||||
|       report: 'Report #%{id}' |       report: 'Report #%{id}' | ||||||
|       reported_account: Reported account |       reported_account: Reported account | ||||||
| @ -582,11 +596,14 @@ en: | |||||||
|       resolved: Resolved |       resolved: Resolved | ||||||
|       resolved_msg: Report successfully resolved! |       resolved_msg: Report successfully resolved! | ||||||
|       status: Status |       status: Status | ||||||
|  |       statuses: Reported content | ||||||
|  |       statuses_description_html: Offending content will be cited in communication with the reported account | ||||||
|       target_origin: Origin of reported account |       target_origin: Origin of reported account | ||||||
|       title: Reports |       title: Reports | ||||||
|       unassign: Unassign |       unassign: Unassign | ||||||
|       unresolved: Unresolved |       unresolved: Unresolved | ||||||
|       updated_at: Updated |       updated_at: Updated | ||||||
|  |       view_profile: View profile | ||||||
|     rules: |     rules: | ||||||
|       add_new: Add rule |       add_new: Add rule | ||||||
|       delete: Delete |       delete: Delete | ||||||
| @ -688,15 +705,13 @@ en: | |||||||
|       destroyed_msg: Site upload successfully deleted! |       destroyed_msg: Site upload successfully deleted! | ||||||
|     statuses: |     statuses: | ||||||
|       back_to_account: Back to account page |       back_to_account: Back to account page | ||||||
|  |       back_to_report: Back to report page | ||||||
|       batch: |       batch: | ||||||
|         delete: Delete |         remove_from_report: Remove from report | ||||||
|         nsfw_off: Mark as not sensitive |         report: Report | ||||||
|         nsfw_on: Mark as sensitive |  | ||||||
|       deleted: Deleted |       deleted: Deleted | ||||||
|       failed_to_execute: Failed to execute |  | ||||||
|       media: |       media: | ||||||
|         title: Media |         title: Media | ||||||
|       no_media: No media |  | ||||||
|       no_status_selected: No posts were changed as none were selected |       no_status_selected: No posts were changed as none were selected | ||||||
|       title: Account posts |       title: Account posts | ||||||
|       with_media: With media |       with_media: With media | ||||||
| @ -1457,6 +1472,7 @@ en: | |||||||
|     formats: |     formats: | ||||||
|       default: "%b %d, %Y, %H:%M" |       default: "%b %d, %Y, %H:%M" | ||||||
|       month: "%b %Y" |       month: "%b %Y" | ||||||
|  |       time: "%H:%M" | ||||||
|   two_factor_authentication: |   two_factor_authentication: | ||||||
|     add: Add |     add: Add | ||||||
|     disable: Disable 2FA |     disable: Disable 2FA | ||||||
| @ -1484,24 +1500,31 @@ en: | |||||||
|       subject: Please confirm attempted sign in |       subject: Please confirm attempted sign in | ||||||
|       title: Sign in attempt |       title: Sign in attempt | ||||||
|     warning: |     warning: | ||||||
|  |       categories: | ||||||
|  |         spam: Spam | ||||||
|  |         violation: Content violates the following community guidelines | ||||||
|       explanation: |       explanation: | ||||||
|         disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact. |         delete_statuses: Some of your posts have been found to violate one or more community guidelines and have been subsequently removed by the moderators of %{instance}. Future violations may result in harsher punitive actions against your account. | ||||||
|         sensitive: Your uploaded media files and linked media will be treated as sensitive. |         disable: You can no longer use your account, but your profile and other data remains intact. You can request a backup of your data, change account settings or delete your account. | ||||||
|         silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you. |         sensitive: From now on, all your uploaded media files will be marked as sensitive and hidden behind a click-through warning. | ||||||
|         suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension. |         silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various discovery features. However, others may still manually follow you. | ||||||
|       get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}. |         suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed in about 30 days, but we will retain some basic data to prevent you from evading the suspension. | ||||||
|  |       get_in_touch: If you believe this is an error, you can reply to this e-mail to get in touch with the staff of %{instance}. | ||||||
|  |       reason: 'Reason:' | ||||||
|       review_server_policies: Review server policies |       review_server_policies: Review server policies | ||||||
|       statuses: 'Specifically, for:' |       statuses: 'Posts that have been found in violation:' | ||||||
|       subject: |       subject: | ||||||
|  |         delete_statuses: Your posts on %{acct} have been removed | ||||||
|         disable: Your account %{acct} has been frozen |         disable: Your account %{acct} has been frozen | ||||||
|         none: Warning for %{acct} |         none: Warning for %{acct} | ||||||
|         sensitive: Your account %{acct} posting media has been marked as sensitive |         sensitive: Your media files on %{acct} will be marked as sensitive from now on | ||||||
|         silence: Your account %{acct} has been limited |         silence: Your account %{acct} has been limited | ||||||
|         suspend: Your account %{acct} has been suspended |         suspend: Your account %{acct} has been suspended | ||||||
|       title: |       title: | ||||||
|  |         delete_statuses: Posts removed | ||||||
|         disable: Account frozen |         disable: Account frozen | ||||||
|         none: Warning |         none: Warning | ||||||
|         sensitive: Your media has been marked as sensitive |         sensitive: Media hidden | ||||||
|         silence: Account limited |         silence: Account limited | ||||||
|         suspend: Account suspended |         suspend: Account suspended | ||||||
|     welcome: |     welcome: | ||||||
|  | |||||||
| @ -233,8 +233,6 @@ Rails.application.routes.draw do | |||||||
|         post :reopen |         post :reopen | ||||||
|         post :resolve |         post :resolve | ||||||
|       end |       end | ||||||
| 
 |  | ||||||
|       resources :reported_statuses, only: [:create] |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     resources :report_notes, only: [:create, :destroy] |     resources :report_notes, only: [:create, :destroy] | ||||||
| @ -261,7 +259,13 @@ Rails.application.routes.draw do | |||||||
|       resource :change_email, only: [:show, :update] |       resource :change_email, only: [:show, :update] | ||||||
|       resource :reset, only: [:create] |       resource :reset, only: [:create] | ||||||
|       resource :action, only: [:new, :create], controller: 'account_actions' |       resource :action, only: [:new, :create], controller: 'account_actions' | ||||||
|       resources :statuses, only: [:index, :show, :create, :update, :destroy] | 
 | ||||||
|  |       resources :statuses, only: [:index] do | ||||||
|  |         collection do | ||||||
|  |           post :batch | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       resources :relationships, only: [:index] |       resources :relationships, only: [:index] | ||||||
| 
 | 
 | ||||||
|       resource :confirmation, only: [:create] do |       resource :confirmation, only: [:create] do | ||||||
| @ -518,7 +522,7 @@ Rails.application.routes.draw do | |||||||
|           resource :action, only: [:create], controller: 'account_actions' |           resource :action, only: [:create], controller: 'account_actions' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         resources :reports, only: [:index, :show] do |         resources :reports, only: [:index, :update, :show] do | ||||||
|           member do |           member do | ||||||
|             post :assign_to_self |             post :assign_to_self | ||||||
|             post :unassign |             post :unassign | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								db/migrate/20211231080958_add_category_to_reports.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								db/migrate/20211231080958_add_category_to_reports.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | require Rails.root.join('lib', 'mastodon', 'migration_helpers') | ||||||
|  | 
 | ||||||
|  | class AddCategoryToReports < ActiveRecord::Migration[6.1] | ||||||
|  |   include Mastodon::MigrationHelpers | ||||||
|  | 
 | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     safety_assured { add_column_with_default :reports, :category, :int, default: 0, allow_null: false } | ||||||
|  |     add_column :reports, :action_taken_at, :datetime | ||||||
|  |     add_column :reports, :rule_ids, :bigint, array: true | ||||||
|  |     safety_assured { execute 'UPDATE reports SET action_taken_at = updated_at WHERE action_taken = TRUE' } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     safety_assured { execute 'UPDATE reports SET action_taken = TRUE WHERE action_taken_at IS NOT NULL' } | ||||||
|  |     remove_column :reports, :category | ||||||
|  |     remove_column :reports, :action_taken_at | ||||||
|  |     remove_column :reports, :rule_ids | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | class AddReportIdToAccountWarnings < ActiveRecord::Migration[6.1] | ||||||
|  |   def change | ||||||
|  |     safety_assured { add_reference :account_warnings, :report, foreign_key: { on_delete: :cascade }, index: false } | ||||||
|  |     add_column :account_warnings, :status_ids, :string, array: true | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										21
									
								
								db/migrate/20220115125341_fix_account_warning_actions.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								db/migrate/20220115125341_fix_account_warning_actions.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | class FixAccountWarningActions < ActiveRecord::Migration[6.1] | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     safety_assured do | ||||||
|  |       execute 'UPDATE account_warnings SET action = 1000 WHERE action = 1' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 2000 WHERE action = 2' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 3000 WHERE action = 3' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 4000 WHERE action = 4' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     safety_assured do | ||||||
|  |       execute 'UPDATE account_warnings SET action = 1 WHERE action = 1000' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 2 WHERE action = 2000' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 3 WHERE action = 3000' | ||||||
|  |       execute 'UPDATE account_warnings SET action = 4 WHERE action = 4000' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,7 @@ | |||||||
|  | class AddDeletedAtIndexOnStatuses < ActiveRecord::Migration[6.1] | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def change | ||||||
|  |     add_index :statuses, :deleted_at, where: 'deleted_at IS NOT NULL', algorithm: :concurrently | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class RemoveActionTakenFromReports < ActiveRecord::Migration[5.2] | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def change | ||||||
|  |     safety_assured { remove_column :reports, :action_taken, :boolean, default: false, null: false } | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										10
									
								
								db/schema.rb
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								db/schema.rb
									
									
									
									
									
								
							| @ -10,7 +10,7 @@ | |||||||
| # | # | ||||||
| # It's strongly recommended that you check this file into your version control system. | # It's strongly recommended that you check this file into your version control system. | ||||||
| 
 | 
 | ||||||
| ActiveRecord::Schema.define(version: 2021_12_13_040746) do | ActiveRecord::Schema.define(version: 2022_01_16_202951) do | ||||||
| 
 | 
 | ||||||
|   # These are extensions that must be enabled in order to support this database |   # These are extensions that must be enabled in order to support this database | ||||||
|   enable_extension "plpgsql" |   enable_extension "plpgsql" | ||||||
| @ -133,6 +133,8 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do | |||||||
|     t.text "text", default: "", null: false |     t.text "text", default: "", null: false | ||||||
|     t.datetime "created_at", null: false |     t.datetime "created_at", null: false | ||||||
|     t.datetime "updated_at", null: false |     t.datetime "updated_at", null: false | ||||||
|  |     t.bigint "report_id" | ||||||
|  |     t.string "status_ids", array: true | ||||||
|     t.index ["account_id"], name: "index_account_warnings_on_account_id" |     t.index ["account_id"], name: "index_account_warnings_on_account_id" | ||||||
|     t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id" |     t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id" | ||||||
|   end |   end | ||||||
| @ -747,7 +749,6 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do | |||||||
|   create_table "reports", force: :cascade do |t| |   create_table "reports", force: :cascade do |t| | ||||||
|     t.bigint "status_ids", default: [], null: false, array: true |     t.bigint "status_ids", default: [], null: false, array: true | ||||||
|     t.text "comment", default: "", null: false |     t.text "comment", default: "", null: false | ||||||
|     t.boolean "action_taken", default: false, null: false |  | ||||||
|     t.datetime "created_at", null: false |     t.datetime "created_at", null: false | ||||||
|     t.datetime "updated_at", null: false |     t.datetime "updated_at", null: false | ||||||
|     t.bigint "account_id", null: false |     t.bigint "account_id", null: false | ||||||
| @ -756,6 +757,9 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do | |||||||
|     t.bigint "assigned_account_id" |     t.bigint "assigned_account_id" | ||||||
|     t.string "uri" |     t.string "uri" | ||||||
|     t.boolean "forwarded" |     t.boolean "forwarded" | ||||||
|  |     t.integer "category", default: 0, null: false | ||||||
|  |     t.datetime "action_taken_at" | ||||||
|  |     t.bigint "rule_ids", array: true | ||||||
|     t.index ["account_id"], name: "index_reports_on_account_id" |     t.index ["account_id"], name: "index_reports_on_account_id" | ||||||
|     t.index ["target_account_id"], name: "index_reports_on_target_account_id" |     t.index ["target_account_id"], name: "index_reports_on_target_account_id" | ||||||
|   end |   end | ||||||
| @ -853,6 +857,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do | |||||||
|     t.string "content_type" |     t.string "content_type" | ||||||
|     t.datetime "deleted_at" |     t.datetime "deleted_at" | ||||||
|     t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" |     t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" | ||||||
|  |     t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)" | ||||||
|     t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" |     t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" | ||||||
|     t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" |     t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" | ||||||
|     t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" |     t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" | ||||||
| @ -1010,6 +1015,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do | |||||||
|   add_foreign_key "account_statuses_cleanup_policies", "accounts", on_delete: :cascade |   add_foreign_key "account_statuses_cleanup_policies", "accounts", on_delete: :cascade | ||||||
|   add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade |   add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade | ||||||
|   add_foreign_key "account_warnings", "accounts", on_delete: :nullify |   add_foreign_key "account_warnings", "accounts", on_delete: :nullify | ||||||
|  |   add_foreign_key "account_warnings", "reports", on_delete: :cascade | ||||||
|   add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify |   add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify | ||||||
|   add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade |   add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade | ||||||
|   add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade |   add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
								
							| @ -64,8 +64,8 @@ | |||||||
|     "@babel/core": "^7.16.7", |     "@babel/core": "^7.16.7", | ||||||
|     "@babel/plugin-proposal-decorators": "^7.16.7", |     "@babel/plugin-proposal-decorators": "^7.16.7", | ||||||
|     "@babel/plugin-transform-react-inline-elements": "^7.16.7", |     "@babel/plugin-transform-react-inline-elements": "^7.16.7", | ||||||
|     "@babel/plugin-transform-runtime": "^7.16.7", |     "@babel/plugin-transform-runtime": "^7.16.8", | ||||||
|     "@babel/preset-env": "^7.16.7", |     "@babel/preset-env": "^7.16.8", | ||||||
|     "@babel/preset-react": "^7.16.7", |     "@babel/preset-react": "^7.16.7", | ||||||
|     "@babel/runtime": "^7.16.7", |     "@babel/runtime": "^7.16.7", | ||||||
|     "@gamestdio/websocket": "^0.3.2", |     "@gamestdio/websocket": "^0.3.2", | ||||||
| @ -125,7 +125,7 @@ | |||||||
|     "postcss-loader": "^3.0.0", |     "postcss-loader": "^3.0.0", | ||||||
|     "postcss-object-fit-images": "^1.1.2", |     "postcss-object-fit-images": "^1.1.2", | ||||||
|     "promise.prototype.finally": "^3.1.3", |     "promise.prototype.finally": "^3.1.3", | ||||||
|     "prop-types": "^15.5.10", |     "prop-types": "^15.8.1", | ||||||
|     "punycode": "^2.1.0", |     "punycode": "^2.1.0", | ||||||
|     "react": "^16.14.0", |     "react": "^16.14.0", | ||||||
|     "react-dom": "^16.14.0", |     "react-dom": "^16.14.0", | ||||||
| @ -141,12 +141,12 @@ | |||||||
|     "react-redux-loading-bar": "^4.0.8", |     "react-redux-loading-bar": "^4.0.8", | ||||||
|     "react-router-dom": "^4.1.1", |     "react-router-dom": "^4.1.1", | ||||||
|     "react-router-scroll-4": "^1.0.0-beta.1", |     "react-router-scroll-4": "^1.0.0-beta.1", | ||||||
|     "react-select": "^5.2.1", |     "react-select": "^5.2.2", | ||||||
|     "react-sparklines": "^1.7.0", |     "react-sparklines": "^1.7.0", | ||||||
|     "react-swipeable-views": "^0.14.0", |     "react-swipeable-views": "^0.14.0", | ||||||
|     "react-textarea-autosize": "^8.3.3", |     "react-textarea-autosize": "^8.3.3", | ||||||
|     "react-toggle": "^4.1.2", |     "react-toggle": "^4.1.2", | ||||||
|     "redis": "^4.0.1", |     "redis": "^4.0.2", | ||||||
|     "redux": "^4.1.2", |     "redux": "^4.1.2", | ||||||
|     "redux-immutable": "^4.0.0", |     "redux-immutable": "^4.0.0", | ||||||
|     "redux-thunk": "^2.4.1", |     "redux-thunk": "^2.4.1", | ||||||
| @ -155,7 +155,7 @@ | |||||||
|     "requestidlecallback": "^0.3.0", |     "requestidlecallback": "^0.3.0", | ||||||
|     "reselect": "^4.1.5", |     "reselect": "^4.1.5", | ||||||
|     "rimraf": "^3.0.2", |     "rimraf": "^3.0.2", | ||||||
|     "sass": "^1.45.2", |     "sass": "^1.48.0", | ||||||
|     "sass-loader": "^10.2.0", |     "sass-loader": "^10.2.0", | ||||||
|     "stacktrace-js": "^2.0.2", |     "stacktrace-js": "^2.0.2", | ||||||
|     "stringz": "^2.1.0", |     "stringz": "^2.1.0", | ||||||
| @ -172,19 +172,19 @@ | |||||||
|     "webpack-cli": "^3.3.12", |     "webpack-cli": "^3.3.12", | ||||||
|     "webpack-merge": "^5.8.0", |     "webpack-merge": "^5.8.0", | ||||||
|     "wicg-inert": "^3.1.1", |     "wicg-inert": "^3.1.1", | ||||||
|     "ws": "^8.3.0" |     "ws": "^8.4.2" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@testing-library/jest-dom": "^5.16.1", |     "@testing-library/jest-dom": "^5.16.1", | ||||||
|     "@testing-library/react": "^12.1.2", |     "@testing-library/react": "^12.1.2", | ||||||
|     "babel-eslint": "^10.1.0", |     "babel-eslint": "^10.1.0", | ||||||
|     "babel-jest": "^27.4.5", |     "babel-jest": "^27.4.6", | ||||||
|     "eslint": "^7.32.0", |     "eslint": "^7.32.0", | ||||||
|     "eslint-plugin-import": "~2.25.4", |     "eslint-plugin-import": "~2.25.4", | ||||||
|     "eslint-plugin-jsx-a11y": "~6.5.1", |     "eslint-plugin-jsx-a11y": "~6.5.1", | ||||||
|     "eslint-plugin-promise": "~6.0.0", |     "eslint-plugin-promise": "~6.0.0", | ||||||
|     "eslint-plugin-react": "~7.28.0", |     "eslint-plugin-react": "~7.28.0", | ||||||
|     "jest": "^27.4.5", |     "jest": "^27.4.7", | ||||||
|     "raf": "^3.4.1", |     "raf": "^3.4.1", | ||||||
|     "react-intl-translations-manager": "^5.0.3", |     "react-intl-translations-manager": "^5.0.3", | ||||||
|     "react-test-renderer": "^16.14.0", |     "react-test-renderer": "^16.14.0", | ||||||
|  | |||||||
| @ -12,11 +12,11 @@ describe Admin::ReportNotesController do | |||||||
|   describe 'POST #create' do |   describe 'POST #create' do | ||||||
|     subject { post :create, params: params } |     subject { post :create, params: params } | ||||||
| 
 | 
 | ||||||
|     let(:report) { Fabricate(:report, action_taken: action_taken, action_taken_by_account_id: account_id) } |     let(:report) { Fabricate(:report, action_taken_at: action_taken, action_taken_by_account_id: account_id) } | ||||||
| 
 | 
 | ||||||
|     context 'when parameter is valid' do |     context 'when parameter is valid' do | ||||||
|       context 'when report is unsolved' do |       context 'when report is unsolved' do | ||||||
|         let(:action_taken) { false } |         let(:action_taken) { nil } | ||||||
|         let(:account_id) { nil } |         let(:account_id) { nil } | ||||||
| 
 | 
 | ||||||
|         context 'when create_and_resolve flag is on' do |         context 'when create_and_resolve flag is on' do | ||||||
| @ -41,7 +41,7 @@ describe Admin::ReportNotesController do | |||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'when report is resolved' do |       context 'when report is resolved' do | ||||||
|         let(:action_taken) { true } |         let(:action_taken) { Time.now.utc } | ||||||
|         let(:account_id) { user.account.id } |         let(:account_id) { user.account.id } | ||||||
| 
 | 
 | ||||||
|         context 'when create_and_unresolve flag is on' do |         context 'when create_and_unresolve flag is on' do | ||||||
| @ -68,7 +68,7 @@ describe Admin::ReportNotesController do | |||||||
| 
 | 
 | ||||||
|     context 'when parameter is invalid' do |     context 'when parameter is invalid' do | ||||||
|       let(:params) { { report_note: { content: '', report_id: report.id } } } |       let(:params) { { report_note: { content: '', report_id: report.id } } } | ||||||
|       let(:action_taken) { false } |       let(:action_taken) { nil } | ||||||
|       let(:account_id) { nil } |       let(:account_id) { nil } | ||||||
| 
 | 
 | ||||||
|       it 'renders admin/reports/show' do |       it 'renders admin/reports/show' do | ||||||
|  | |||||||
| @ -1,59 +0,0 @@ | |||||||
| require 'rails_helper' |  | ||||||
| 
 |  | ||||||
| describe Admin::ReportedStatusesController do |  | ||||||
|   render_views |  | ||||||
| 
 |  | ||||||
|   let(:user) { Fabricate(:user, admin: true) } |  | ||||||
|   let(:report) { Fabricate(:report, status_ids: [status.id]) } |  | ||||||
|   let(:status) { Fabricate(:status) } |  | ||||||
| 
 |  | ||||||
|   before do |  | ||||||
|     sign_in user, scope: :user |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe 'POST #create' do |  | ||||||
|     subject do |  | ||||||
|       -> { post :create, params: { :report_id => report, action => '', :form_status_batch => { status_ids: status_ids } } } |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     let(:action) { 'nsfw_on' } |  | ||||||
|     let(:status_ids) { [status.id] } |  | ||||||
|     let(:status) { Fabricate(:status, sensitive: !sensitive) } |  | ||||||
|     let(:sensitive) { true } |  | ||||||
|     let!(:media_attachment) { Fabricate(:media_attachment, status: status) } |  | ||||||
| 
 |  | ||||||
|     context 'when action is nsfw_on' do |  | ||||||
|       it 'updates sensitive column' do |  | ||||||
|         is_expected.to change { |  | ||||||
|           status.reload.sensitive |  | ||||||
|         }.from(false).to(true) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when action is nsfw_off' do |  | ||||||
|       let(:action) { 'nsfw_off' } |  | ||||||
|       let(:sensitive) { false } |  | ||||||
| 
 |  | ||||||
|       it 'updates sensitive column' do |  | ||||||
|         is_expected.to change { |  | ||||||
|           status.reload.sensitive |  | ||||||
|         }.from(true).to(false) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when action is delete' do |  | ||||||
|       let(:action) { 'delete' } |  | ||||||
| 
 |  | ||||||
|       it 'removes a status' do |  | ||||||
|         allow(RemovalWorker).to receive(:perform_async) |  | ||||||
|         subject.call |  | ||||||
|         expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it 'redirects to report page' do |  | ||||||
|       subject.call |  | ||||||
|       expect(response).to redirect_to(admin_report_path(report)) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @ -10,8 +10,8 @@ describe Admin::ReportsController do | |||||||
| 
 | 
 | ||||||
|   describe 'GET #index' do |   describe 'GET #index' do | ||||||
|     it 'returns http success with no filters' do |     it 'returns http success with no filters' do | ||||||
|       specified = Fabricate(:report, action_taken: false) |       specified = Fabricate(:report, action_taken_at: nil) | ||||||
|       Fabricate(:report, action_taken: true) |       Fabricate(:report, action_taken_at: Time.now.utc) | ||||||
| 
 | 
 | ||||||
|       get :index |       get :index | ||||||
| 
 | 
 | ||||||
| @ -22,10 +22,10 @@ describe Admin::ReportsController do | |||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'returns http success with resolved filter' do |     it 'returns http success with resolved filter' do | ||||||
|       specified = Fabricate(:report, action_taken: true) |       specified = Fabricate(:report, action_taken_at: Time.now.utc) | ||||||
|       Fabricate(:report, action_taken: false) |       Fabricate(:report, action_taken_at: nil) | ||||||
| 
 | 
 | ||||||
|       get :index, params: { resolved: 1 } |       get :index, params: { resolved: '1' } | ||||||
| 
 | 
 | ||||||
|       reports = assigns(:reports).to_a |       reports = assigns(:reports).to_a | ||||||
|       expect(reports.size).to eq 1 |       expect(reports.size).to eq 1 | ||||||
| @ -54,15 +54,7 @@ describe Admin::ReportsController do | |||||||
|       expect(response).to redirect_to(admin_reports_path) |       expect(response).to redirect_to(admin_reports_path) | ||||||
|       report.reload |       report.reload | ||||||
|       expect(report.action_taken_by_account).to eq user.account |       expect(report.action_taken_by_account).to eq user.account | ||||||
|       expect(report.action_taken).to eq true |       expect(report.action_taken?).to eq true | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it 'sets trust level when the report is an antispam one' do |  | ||||||
|       report = Fabricate(:report, account: Account.representative) |  | ||||||
| 
 |  | ||||||
|       put :resolve, params: { id: report } |  | ||||||
|       report.reload |  | ||||||
|       expect(report.target_account.trust_level).to eq Account::TRUST_LEVELS[:trusted] |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
| @ -74,7 +66,7 @@ describe Admin::ReportsController do | |||||||
|       expect(response).to redirect_to(admin_report_path(report)) |       expect(response).to redirect_to(admin_report_path(report)) | ||||||
|       report.reload |       report.reload | ||||||
|       expect(report.action_taken_by_account).to eq nil |       expect(report.action_taken_by_account).to eq nil | ||||||
|       expect(report.action_taken).to eq false |       expect(report.action_taken?).to eq false | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,65 +18,46 @@ describe Admin::StatusesController do | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'GET #index' do |   describe 'GET #index' do | ||||||
|     it 'returns http success with no media' do |     context do | ||||||
|       get :index, params: { account_id: account.id } |       before do | ||||||
|  |         get :index, params: { account_id: account.id } | ||||||
|  |       end | ||||||
| 
 | 
 | ||||||
|       statuses = assigns(:statuses).to_a |       it 'returns http success' do | ||||||
|       expect(statuses.size).to eq 4 |         expect(response).to have_http_status(200) | ||||||
|       expect(statuses.first.id).to eq last_status.id |       end | ||||||
|       expect(response).to have_http_status(200) |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'returns http success with media' do |     context 'filtering by media' do | ||||||
|       get :index, params: { account_id: account.id, media: true } |       before do | ||||||
|  |         get :index, params: { account_id: account.id, media: '1' } | ||||||
|  |       end | ||||||
| 
 | 
 | ||||||
|       statuses = assigns(:statuses).to_a |       it 'returns http success' do | ||||||
|       expect(statuses.size).to eq 2 |         expect(response).to have_http_status(200) | ||||||
|       expect(statuses.first.id).to eq last_media_attached_status.id |       end | ||||||
|       expect(response).to have_http_status(200) |  | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'POST #create' do |   describe 'POST #batch' do | ||||||
|     subject do |     before do | ||||||
|       -> { post :create, params: { :account_id => account.id, action => '', :form_status_batch => { status_ids: status_ids } } } |       post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } } | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     let(:action) { 'nsfw_on' } |  | ||||||
|     let(:status_ids) { [media_attached_status.id] } |     let(:status_ids) { [media_attached_status.id] } | ||||||
| 
 | 
 | ||||||
|     context 'when action is nsfw_on' do |     context 'when action is report' do | ||||||
|       it 'updates sensitive column' do |       let(:action) { 'report' } | ||||||
|         is_expected.to change { | 
 | ||||||
|           media_attached_status.reload.sensitive |       it 'creates a report' do | ||||||
|         }.from(false).to(true) |         report = Report.last | ||||||
|  |         expect(report.target_account_id).to eq account.id | ||||||
|  |         expect(report.status_ids).to eq status_ids | ||||||
|       end |       end | ||||||
|     end |  | ||||||
| 
 | 
 | ||||||
|     context 'when action is nsfw_off' do |       it 'redirects to report page' do | ||||||
|       let(:action) { 'nsfw_off' } |         expect(response).to redirect_to(admin_report_path(Report.last.id)) | ||||||
|       let(:sensitive) { false } |  | ||||||
| 
 |  | ||||||
|       it 'updates sensitive column' do |  | ||||||
|         is_expected.to change { |  | ||||||
|           media_attached_status.reload.sensitive |  | ||||||
|         }.from(true).to(false) |  | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 |  | ||||||
|     context 'when action is delete' do |  | ||||||
|       let(:action) { 'delete' } |  | ||||||
| 
 |  | ||||||
|       it 'removes a status' do |  | ||||||
|         allow(RemovalWorker).to receive(:perform_async) |  | ||||||
|         subject.call |  | ||||||
|         expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it 'redirects to account statuses page' do |  | ||||||
|       subject.call |  | ||||||
|       expect(response).to redirect_to(admin_account_statuses_path(account.id)) |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| Fabricator(:report) do | Fabricator(:report) do | ||||||
|   account |   account | ||||||
|   target_account { Fabricate(:account) } |   target_account  { Fabricate(:account) } | ||||||
|   comment      "You nasty" |   comment         "You nasty" | ||||||
|   action_taken false |   action_taken_at nil | ||||||
| end | end | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ class UserMailerPreview < ActionMailer::Preview | |||||||
| 
 | 
 | ||||||
|   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning |   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning | ||||||
|   def warning |   def warning | ||||||
|     UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id]) |     UserMailer.warning(User.first, AccountWarning.last) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token |   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token | ||||||
|  | |||||||
| @ -1,52 +0,0 @@ | |||||||
| require 'rails_helper' |  | ||||||
| 
 |  | ||||||
| describe Form::StatusBatch do |  | ||||||
|   let(:form) { Form::StatusBatch.new(action: action, status_ids: status_ids) } |  | ||||||
|   let(:status) { Fabricate(:status) } |  | ||||||
| 
 |  | ||||||
|   describe 'with nsfw action' do |  | ||||||
|     let(:status_ids) { [status.id, nonsensitive_status.id, sensitive_status.id] } |  | ||||||
|     let(:nonsensitive_status) { Fabricate(:status, sensitive: false) } |  | ||||||
|     let(:sensitive_status) { Fabricate(:status, sensitive: true) } |  | ||||||
|     let!(:shown_media_attachment) { Fabricate(:media_attachment, status: nonsensitive_status) } |  | ||||||
|     let!(:hidden_media_attachment) { Fabricate(:media_attachment, status: sensitive_status) } |  | ||||||
| 
 |  | ||||||
|     context 'nsfw_on' do |  | ||||||
|       let(:action) { 'nsfw_on' } |  | ||||||
| 
 |  | ||||||
|       it { expect(form.save).to be true } |  | ||||||
|       it { expect { form.save }.to change { nonsensitive_status.reload.sensitive }.from(false).to(true) } |  | ||||||
|       it { expect { form.save }.not_to change { sensitive_status.reload.sensitive } } |  | ||||||
|       it { expect { form.save }.not_to change { status.reload.sensitive } } |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'nsfw_off' do |  | ||||||
|       let(:action) { 'nsfw_off' } |  | ||||||
| 
 |  | ||||||
|       it { expect(form.save).to be true } |  | ||||||
|       it { expect { form.save }.to change { sensitive_status.reload.sensitive }.from(true).to(false) } |  | ||||||
|       it { expect { form.save }.not_to change { nonsensitive_status.reload.sensitive } } |  | ||||||
|       it { expect { form.save }.not_to change { status.reload.sensitive } } |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe 'with delete action' do |  | ||||||
|     let(:status_ids) { [status.id] } |  | ||||||
|     let(:action) { 'delete' } |  | ||||||
|     let!(:another_status) { Fabricate(:status) } |  | ||||||
| 
 |  | ||||||
|     before do |  | ||||||
|       allow(RemovalWorker).to receive(:perform_async) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it 'call RemovalWorker' do |  | ||||||
|       form.save |  | ||||||
|       expect(RemovalWorker).to have_received(:perform_async).with(status.id, immediate: true) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it 'do not call RemovalWorker' do |  | ||||||
|       form.save |  | ||||||
|       expect(RemovalWorker).not_to have_received(:perform_async).with(another_status.id, immediate: true) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @ -54,7 +54,7 @@ describe Report do | |||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'resolve!' do |   describe 'resolve!' do | ||||||
|     subject(:report) { Fabricate(:report, action_taken: false, action_taken_by_account_id: nil) } |     subject(:report) { Fabricate(:report, action_taken_at: nil, action_taken_by_account_id: nil) } | ||||||
| 
 | 
 | ||||||
|     let(:acting_account) { Fabricate(:account) } |     let(:acting_account) { Fabricate(:account) } | ||||||
| 
 | 
 | ||||||
| @ -63,12 +63,13 @@ describe Report do | |||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'records action taken' do |     it 'records action taken' do | ||||||
|       expect(report).to have_attributes(action_taken: true, action_taken_by_account_id: acting_account.id) |       expect(report.action_taken?).to be true | ||||||
|  |       expect(report.action_taken_by_account_id).to eq acting_account.id | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'unresolve!' do |   describe 'unresolve!' do | ||||||
|     subject(:report) { Fabricate(:report, action_taken: true, action_taken_by_account_id: acting_account.id) } |     subject(:report) { Fabricate(:report, action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id) } | ||||||
| 
 | 
 | ||||||
|     let(:acting_account) { Fabricate(:account) } |     let(:acting_account) { Fabricate(:account) } | ||||||
| 
 | 
 | ||||||
| @ -77,23 +78,24 @@ describe Report do | |||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'unresolves' do |     it 'unresolves' do | ||||||
|       expect(report).to have_attributes(action_taken: false, action_taken_by_account_id: nil) |       expect(report.action_taken?).to be false | ||||||
|  |       expect(report.action_taken_by_account_id).to be_nil | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'unresolved?' do |   describe 'unresolved?' do | ||||||
|     subject { report.unresolved? } |     subject { report.unresolved? } | ||||||
| 
 | 
 | ||||||
|     let(:report) { Fabricate(:report, action_taken: action_taken) } |     let(:report) { Fabricate(:report, action_taken_at: action_taken) } | ||||||
| 
 | 
 | ||||||
|     context 'if action is taken' do |     context 'if action is taken' do | ||||||
|       let(:action_taken) { true } |       let(:action_taken) { Time.now.utc } | ||||||
| 
 | 
 | ||||||
|       it { is_expected.to be false } |       it { is_expected.to be false } | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'if action not is taken' do |     context 'if action not is taken' do | ||||||
|       let(:action_taken) { false } |       let(:action_taken) { nil } | ||||||
| 
 | 
 | ||||||
|       it { is_expected.to be true } |       it { is_expected.to be true } | ||||||
|     end |     end | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user