Преглед изворни кода

Add table of contents to about page (#11885)

Move public domain blocks information to about page
master^2
Eugen Rochko пре 4 година
committed by GitHub
родитељ
комит
d930eb88b6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 измењених фајлова са 322 додато и 209 уклоњено
  1. +15
    -28
      app/controllers/about_controller.rb
  2. +61
    -77
      app/javascript/styles/mastodon/about.scss
  3. +62
    -0
      app/javascript/styles/mastodon/containers.scss
  4. +60
    -23
      app/javascript/styles/mastodon/widgets.scss
  5. +69
    -0
      app/lib/toc_generator.rb
  6. +1
    -0
      app/models/domain_block.rb
  7. +0
    -48
      app/views/about/blocks.html.haml
  8. +47
    -12
      app/views/about/more.html.haml
  9. +7
    -20
      config/locales/en.yml
  10. +0
    -1
      config/routes.rb

+ 15
- 28
app/controllers/about_controller.rb Прегледај датотеку

@@ -3,9 +3,7 @@
class AboutController < ApplicationController class AboutController < ApplicationController
layout 'public' 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_body_classes, only: :show
before_action :set_instance_presenter before_action :set_instance_presenter
before_action :set_expires_in, only: [:show, :more, :terms] before_action :set_expires_in, only: [:show, :more, :terms]
@@ -16,15 +14,20 @@ class AboutController < ApplicationController


def more def more
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor] 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 end


def terms; 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 private


@@ -32,28 +35,14 @@ class AboutController < ApplicationController
not_found if whitelist_mode? not_found if whitelist_mode?
end 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 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 end


helper_method :block_severity_text
helper_method :public_fetch_mode?

def new_user def new_user
User.new.tap do |user| User.new.tap do |user|
user.build_account user.build_account
@@ -61,8 +50,6 @@ class AboutController < ApplicationController
end end
end end


helper_method :new_user

def set_instance_presenter def set_instance_presenter
@instance_presenter = InstancePresenter.new @instance_presenter = InstancePresenter.new
end end


+ 61
- 77
app/javascript/styles/mastodon/about.scss Прегледај датотеку

@@ -17,109 +17,102 @@ $small-breakpoint: 960px;


.rich-formatting { .rich-formatting {
font-family: $font-sans-serif, sans-serif; font-family: $font-sans-serif, sans-serif;
font-size: 16px;
font-size: 14px;
font-weight: 400; font-weight: 400;
font-size: 16px;
line-height: 30px;
line-height: 1.7;
word-wrap: break-word;
color: $darker-text-color; color: $darker-text-color;
padding-right: 10px;


a { a {
color: $highlight-text-color; color: $highlight-text-color;
text-decoration: underline; text-decoration: underline;

&:hover,
&:focus,
&:active {
text-decoration: none;
}
} }


p, p,
li { 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; color: $darker-text-color;
}


a {
color: $highlight-text-color;
text-decoration: underline;
}
p {
margin-top: 0;
margin-bottom: .85em;


&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }


strong,
em {
strong {
font-weight: 700; 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; 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-family: $font-display, sans-serif;
font-size: 22px;
line-height: 26px;
margin-top: 1.275em;
margin-bottom: .85em;
font-weight: 500; font-weight: 500;
margin-bottom: 20px;
color: $secondary-text-color; color: $secondary-text-color;
} }


h1 {
font-size: 2em;
}

h2 {
font-size: 1.75em;
}

h3 { 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 { 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, ul,
ol { ol {
margin-left: 20px;
margin: 0;
padding: 0;
padding-left: 2em;
margin-bottom: 0.85em;


&[type='a'] { &[type='a'] {
list-style-type: lower-alpha; 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 { hr {
width: 100%; width: 100%;
height: 0; height: 0;
border: 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 { &.spacer {
height: 1px; height: 1px;
border: 0; border: 0;
} }
} }

& > :first-child {
margin-top: 0;
}
} }


.information-board { .information-board {
@@ -416,7 +400,7 @@ $small-breakpoint: 960px;
} }


&__call-to-action { &__call-to-action {
background: darken($ui-base-color, 4%);
background: $ui-base-color;
border-radius: 4px; border-radius: 4px;
padding: 25px 40px; padding: 25px 40px;
overflow: hidden; overflow: hidden;


+ 62
- 0
app/javascript/styles/mastodon/containers.scss Прегледај датотеку

@@ -141,6 +141,63 @@
grid-row: 3; 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 { .landing-page__call-to-action {
min-height: 100%; min-height: 100%;
} }
@@ -190,6 +247,11 @@


.column-3 { .column-3 {
grid-column: 1; grid-column: 1;
grid-row: 5;
}

.column-4 {
grid-column: 1;
grid-row: 4; grid-row: 4;
} }
} }


+ 60
- 23
app/javascript/styles/mastodon/widgets.scss Прегледај датотеку

@@ -128,41 +128,43 @@
margin-bottom: 10px; 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 { .contact-widget {
min-height: 100%;
font-size: 15px; font-size: 15px;
color: $darker-text-color; color: $darker-text-color;
line-height: 20px; line-height: 20px;
word-wrap: break-word; word-wrap: break-word;
font-weight: 400; 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
- 0
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

+ 1
- 0
app/models/domain_block.rb Прегледај датотеку

@@ -26,6 +26,7 @@ class DomainBlock < ApplicationRecord


scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } 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 :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 class << self
def suspend?(domain) def suspend?(domain)


+ 0
- 48
app/views/about/blocks.html.haml Прегледај датотеку

@@ -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'

+ 47
- 12
app/views/about/more.html.haml Прегледај датотеку

@@ -5,7 +5,7 @@
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous' = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
= render partial: 'shared/og' = render partial: 'shared/og'


.grid-3
.grid-4
.column-0 .column-0
.public-account-header.public-account-header--no-bar .public-account-header.public-account-header--no-bar
.public-account-header__image .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: '' = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''


.column-2 .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) = account_link_to(@instance_presenter.contact_account)


- if @instance_presenter.site_contact_email.present? - 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 .column-3
= render 'application/flashes' = 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'

+ 7
- 20
config/locales/en.yml Прегледај датотеку

@@ -17,9 +17,6 @@ en:
contact_unavailable: N/A contact_unavailable: N/A
discover_users: Discover users discover_users: Discover users
documentation: Documentation documentation: Documentation
extended_description_html: |
<h3>A good place for rules</h3>
<p>The extended description has not been set up yet.</p>
federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond. federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
generic_description: "%{domain} is one server in the network" generic_description: "%{domain} is one server in the network"
get_apps: Try a mobile app get_apps: Try a mobile app
@@ -38,6 +35,13 @@ en:
status_count_before: Who authored status_count_before: Who authored
tagline: Follow friends and discover new ones tagline: Follow friends and discover new ones
terms: Terms of service terms: Terms of service
unavailable_content: Unavailable content
unavailable_content_description:
reason: 'Reason:'
rejecting_media: Media files from this server will not be processed and and no thumbnails will be displayed, requiring manual click-through to the other server.
silenced: Posts from this server will not show up anywhere except your home feed if you follow the author.
suspended: You won't be able to follow anyone from this server, and no data from it will be processed or stored, and no data exchanged.
unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.
user_count_after: user_count_after:
one: user one: user
other: users other: users
@@ -661,23 +665,6 @@ en:
directory: Profile directory directory: Profile directory
explanation: Discover users based on their interests explanation: Discover users based on their interests
explore_mastodon: Explore %{title} explore_mastodon: Explore %{title}
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: domain_validator:
invalid_domain: is not a valid domain name invalid_domain: is not a valid domain name
errors: errors:


+ 0
- 1
config/routes.rb Прегледај датотеку

@@ -441,7 +441,6 @@ Rails.application.routes.draw do


get '/about', to: 'about#show' get '/about', to: 'about#show'
get '/about/more', to: 'about#more' get '/about/more', to: 'about#more'
get '/about/blocks', to: 'about#blocks'
get '/terms', to: 'about#terms' get '/terms', to: 'about#terms'


match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false


Loading…
Откажи
Сачувај