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]
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
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();
}
}
}
+
+.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;
+ }
+}
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)
mascot
spam_check_enabled
trends
+ show_domain_blocks
+ show_domain_blocks_rationale
).freeze
BOOLEAN_KEYS = %i(
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
--- /dev/null
+- 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'
.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?
custom_css:
desc_html: Modify the look with CSS loaded on every page
title: Custom CSS
+ domain_blocks:
+ all: To everyone
+ disabled: To no one
+ title: Show domain blocks
+ users: To logged-in local users
+ domain_blocks_rationale:
+ title: Show rationale
hero:
desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
title: Hero image
people:
one: "%{count} person"
other: "%{count} people"
+ domain_blocks:
+ blocked_domains: List of limited and blocked domains
+ description: This is the list of servers that %{instance} limits or reject federation with.
+ domain: Domain
+ media_block: Media block
+ no_domain_blocks: "(No domain blocks)"
+ severity: Severity
+ severity_legend:
+ media_block: Media files coming from the server are neither fetched, stored, or displayed to the user.
+ silence: Accounts from silenced servers can be found, followed and interacted with, but their toots will not appear in the public timelines, and notifications from them will not reach local users who are not following them.
+ suspension: No content from suspended servers is stored or displayed, nor is any content sent to them. Interactions from suspended servers are ignored.
+ suspension_disclaimer: Suspended servers may occasionally retrieve public content from this server.
+ title: Severities
+ show_rationale: Show rationale
+ silence: Silence
+ suspension: Suspension
+ title: "%{instance} List of blocked instances"
domain_validator:
invalid_domain: is not a valid domain name
errors:
get '/web/(*any)', to: 'home#index', as: :web
- get '/about', to: 'about#show'
- get '/about/more', to: 'about#more'
- get '/terms', to: 'about#terms'
+ get '/about', to: 'about#show'
+ get '/about/more', to: 'about#more'
+ get '/about/blocks', to: 'about#blocks'
+ get '/terms', to: 'about#terms'
root 'home#index'
peers_api_enabled: true
show_known_fediverse_at_about_page: true
spam_check_enabled: true
+ show_domain_blocks: 'disabled'
+ show_domain_blocks_rationale: 'disabled'
development:
<<: *defaults