Add table of contents to about page (#11885)
Move public domain blocks information to about page
This commit is contained in:
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
69
app/lib/toc_generator.rb
Normal 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
|
@ -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)
|
||||
|
@ -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'
|
@ -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'
|
||||
|
Reference in New Issue
Block a user