Add public blocks to /about/blocks (#11298)

* Add automatic blocklist display in /about/blocks

Inspired by https://github.com/Gargron/mastodon.social-misc

* Add admin option to set who can see instance blocks

* Normalize locales files

* Rename “Sandbox” to “Silence” for consistency

* Disable /about/blocks when in whitelist mode

* Optionally display rationale for domain blocks

* Only display domain blocks that have user-facing limitations, and order them

* Redesign table of blocked domains to better handle long domain names and rationales

* Change domain blocks ordering now that rationales aren't displayed right away

* Only show explanation for block severities actually in use

* Reword instance block explanations and add disclaimer for public fetch mode
This commit is contained in:
ThibG
2019-08-19 11:35:48 +02:00
committed by Eugen Rochko
parent 9e1d28f48e
commit 9b6a5ed109
10 changed files with 197 additions and 5 deletions

View File

@ -3,10 +3,12 @@
class AboutController < ApplicationController
layout 'public'
before_action :require_open_federation!, only: [:show, :more]
before_action :require_open_federation!, only: [:show, :more, :blocks]
before_action :check_blocklist_enabled, only: [:blocks]
before_action :authenticate_user!, only: [:blocks], if: :blocklist_account_required?
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in
before_action :set_expires_in, only: [:show, :more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]
@ -18,12 +20,40 @@ class AboutController < ApplicationController
def terms; end
def blocks
@show_rationale = Setting.show_domain_blocks_rationale == 'all'
@show_rationale |= Setting.show_domain_blocks_rationale == 'users' && !current_user.nil? && current_user.functional?
@blocks = DomainBlock.with_user_facing_limitations.order('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain').to_a
end
private
def require_open_federation!
not_found if whitelist_mode?
end
def check_blocklist_enabled
not_found if Setting.show_domain_blocks == 'disabled'
end
def blocklist_account_required?
Setting.show_domain_blocks == 'users'
end
def block_severity_text(block)
if block.severity == 'suspend'
I18n.t('domain_blocks.suspension')
else
limitations = []
limitations << I18n.t('domain_blocks.media_block') if block.reject_media?
limitations << I18n.t('domain_blocks.silence') if block.severity == 'silence'
limitations.join(', ')
end
end
helper_method :block_severity_text
helper_method :public_fetch_mode?
def new_user
User.new.tap do |user|
user.build_account

View File

@ -141,6 +141,15 @@ function main() {
return false;
});
delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
e.preventDefault();
const classList = this.firstElementChild.classList;
classList.toggle('fa-chevron-down');
classList.toggle('fa-chevron-up');
this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
});
delegate(document, '.modal-button', 'click', e => {
e.preventDefault();

View File

@ -241,3 +241,70 @@ a.table-action-link {
}
}
}
.blocks-table {
width: 100%;
max-width: 100%;
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
border: 1px solid darken($ui-base-color, 8%);
thead {
border: 1px solid darken($ui-base-color, 8%);
background: darken($ui-base-color, 4%);
font-weight: 500;
th.severity-column {
width: 120px;
}
th.button-column {
width: 23px;
}
}
tbody > tr {
border: 1px solid darken($ui-base-color, 8%);
border-bottom: 0;
background: darken($ui-base-color, 4%);
&:hover {
background: darken($ui-base-color, 2%);
}
&.even {
background: $ui-base-color;
&:hover {
background: lighten($ui-base-color, 2%);
}
}
&.rationale {
background: lighten($ui-base-color, 4%);
border-top: 0;
&:hover {
background: lighten($ui-base-color, 6%);
}
&.hidden {
display: none;
}
}
td:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
}
th,
td {
padding: 8px;
line-height: 18px;
vertical-align: top;
text-align: left;
}
}

View File

@ -25,6 +25,7 @@ class DomainBlock < ApplicationRecord
delegate :count, to: :accounts, prefix: true
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
class << self
def suspend?(domain)

View File

@ -30,6 +30,8 @@ class Form::AdminSettings
mascot
spam_check_enabled
trends
show_domain_blocks
show_domain_blocks_rationale
).freeze
BOOLEAN_KEYS = %i(
@ -60,6 +62,8 @@ class Form::AdminSettings
validates :site_contact_email, :site_contact_username, presence: true
validates :site_contact_username, existing_username: true
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
def initialize(_attributes = {})
super

View File

@ -0,0 +1,48 @@
- content_for :page_title do
= t('domain_blocks.title', instance: site_hostname)
.grid
.column-0
.box-widget.rich-formatting
%h2= t('domain_blocks.blocked_domains')
%p= t('domain_blocks.description', instance: site_hostname)
.table-wrapper
%table.blocks-table
%thead
%tr
%th= t('domain_blocks.domain')
%th.severity-column= t('domain_blocks.severity')
- if @show_rationale
%th.button-column
%tbody
- if @blocks.empty?
%tr
%td{ colspan: @show_rationale ? 3 : 2 }= t('domain_blocks.no_domain_blocks')
- else
- @blocks.each_with_index do |block, i|
%tr{ class: i % 2 == 0 ? 'even': nil }
%td{ title: block.domain }= block.domain
%td= block_severity_text(block)
- if @show_rationale
%td
- if block.public_comment.present?
%button.icon-button{ title: t('domain_blocks.show_rationale'), 'aria-label' => t('domain_blocks.show_rationale') }
= fa_icon 'chevron-down fw', 'aria-hidden' => true
- if @show_rationale
- if block.public_comment.present?
%tr.rationale.hidden
%td{ colspan: 3 }= block.public_comment.presence
%h2= t('domain_blocks.severity_legend.title')
- if @blocks.any? { |block| block.reject_media? }
%h3= t('domain_blocks.media_block')
%p= t('domain_blocks.severity_legend.media_block')
- if @blocks.any? { |block| block.severity == 'silence' }
%h3= t('domain_blocks.silence')
%p= t('domain_blocks.severity_legend.silence')
- if @blocks.any? { |block| block.severity == 'suspend' }
%h3= t('domain_blocks.suspension')
%p= t('domain_blocks.severity_legend.suspension')
- if public_fetch_mode?
%p= t('domain_blocks.severity_legend.suspension_disclaimer')
.column-1
= render 'application/sidebar'

View File

@ -79,6 +79,12 @@
.fields-group
= f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-row
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-row__column.fields-row__column-6.fields-group
= f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
.fields-group
= f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
= f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?