about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-09-19 11:09:05 +0200
committerGitHub <noreply@github.com>2019-09-19 11:09:05 +0200
commitd930eb88b671fa6e5573fe7342bcdda87501bdb7 (patch)
treea67ab76528da0060e7def3d13f5802904d215939 /app
parente1066cd4319a220d5be16e51ffaf5236a2f6e866 (diff)
Add table of contents to about page (#11885)
Move public domain blocks information to about page
Diffstat (limited to 'app')
-rw-r--r--app/controllers/about_controller.rb43
-rw-r--r--app/javascript/styles/mastodon/about.scss138
-rw-r--r--app/javascript/styles/mastodon/containers.scss62
-rw-r--r--app/javascript/styles/mastodon/widgets.scss83
-rw-r--r--app/lib/toc_generator.rb69
-rw-r--r--app/models/domain_block.rb1
-rw-r--r--app/views/about/blocks.html.haml48
-rw-r--r--app/views/about/more.html.haml59
8 files changed, 315 insertions, 188 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 5e942e5c0..abd1ec0cb 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -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'
-  end
-
-  def blocklist_account_required?
-    Setting.show_domain_blocks == 'users'
+  def display_blocks?
+    Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == '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
+  def display_blocks_rationale?
+    Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
   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
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index 61637ce96..c056ef85d 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -17,109 +17,102 @@ $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;
   }
 
-  h1 {
-    font-family: $font-display, sans-serif;
-    font-size: 26px;
-    line-height: 30px;
-    font-weight: 500;
-    margin-bottom: 20px;
+  em {
+    font-style: italic;
     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%);
-    }
+  code {
+    font-size: 0.85em;
+    background: darken($ui-base-color, 8%);
+    border-radius: 4px;
+    padding: 0.2em 0.3em;
   }
 
-  h2 {
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
     font-family: $font-display, sans-serif;
-    font-size: 22px;
-    line-height: 26px;
+    margin-top: 1.275em;
+    margin-bottom: .85em;
     font-weight: 500;
-    margin-bottom: 20px;
     color: $secondary-text-color;
   }
 
+  h1 {
+    font-size: 2em;
+  }
+
+  h2 {
+    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;
+    font-size: 1.25em;
   }
 
-  h5 {
-    font-family: $font-display, sans-serif;
-    font-size: 14px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+  h5,
+  h6 {
+    font-size: 1em;
   }
 
-  h6 {
-    font-family: $font-display, sans-serif;
-    font-size: 12px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+  ul {
+    list-style: disc;
+  }
+
+  ol {
+    list-style: decimal;
   }
 
   ul,
   ol {
-    margin-left: 20px;
+    margin: 0;
+    padding: 0;
+    padding-left: 2em;
+    margin-bottom: 0.85em;
 
     &[type='a'] {
       list-style-type: lower-alpha;
@@ -130,31 +123,22 @@ $small-breakpoint: 960px;
     }
   }
 
-  ul {
-    list-style: disc;
-  }
-
-  ol {
-    list-style: decimal;
-  }
-
-  li > ol,
-  li > ul {
-    margin-top: 6px;
-  }
-
   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;
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index aa45c0174..24bbf8211 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -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%;
   }
@@ -190,6 +247,11 @@
 
     .column-3 {
       grid-column: 1;
+      grid-row: 5;
+    }
+
+    .column-4 {
+      grid-column: 1;
       grid-row: 4;
     }
   }
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 04beb869c..ca050a8d9 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -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%);
+  }
+}
diff --git a/app/lib/toc_generator.rb b/app/lib/toc_generator.rb
new file mode 100644
index 000000000..c6e179557
--- /dev/null
+++ b/app/lib/toc_generator.rb
@@ -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
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 4383cbd05..4e865b850 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -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)
diff --git a/app/views/about/blocks.html.haml b/app/views/about/blocks.html.haml
deleted file mode 100644
index a81a4d1eb..000000000
--- a/app/views/about/blocks.html.haml
+++ /dev/null
@@ -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'
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 21431ef8e..4b3035ee8 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -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'