Add table of contents to about page (#11885)

Move public domain blocks information to about page
This commit is contained in:
Eugen Rochko
2019-09-19 11:09:05 +02:00
committed by GitHub
parent e1066cd431
commit d930eb88b6
10 changed files with 328 additions and 215 deletions

View File

@ -3,9 +3,7 @@
class AboutController < ApplicationController
layout 'public'
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 :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in, only: [:show, :more, :terms]
@ -16,15 +14,20 @@ class AboutController < ApplicationController
def more
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
@contents = toc_generator.html
@table_of_contents = toc_generator.toc
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
end
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
helper_method :display_blocks?
helper_method :display_blocks_rationale?
helper_method :public_fetch_mode?
helper_method :new_user
private
@ -32,28 +35,14 @@ class AboutController < ApplicationController
not_found if whitelist_mode?
end
def check_blocklist_enabled
not_found if Setting.show_domain_blocks == 'disabled'
def display_blocks?
Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
end
def blocklist_account_required?
Setting.show_domain_blocks == 'users'
def display_blocks_rationale?
Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
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
@ -61,8 +50,6 @@ class AboutController < ApplicationController
end
end
helper_method :new_user
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end

View File

@ -17,117 +17,86 @@ $small-breakpoint: 960px;
.rich-formatting {
font-family: $font-sans-serif, sans-serif;
font-size: 16px;
font-size: 14px;
font-weight: 400;
font-size: 16px;
line-height: 30px;
line-height: 1.7;
word-wrap: break-word;
color: $darker-text-color;
padding-right: 10px;
a {
color: $highlight-text-color;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
p,
li {
font-family: $font-sans-serif, sans-serif;
font-size: 16px;
font-weight: 400;
font-size: 16px;
line-height: 30px;
margin-bottom: 12px;
color: $darker-text-color;
}
a {
color: $highlight-text-color;
text-decoration: underline;
}
p {
margin-top: 0;
margin-bottom: .85em;
&:last-child {
margin-bottom: 0;
}
}
strong,
em {
strong {
font-weight: 700;
color: lighten($darker-text-color, 10%);
color: $secondary-text-color;
}
em {
font-style: italic;
color: $secondary-text-color;
}
code {
font-size: 0.85em;
background: darken($ui-base-color, 8%);
border-radius: 4px;
padding: 0.2em 0.3em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-display, sans-serif;
margin-top: 1.275em;
margin-bottom: .85em;
font-weight: 500;
color: $secondary-text-color;
}
h1 {
font-family: $font-display, sans-serif;
font-size: 26px;
line-height: 30px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
small {
font-family: $font-sans-serif, sans-serif;
display: block;
font-size: 18px;
font-weight: 400;
color: lighten($darker-text-color, 10%);
}
font-size: 2em;
}
h2 {
font-family: $font-display, sans-serif;
font-size: 22px;
line-height: 26px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
font-size: 1.75em;
}
h3 {
font-family: $font-display, sans-serif;
font-size: 18px;
line-height: 24px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
font-size: 1.5em;
}
h4 {
font-family: $font-display, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
}
h5 {
font-family: $font-display, sans-serif;
font-size: 14px;
line-height: 24px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
font-size: 1.25em;
}
h5,
h6 {
font-family: $font-display, sans-serif;
font-size: 12px;
line-height: 24px;
font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color;
}
ul,
ol {
margin-left: 20px;
&[type='a'] {
list-style-type: lower-alpha;
}
&[type='i'] {
list-style-type: lower-roman;
}
font-size: 1em;
}
ul {
@ -138,23 +107,38 @@ $small-breakpoint: 960px;
list-style: decimal;
}
li > ol,
li > ul {
margin-top: 6px;
ul,
ol {
margin: 0;
padding: 0;
padding-left: 2em;
margin-bottom: 0.85em;
&[type='a'] {
list-style-type: lower-alpha;
}
&[type='i'] {
list-style-type: lower-roman;
}
}
hr {
width: 100%;
height: 0;
border: 0;
border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
margin: 20px 0;
border-bottom: 1px solid lighten($ui-base-color, 4%);
margin: 1.7em 0;
&.spacer {
height: 1px;
border: 0;
}
}
& > :first-child {
margin-top: 0;
}
}
.information-board {
@ -416,7 +400,7 @@ $small-breakpoint: 960px;
}
&__call-to-action {
background: darken($ui-base-color, 4%);
background: $ui-base-color;
border-radius: 4px;
padding: 25px 40px;
overflow: hidden;

View File

@ -141,6 +141,63 @@
grid-row: 3;
}
@media screen and (max-width: $no-gap-breakpoint) {
grid-gap: 0;
grid-template-columns: minmax(0, 100%);
.column-0 {
grid-column: 1;
}
.column-1 {
grid-column: 1;
grid-row: 3;
}
.column-2 {
grid-column: 1;
grid-row: 2;
}
.column-3 {
grid-column: 1;
grid-row: 4;
}
}
}
.grid-4 {
display: grid;
grid-gap: 10px;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-auto-columns: 25%;
grid-auto-rows: max-content;
.column-0 {
grid-column: 1 / 5;
grid-row: 1;
}
.column-1 {
grid-column: 1 / 4;
grid-row: 2;
}
.column-2 {
grid-column: 4;
grid-row: 2;
}
.column-3 {
grid-column: 2 / 5;
grid-row: 3;
}
.column-4 {
grid-column: 1;
grid-row: 3;
}
.landing-page__call-to-action {
min-height: 100%;
}
@ -189,6 +246,11 @@
}
.column-3 {
grid-column: 1;
grid-row: 5;
}
.column-4 {
grid-column: 1;
grid-row: 4;
}

View File

@ -128,41 +128,43 @@
margin-bottom: 10px;
}
.contact-widget,
.landing-page__information.contact-widget {
box-sizing: border-box;
padding: 20px;
min-height: 100%;
border-radius: 4px;
background: $ui-base-color;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
}
.contact-widget {
min-height: 100%;
font-size: 15px;
color: $darker-text-color;
line-height: 20px;
word-wrap: break-word;
font-weight: 400;
padding: 0;
strong {
font-weight: 500;
h4 {
padding: 10px;
text-transform: uppercase;
font-weight: 700;
font-size: 13px;
color: $darker-text-color;
}
p {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.account {
border-bottom: 0;
padding: 10px 0;
padding-top: 5px;
}
&__mail {
margin-top: 10px;
& > a {
display: inline-block;
padding: 10px;
padding-top: 0;
color: $darker-text-color;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
a {
color: $primary-text-color;
text-decoration: none;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
}
@ -562,3 +564,38 @@ $fluid-breakpoint: $maximum-width + 20px;
}
}
}
.table-of-contents {
background: darken($ui-base-color, 4%);
min-height: 100%;
font-size: 14px;
border-radius: 4px;
li a {
display: block;
font-weight: 500;
padding: 15px;
overflow: hidden;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: none;
color: $primary-text-color;
border-bottom: 1px solid lighten($ui-base-color, 4%);
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
li:last-child a {
border-bottom: 0;
}
li ul {
padding-left: 20px;
border-bottom: 1px solid lighten($ui-base-color, 4%);
}
}

69
app/lib/toc_generator.rb Normal file
View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
class TOCGenerator
TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze
LISTED_ELEMENTS = %w(h2 h3).freeze
class Section
attr_accessor :depth, :title, :children, :anchor
def initialize(depth, title, anchor)
@depth = depth
@title = title
@children = []
@anchor = anchor
end
delegate :<<, to: :children
end
def initialize(source_html)
@source_html = source_html
@processed = false
@target_html = ''
@headers = []
@slugs = Hash.new { |h, k| h[k] = 0 }
end
def html
parse_and_transform unless @processed
@target_html
end
def toc
parse_and_transform unless @processed
@headers
end
private
def parse_and_transform
return if @source_html.blank?
parsed_html = Nokogiri::HTML.fragment(@source_html)
parsed_html.traverse do |node|
next unless TARGET_ELEMENTS.include?(node.name)
anchor = node.text.parameterize
@slugs[anchor] += 1
anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1
node['id'] = anchor
next unless LISTED_ELEMENTS.include?(node.name)
depth = node.name[1..-1]
latest_section = @headers.last
if latest_section.nil? || latest_section.depth >= depth
@headers << Section.new(depth, node.text, anchor)
else
latest_section << Section.new(depth, node.text, anchor)
end
end
@target_html = parsed_html.to_s
@processed = true
end
end

View File

@ -26,6 +26,7 @@ class DomainBlock < ApplicationRecord
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) }
class << self
def suspend?(domain)

View File

@ -1,48 +0,0 @@
- 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

@ -5,7 +5,7 @@
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
= render partial: 'shared/og'
.grid-3
.grid-4
.column-0
.public-account-header.public-account-header--no-bar
.public-account-header__image
@ -28,22 +28,57 @@
= image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
.column-2
.landing-page__information.contact-widget
%p
%strong= t 'about.administered_by'
.contact-widget
%h4= t 'about.administered_by'
= account_link_to(@instance_presenter.contact_account)
- if @instance_presenter.site_contact_email.present?
%p.contact-widget__mail
%strong
= succeed ':' do
= t 'about.contact'
%br/
= mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
%h4
= succeed ':' do
= t 'about.contact'
= mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
.column-3
= render 'application/flashes'
.box-widget
.rich-formatting= @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html')
- if @contents.blank? && (!display_blocks? || @blocks&.empty?)
= nothing_here
- else
.box-widget
.rich-formatting
= @contents.html_safe
- if display_blocks? && !@blocks.empty?
%h2#unavailable-content= t('about.unavailable_content')
%p= t('about.unavailable_content_html')
- @blocks.each do |domain_block|
%p
%strong= "#{domain_block.domain}:"
- if domain_block.suspend?
= t('about.unavailable_content_description.suspended')
- else
= t('about.unavailable_content_description.silenced') if domain_block.silence?
= t('about.unavailable_content_description.rejecting_media') if domain_block.reject_media?
- if display_blocks_rationale?
%strong= t('about.unavailable_content_description.reason')
= domain_block.public_comment
.column-4
%ul.table-of-contents
- @table_of_contents.each do |item|
%li
= link_to item.title, "##{item.anchor}"
- unless item.children.empty?
%ul
- item.children.each do |sub_item|
%li= link_to sub_item.title, "##{sub_item.anchor}"
- if display_blocks? && !@blocks.empty?
%li= link_to t('about.unavailable_content'), '#unavailable-content'