forked from cybrespace/mastodon
		
	Add table of contents to about page (#11885)
Move public domain blocks information to about page
This commit is contained in:
		
							parent
							
								
									e1066cd431
								
							
						
					
					
						commit
						d930eb88b6
					
				
					 10 changed files with 328 additions and 215 deletions
				
			
		| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,9 +17,6 @@ en:
 | 
			
		|||
    contact_unavailable: N/A
 | 
			
		||||
    discover_users: Discover users
 | 
			
		||||
    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.
 | 
			
		||||
    generic_description: "%{domain} is one server in the network"
 | 
			
		||||
    get_apps: Try a mobile app
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +35,13 @@ en:
 | 
			
		|||
    status_count_before: Who authored
 | 
			
		||||
    tagline: Follow friends and discover new ones
 | 
			
		||||
    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:
 | 
			
		||||
      one: user
 | 
			
		||||
      other: users
 | 
			
		||||
| 
						 | 
				
			
			@ -661,23 +665,6 @@ en:
 | 
			
		|||
    directory: Profile directory
 | 
			
		||||
    explanation: Discover users based on their interests
 | 
			
		||||
    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:
 | 
			
		||||
    invalid_domain: is not a valid domain name
 | 
			
		||||
  errors:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -441,7 +441,6 @@ Rails.application.routes.draw do
 | 
			
		|||
 | 
			
		||||
  get '/about',        to: 'about#show'
 | 
			
		||||
  get '/about/more',   to: 'about#more'
 | 
			
		||||
  get '/about/blocks', to: 'about#blocks'
 | 
			
		||||
  get '/terms',        to: 'about#terms'
 | 
			
		||||
 | 
			
		||||
  match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue