about summary refs log tree commit diff
path: root/app/views
diff options
context:
space:
mode:
Diffstat (limited to 'app/views')
-rw-r--r--app/views/about/_domain_allows.html.haml12
-rw-r--r--app/views/about/_registration.html.haml5
-rw-r--r--app/views/about/more.html.haml10
-rw-r--r--app/views/about/show.html.haml171
-rw-r--r--app/views/accounts/_header.html.haml5
-rw-r--r--app/views/accounts/show.html.haml10
-rw-r--r--app/views/admin/accounts/index.html.haml2
-rw-r--r--app/views/admin/domain_allows/new.html.haml1
-rw-r--r--app/views/admin/instances/index.html.haml10
-rw-r--r--app/views/admin/instances/show.html.haml7
-rw-r--r--app/views/admin/pending_accounts/index.html.haml2
-rw-r--r--app/views/admin/settings/edit.html.haml47
-rw-r--r--app/views/auth/registrations/new.html.haml5
-rw-r--r--app/views/custom_emojis/_custom_emoji.html.haml (renamed from app/views/admin/custom_emojis/_custom_emoji.html.haml)1
-rw-r--r--app/views/custom_emojis/index.html.haml (renamed from app/views/admin/custom_emojis/index.html.haml)41
-rw-r--r--app/views/custom_emojis/new.html.haml (renamed from app/views/admin/custom_emojis/new.html.haml)2
-rwxr-xr-xapp/views/layouts/application.html.haml5
-rw-r--r--app/views/layouts/public.html.haml7
-rw-r--r--app/views/settings/preferences/appearance/show.html.haml34
-rw-r--r--app/views/settings/preferences/filters/show.html.haml24
-rw-r--r--app/views/settings/preferences/notifications/show.html.haml5
-rw-r--r--app/views/settings/preferences/other/show.html.haml27
-rw-r--r--app/views/settings/preferences/privacy/show.html.haml40
-rw-r--r--app/views/settings/preferences/publishing/show.html.haml23
-rw-r--r--app/views/settings/profiles/show.html.haml29
-rw-r--r--app/views/statuses/_detailed_status.html.haml21
-rw-r--r--app/views/statuses/_simple_status.html.haml25
-rw-r--r--app/views/statuses/_status.html.haml9
-rw-r--r--app/views/statuses/show.html.haml4
29 files changed, 400 insertions, 184 deletions
diff --git a/app/views/about/_domain_allows.html.haml b/app/views/about/_domain_allows.html.haml
new file mode 100644
index 000000000..ab5755b41
--- /dev/null
+++ b/app/views/about/_domain_allows.html.haml
@@ -0,0 +1,12 @@
+%table
+  %thead
+    %tr
+      %th{colspan: 3}= t('about.unavailable_content_description.domain')
+  %tbody
+    - domain_allows.in_groups_of(3) do |group|
+      %tr
+      - group.each do |domain_allow|
+        %td.nowrap
+          - unless domain_allow.nil?
+            %span
+              %a{ title: domain_allow.domain, href: "https://#{domain_allow.domain}", rel: 'noopener nofollow' }= domain_allow.domain
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index 5d159e9e6..c3bd3ed60 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -6,14 +6,17 @@
       = f.simple_fields_for :account do |account_fields|
         = account_fields.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
 
+      = f.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_confirmation'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username_confirmation'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
       = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
       = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: closed_registrations?
       = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
+      = f.hidden_field :kobold, input_html: { :autocomplete => 'off' }
+
 
     - if approved_registrations?
       .fields-group
         = f.simple_fields_for :invite_request do |invite_request_fields|
-          = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false
+          = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: true
 
     .fields-group
       = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true, disabled: closed_registrations?
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 0a12ab8d6..0e4465a4a 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -42,13 +42,18 @@
   .column-3
     = render 'application/flashes'
 
-    - if @contents.blank? && (!display_blocks? || @blocks&.empty?)
+    - if @contents.blank? && ((!display_allows? || @allows&.empty?) && (!display_blocks? || @blocks&.empty?))
       = nothing_here
     - else
       .box-widget
         .rich-formatting
           = @contents.html_safe
 
+          - if display_allows? && !@allows.empty?
+            %h2#available-content= t('about.available_content')
+            %p= t('about.available_content_html')
+            = render partial: 'domain_allows', locals: { domain_allows: @allows }
+
           - if display_blocks? && !@blocks.empty?
             %h2#unavailable-content= t('about.unavailable_content')
 
@@ -78,5 +83,8 @@
               - item.children.each do |sub_item|
                 %li= link_to sub_item.title, "##{sub_item.anchor}"
 
+      - if display_allows? && !@allows.empty?
+        %li= link_to t('about.available_content'), '#available-content'
+
       - if display_blocks? && !@blocks.empty?
         %li= link_to t('about.unavailable_content'), '#unavailable-content'
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 565c4ed59..4cacb6f3c 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -3,77 +3,116 @@
 
 - content_for :header_tags do
   %link{ rel: 'canonical', href: about_url }/
+  %script{ src: '/registration.js', type: 'text/javascript', crossorigin: 'anonymous' }
   = render partial: 'shared/og'
 
-.landing
-  .landing__brand
-    = link_to root_url, class: 'brand' do
-      = svg_logo_full
-      %span.brand__tagline=t 'about.tagline'
+.grid-4
+  .column-0
+    .public-account-header.public-account-header--no-bar
+      .public-account-header__image
+        = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
 
-  .landing__grid
-    .landing__grid__column.landing__grid__column-registration
+  .column-1
+    .landing-page__call-to-action{ dir: 'ltr' }
+      .row
+        .row__information-board
+          .information-board__section
+            %span= t 'about.user_count_before'
+            %strong= number_with_delimiter @instance_presenter.user_count
+            %span= t 'about.user_count_after', count: @instance_presenter.user_count
+          .information-board__section
+            %span= t 'about.status_count_before'
+            %strong= number_with_delimiter @instance_presenter.status_count
+            %span= t 'about.status_count_after', count: @instance_presenter.status_count
+        .row__mascot
+          .landing-page__mascot
+            = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
+
+  .column-2
+    .contact-widget
+      %h4= t 'about.administered_by'
+
+      = account_link_to(@instance_presenter.contact_account)
+
+      - if @instance_presenter.site_contact_email.present?
+        %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
+      = render 'registration'
+
+    %br
+
+    - if @contents.blank? && ((!display_allows? || @allows&.empty?) && (!display_blocks? || @blocks&.empty?))
+      = nothing_here
+    - else
       .box-widget
-        = render 'registration'
-
-      .directory
-        - if Setting.profile_directory
-          .directory__tag
-            = optional_link_to Setting.profile_directory, explore_path do
-              %h4
-                = fa_icon 'address-book fw'
-                = t('about.discover_users')
-                %small= t('about.browse_directory')
-
-              .avatar-stack
-                - @instance_presenter.sample_accounts.each do |account|
-                  = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, alt: '', class: 'account__avatar'
-
-        - if Setting.timeline_preview
-          .directory__tag
-            = optional_link_to Setting.timeline_preview, public_timeline_path do
-              %h4
-                = fa_icon 'globe fw'
-                = t('about.see_whats_happening')
-                %small= t('about.browse_public_posts')
+        .rich-formatting
+          = @contents.html_safe
+
+          - if display_allows? && !@allows.empty?
+            %h2#available-content= t('about.available_content')
+            %p= t('about.available_content_html')
+            = render partial: 'domain_allows', locals: { domain_allows: @allows }
+
+          - if display_blocks? && !@blocks.empty?
+            %h2#unavailable-content= t('about.unavailable_content')
 
+            - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty?
+              %h3= t('about.unavailable_content_description.rejecting_media_title')
+              %p= t('about.unavailable_content_description.rejecting_media')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+            - if (blocks = @blocks.select(&:silence?)) && !blocks.empty?
+              %h3= t('about.unavailable_content_description.silenced_title')
+              %p= t('about.unavailable_content_description.silenced')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+            - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty?
+              %h3= t('about.unavailable_content_description.suspended_title')
+              %p= t('about.unavailable_content_description.suspended')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+
+  .column-4
+    .box-widget
+      = render 'login'
+
+    %br
+
+    %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_allows? && !@allows.empty?
+        %li= link_to t('about.available_content'), '#available-content'
+
+      - if display_blocks? && !@blocks.empty?
+        %li= link_to t('about.unavailable_content'), '#unavailable-content'
+
+    %br
+
+    .directory
+      - if Setting.profile_directory
         .directory__tag
-          = link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
+          = optional_link_to Setting.profile_directory, explore_path do
             %h4
-              = fa_icon 'tablet fw'
-              = t('about.get_apps')
-              %small= t('about.apps_platforms')
+              = fa_icon 'address-book fw'
+              = t('about.discover_users')
 
-    .landing__grid__column.landing__grid__column-login
-      .box-widget
-        = render 'login'
-
-      .hero-widget
-        .hero-widget__img
-          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
-
-        .hero-widget__text
-          %p
-            = @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
-            = link_to about_more_path do
-              = t('about.learn_more')
-              = fa_icon 'angle-double-right'
-
-        .hero-widget__footer
-          .hero-widget__footer__column
-            %h4= t 'about.administered_by'
-
-            = account_link_to @instance_presenter.contact_account
-
-          .hero-widget__footer__column
-            %h4= t 'about.server_stats'
-
-            .hero-widget__counters__wrapper
-              .hero-widget__counter
-                %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true
-                %span= t 'about.user_count_after', count: @instance_presenter.user_count
-              .hero-widget__counter
-                %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true
-                %span
-                  = t 'about.active_count_after'
-                  %abbr{ title: t('about.active_footnote') } *
+      .directory__tag
+        = link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
+          %h4
+            = fa_icon 'tablet fw'
+            = t('about.get_apps')
+
+    %br
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index 52fb0d946..27a29c061 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -13,19 +13,16 @@
             = fa_icon('lock') if account.locked?
       .public-account-header__tabs__tabs
         .details-counters
-          .counter{ class: active_nav_class(short_account_url(account), short_account_with_replies_url(account), short_account_media_url(account)) }
+          .counter{ class: active_nav_class(short_account_url(account), short_account_threads_url(account), short_account_with_replies_url(account), short_account_reblogs_url(account), short_account_mentions_url(account), short_account_media_url(account)) }
             = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do
-              %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
               %span.counter-label= t('accounts.posts', count: account.statuses_count)
 
           .counter{ class: active_nav_class(account_following_index_url(account)) }
             = link_to account_following_index_url(account), title: number_with_delimiter(account.following_count) do
-              %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
               %span.counter-label= t('accounts.following', count: account.following_count)
 
           .counter{ class: active_nav_class(account_followers_url(account)) }
             = link_to account_followers_url(account), title: hide_followers_count?(account) ? nil : number_with_delimiter(account.followers_count) do
-              %span.counter-number= hide_followers_count?(account) ? '-' : (number_to_human account.followers_count, strip_insignificant_zeros: true)
               %span.counter-label= t('accounts.followers', count: account.followers_count)
         .spacer
         .public-account-header__tabs__tabs__buttons
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 1a81b96f6..d38d57894 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -16,6 +16,9 @@
   = opengraph 'og:type', 'profile'
   = render 'og', account: @account, url: short_account_url(@account, only_path: false)
 
+- content_for :header_overrides do
+  - if @account&.user&.setting_style_css_profile.present?
+    = stylesheet_link_tag user_profile_css_path(id: @account.id), media: 'all'
 
 = render 'header', account: @account, with_bio: true
 
@@ -26,8 +29,13 @@
 
       .account__section-headline
         = active_link_to t('accounts.posts_tab_heading'), short_account_url(@account)
-        = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
+        = active_link_to t('accounts.threads'), short_account_threads_url(@account)
+        - if @account.show_replies?
+          = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account)
+        - if current_account.present? && @account.id != current_account.id
+          = active_link_to t('accounts.mentions'), short_account_mentions_url(@account)
         = active_link_to t('accounts.media'), short_account_media_url(@account)
+        = active_link_to t('accounts.reblogs'), short_account_reblogs_url(@account)
 
       - if user_signed_in? && @account.blocking?(current_account)
         .nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 8eac226e0..bff1f2b20 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -10,7 +10,7 @@
   .filter-subset
     %strong= t('admin.accounts.moderation.title')
     %ul
-      %li= link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), admin_pending_accounts_path
+      %li= link_to safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.confirmed.count)})"], ' '), admin_pending_accounts_path
       %li= filter_link_to t('admin.accounts.moderation.active'), silenced: nil, suspended: nil, pending: nil
       %li= filter_link_to t('admin.accounts.moderation.silenced'), silenced: '1', suspended: nil, pending: nil
       %li= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1', silenced: nil, pending: nil
diff --git a/app/views/admin/domain_allows/new.html.haml b/app/views/admin/domain_allows/new.html.haml
index 85ab7e464..0540765d7 100644
--- a/app/views/admin/domain_allows/new.html.haml
+++ b/app/views/admin/domain_allows/new.html.haml
@@ -6,6 +6,7 @@
 
   .fields-group
     = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), required: true
+    = f.input :hidden, wrapper: :with_label, label: t('admin.domain_allows.hidden')
 
   .actions
     = f.button :button, t('admin.domain_allows.add_new'), type: :submit
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 696ba3c7f..5aec735e4 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -2,19 +2,15 @@
   = t('admin.instances.title')
 
 - content_for :heading_actions do
-  - if whitelist_mode?
-    = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
-  - else
-    = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
+  = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
+  = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
 
 .filters
   .filter-subset
     %strong= t('admin.instances.moderation.title')
     %ul
       %li= filter_link_to t('admin.instances.moderation.all'), limited: nil
-
-      - unless whitelist_mode?
-        %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
+      %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
 
 - unless whitelist_mode?
   = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 92e14c0df..e5a5a6129 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -52,8 +52,11 @@
   %div
     - if @domain_allow
       = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
-    - elsif @domain_block
+    - else
+      = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path(_domain: @instance.domain), class: 'button'
+
+    - if @domain_block
       = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@domain_block), class: 'button'
       = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
     - else
-      = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
+      = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button button--destructive'
diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml
index 8101d7f99..2f73d12b4 100644
--- a/app/views/admin/pending_accounts/index.html.haml
+++ b/app/views/admin/pending_accounts/index.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('admin.pending_accounts.title', count: User.pending.count)
+  = t('admin.pending_accounts.title', count: User.pending.confirmed.count)
 
 = form_for(@form, url: batch_admin_pending_accounts_path) do |f|
   = hidden_field_tag :page, params[:page] || 1
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 108846ca9..0d36e4551 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -47,12 +47,11 @@
 
   %hr.spacer/
 
-  - unless whitelist_mode?
-    .fields-group
-      = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
+  .fields-group
+    = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
 
-    .fields-group
-      = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
+  .fields-group
+    = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
 
   .fields-group
     = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
@@ -60,27 +59,26 @@
   .fields-group
     = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
 
-  - unless whitelist_mode?
-    .fields-group
-      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
+  .fields-group
+    = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
 
-    .fields-group
-      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
+  .fields-group
+    = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
 
-    .fields-group
-      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
+  .fields-group
+    = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
 
-    .fields-group
-      = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
+  .fields-group
+    = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
 
-    .fields-group
-      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
+  .fields-group
+    = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
 
-    .fields-group
-      = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
+  .fields-group
+    = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
 
-    .fields-group
-      = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
+  .fields-group
+    = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
 
   .fields-group
     = f.input :hide_followers_count, as: :boolean, wrapper: :with_label, label: t('admin.settings.hide_followers_count.title'), hint: t('admin.settings.hide_followers_count.desc_html')
@@ -99,8 +97,11 @@
 
   %hr.spacer/
 
-  .fields-group
-    = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_allows, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_allows.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
   .fields-row
     .fields-row__column.fields-row__column-6.fields-group
@@ -112,7 +113,7 @@
     = f.input :outgoing_spoilers, wrapper: :with_label, label: t('admin.settings.outgoing_spoilers.title'), hint: t('admin.settings.outgoing_spoilers.desc_html')
 
   .fields-group
-    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
+    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 }
     = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
     = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index cc72b87ce..ab2d33d69 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -2,6 +2,7 @@
   = t('auth.register')
 
 - content_for :header_tags do
+  %script{ src: '/registration.js', type: 'text/javascript', crossorigin: 'anonymous' }
   = render partial: 'shared/og', locals: { description: description_for_sign_up }
 
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { novalidate: false }) do |f|
@@ -15,6 +16,7 @@
   = f.simple_fields_for :account do |ff|
     .fields-group
       = ff.input :username, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: t('simple_form.hints.defaults.username', domain: site_hostname)
+      = f.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_confirmation'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username_confirmation'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
 
   .fields-group
     = f.input :email, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
@@ -28,9 +30,10 @@
   - if approved_registrations? && !@invite.present?
     .fields-group
       = f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
-        = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: false
+        = invite_request_fields.input :text, as: :text, wrapper: :with_block_label, required: true
 
   = f.input :invite_code, as: :hidden
+  = f.hidden_field :kobold, input_html: { :autocomplete => 'off' }
 
   .fields-group
     = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), required: true
diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/custom_emojis/_custom_emoji.html.haml
index 526c844e9..e124373c6 100644
--- a/app/views/admin/custom_emojis/_custom_emoji.html.haml
+++ b/app/views/custom_emojis/_custom_emoji.html.haml
@@ -7,6 +7,7 @@
 
     .batch-table__row__content__text
       %samp= ":#{custom_emoji.shortcode}:"
+      %p.hint.muted-hint{ title: t('admin.custom_emojis.owner') }= custom_emoji.account_id.present? ? "@#{custom_emoji.account.username}" : t('admin.custom_emojis.unclaimed') if custom_emoji.local?
 
       - if custom_emoji.local?
         %span.account-role.bot= custom_emoji.category&.name || t('admin.custom_emojis.uncategorized')
diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/custom_emojis/index.html.haml
index b6cf7ba64..f81d91d53 100644
--- a/app/views/admin/custom_emojis/index.html.haml
+++ b/app/views/custom_emojis/index.html.haml
@@ -3,7 +3,9 @@
 
 - if can?(:create, :custom_emoji)
   - content_for :heading_actions do
-    = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button'
+    = link_to t('admin.custom_emojis.upload'), new_custom_emoji_path, class: 'button'
+
+%p= t('admin.custom_emojis.ownership_warning')
 
 .filters
   .filter-subset
@@ -11,17 +13,27 @@
     %ul
       %li= filter_link_to t('admin.accounts.location.all'), local: nil, remote: nil
       %li
-        - if selected? local: '1', remote: nil
-          = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil}, {local: '1', remote: nil}
+        - if selected? local: '1', remote: nil, claimed: nil, unclaimed: nil
+          = filter_link_to t('admin.accounts.location.local'), {local: nil, remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: nil, unclaimed: nil}
+        - else
+          = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil, claimed: nil, unclaimed: nil
+      %li
+        - if selected? remote: '1', local: nil, claimed: nil, unclaimed: nil
+          = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil, claimed: nil, unclaimed: nil}, {remote: '1', local: nil, claimed: nil, unclaimed: nil}
         - else
-          = filter_link_to t('admin.accounts.location.local'), local: '1', remote: nil
+          = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil, claimed: nil, unclained: nil
       %li
-        - if selected? remote: '1', local: nil
-          = filter_link_to t('admin.accounts.location.remote'), {remote: nil, local: nil}, {remote: '1', local: nil}
+        - if selected? local: '1', remote: nil, claimed: '1', unclaimed: nil
+          = filter_link_to t('admin.accounts.location.claimed'), {local: '1', remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: '1', unclaimed: nil}
         - else
-          = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil
+          = filter_link_to t('admin.accounts.location.claimed'), local: '1', remote: nil, claimed: '1', unclaimed: nil
+      %li
+        - if selected? local: '1', remote: nil, claimed: nil, unclaimed: '1'
+          = filter_link_to t('admin.accounts.location.unclaimed'), {local: '1', remote: nil, claimed: nil, unclaimed: nil}, {local: '1', remote: nil, claimed: nil, unclaimed: '1'}
+        - else
+          = filter_link_to t('admin.accounts.location.unclaimed'), local: '1', remote: nil, claimed: nil, unclaimed: '1'
 
-= form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do
+= form_tag custom_emojis_url, method: 'GET', class: 'simple_form' do
   .fields-group
     - CustomEmojiFilter::KEYS.each do |key|
       = hidden_field_tag key, params[key] if params[key].present?
@@ -32,9 +44,9 @@
 
     .actions
       %button.button= t('admin.accounts.search')
-      = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button negative'
+      = link_to t('admin.accounts.reset'), custom_emojis_path, class: 'button negative'
 
-= form_for(@form, url: batch_admin_custom_emojis_path) do |f|
+= form_for(@form, url: batch_custom_emojis_path) do |f|
   = hidden_field_tag :page, params[:page] || 1
 
   - CustomEmojiFilter::KEYS.each do |key|
@@ -45,6 +57,10 @@
       %label.batch-table__toolbar__select.batch-checkbox-all
         = check_box_tag :batch_checkbox_all, nil, false
       .batch-table__toolbar__actions
+        = f.button safe_join([fa_icon('lock'), t('admin.custom_emojis.claim')]), name: :claim, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('unlock'), t('admin.custom_emojis.unclaim')]), name: :unclaim, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
         - if params[:local] == '1'
           = f.button safe_join([fa_icon('save'), t('generic.save_changes')]), name: :update, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
@@ -56,10 +72,9 @@
 
         = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
-        - if can?(:destroy, :custom_emoji)
-          = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+        = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
-        - if can?(:copy, :custom_emoji) && params[:local] != '1'
+        - if params[:local] != '1'
           = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
 
     - if params[:local] == '1'
diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/custom_emojis/new.html.haml
index e15a07cb8..fe9d8fc64 100644
--- a/app/views/admin/custom_emojis/new.html.haml
+++ b/app/views/custom_emojis/new.html.haml
@@ -1,7 +1,7 @@
 - content_for :page_title do
   = t('.title')
 
-= simple_form_for @custom_emoji, url: admin_custom_emojis_path do |f|
+= simple_form_for @custom_emoji, url: custom_emojis_path do |f|
   = render 'shared/error_messages', object: @custom_emoji
 
   .fields-group
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 32681773f..ce152f407 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -41,6 +41,11 @@
     - if Setting.custom_css.present?
       = stylesheet_link_tag custom_css_path, media: 'all'
 
+    - if current_account&.user.present?
+      = stylesheet_link_tag user_webapp_css_path(current_account.id), media: 'all'
+
+    = yield :header_overrides
+
   %body{ class: body_classes }
     = content_for?(:content) ? yield(:content) : yield
 
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index eaa0437c2..e820285cb 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -10,10 +10,9 @@
             = link_to root_url, class: 'brand' do
               = svg_logo_full
 
-            - unless whitelist_mode?
-              = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
-              = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
-              = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
+            = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
+            = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
+            = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
 
           .nav-center
 
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index ccea2e9b7..7658b8017 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -14,10 +14,8 @@
 
   %h4= t 'appearance.advanced_web_interface'
 
-  %p.hint= t 'appearance.advanced_web_interface_hint'
-
   .fields-group
-    = f.input :setting_advanced_layout, as: :boolean, wrapper: :with_label, hint: false
+    = f.input :setting_advanced_layout, as: :boolean, wrapper: :with_label, hint: t('appearance.advanced_web_interface_hint')
 
   %h4= t 'appearance.animations_and_accessibility'
 
@@ -31,6 +29,12 @@
     = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
     = f.input :setting_system_emoji_font, as: :boolean, wrapper: :with_label
 
+  .fields-group
+    = f.input :setting_style_wide_media, as: :boolean, wrapper: :with_label
+    = f.input :setting_style_dashed_nest, as: :boolean, wrapper: :with_label
+    = f.input :setting_style_underline_a, as: :boolean, wrapper: :with_label
+    = f.input :setting_style_lowercase, as: :boolean, wrapper: :with_label
+
   %h4= t 'appearance.toot_layout'
 
   .fields-group
@@ -60,5 +64,29 @@
   .fields-group
     = f.input :setting_expand_spoilers, as: :boolean, wrapper: :with_label
 
+  %h4= t 'appearance.custom_css'
+
+  .fields-group
+    = f.input :setting_style_css_profile, as: :text, wrapper: :with_label
+
+    - if current_user.setting_style_css_profile_errors.present?
+      %p
+        %strong= t('appearance.custom_css_error')
+
+      %ul
+        - current_user.setting_style_css_profile_errors.each do |error|
+          %li.hint= error
+
+  .fields-group
+    = f.input :setting_style_css_webapp, as: :text, wrapper: :with_label
+
+    - if current_user&.setting_style_css_webapp_errors.present?
+      %p
+        %strong= t('appearance.custom_css_error')
+
+      %ul
+        - current_user.setting_style_css_webapp_errors.each do |error|
+          %li.hint= error
+
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/preferences/filters/show.html.haml b/app/views/settings/preferences/filters/show.html.haml
new file mode 100644
index 000000000..17fa4c4a6
--- /dev/null
+++ b/app/views/settings/preferences/filters/show.html.haml
@@ -0,0 +1,24 @@
+- content_for :page_title do
+  = t('settings.preferences')
+
+- content_for :heading_actions do
+  = button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
+
+= simple_form_for current_user, url: settings_preferences_filters_path, html: { method: :put, id: 'edit_preferences' } do |f|
+  = render 'shared/error_messages', object: current_user
+
+  %h4= t 'preferences.filtering'
+
+  .fields-group
+    = f.input :setting_home_reblogs, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :setting_filter_unknown, as: :boolean, wrapper: :with_label
+
+  %h4= t 'preferences.public_timelines'
+
+  .fields-group
+    = f.input :chosen_languages, collection: filterable_languages.sort, wrapper: :with_block_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index d7cc1ed5d..9cbdfc3cd 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -7,6 +7,11 @@
 = simple_form_for current_user, url: settings_preferences_notifications_path, html: { method: :put, id: 'edit_notification' } do |f|
   = render 'shared/error_messages', object: current_user
 
+  %h4= t 'notifications.web_settings'
+
+  .fields-group
+    = f.input :setting_web_push, as: :boolean, wrapper: :with_label
+
   %h4= t 'notifications.email_events'
 
   %p.hint= t 'notifications.email_events_hint'
diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml
index 3b5c7016d..b85b24a58 100644
--- a/app/views/settings/preferences/other/show.html.haml
+++ b/app/views/settings/preferences/other/show.html.haml
@@ -7,41 +7,16 @@
 = simple_form_for current_user, url: settings_preferences_other_path, html: { method: :put, id: 'edit_preferences' } do |f|
   = render 'shared/error_messages', object: current_user
 
-  .fields-group
-    = f.input :setting_noindex, as: :boolean, wrapper: :with_label
-
-  .fields-group
-    = f.input :setting_hide_network, as: :boolean, wrapper: :with_label
-
-  .fields-group
-    = f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
-
-  - unless Setting.hide_followers_count
-    .fields-group
-      = f.input :setting_hide_followers_count, as: :boolean, wrapper: :with_label
-
   %h4= t 'preferences.posting_defaults'
 
-  .fields-row
-    .fields-group.fields-row__column.fields-row__column-6
-      = f.input :setting_default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false
-
-    .fields-group.fields-row__column.fields-row__column-6
-      = f.input :setting_default_language, collection: [nil] + filterable_languages.sort, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.language_detection') : human_locale(locale) }, required: false, include_blank: false, hint: false
-
   .fields-group
     = f.input :setting_default_sensitive, as: :boolean, wrapper: :with_label
 
   .fields-group
-    = f.input :setting_show_application, as: :boolean, wrapper: :with_label, recommended: true
+    = f.input :setting_default_language, collection: [nil] + filterable_languages.sort, wrapper: :with_label, label_method: lambda { |locale| locale.nil? ? I18n.t('statuses.language_detection') : human_locale(locale) }, required: false, include_blank: false, hint: false
 
   .fields-group
     = f.input :setting_default_content_type, collection: ['text/plain', 'text/markdown', 'text/html'], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.setting_default_content_type_#{item.split('/')[1]}"), content_tag(:span, t("simple_form.hints.defaults.setting_default_content_type_#{item.split('/')[1]}"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
-  %h4= t 'preferences.public_timelines'
-
-  .fields-group
-    = f.input :chosen_languages, collection: filterable_languages.sort, wrapper: :with_block_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/preferences/privacy/show.html.haml b/app/views/settings/preferences/privacy/show.html.haml
new file mode 100644
index 000000000..8f7199665
--- /dev/null
+++ b/app/views/settings/preferences/privacy/show.html.haml
@@ -0,0 +1,40 @@
+- content_for :page_title do
+  = t('settings.preferences')
+
+- content_for :heading_actions do
+  = button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
+
+= simple_form_for current_user, url: settings_preferences_privacy_path, html: { method: :put, id: 'edit_preferences' } do |f|
+  = render 'shared/error_messages', object: current_user
+
+  %h4= t 'preferences.privacy'
+
+  .fields-row
+    .fields-group.fields-row__column.fields-row__column-6
+      = f.input :setting_max_history_public, collection: Status::HISTORY_VALUES, wrapper: :with_label, label_method: lambda { |m| I18n.t("history.#{m}") }, required: false, include_blank: false
+
+    .fields-group.fields-row__column.fields-row__column-6
+      = f.input :setting_max_history_private, collection: Status::HISTORY_VALUES, wrapper: :with_label, label_method: lambda { |m| I18n.t("history.#{m}") }, required: false, include_blank: false
+
+
+  .fields-group
+    = f.input :setting_default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false
+
+  .fields-group
+    = f.input :setting_show_application, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :setting_noindex, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :setting_hide_network, as: :boolean, wrapper: :with_label
+
+  .fields-group
+    = f.input :setting_rss_disabled, as: :boolean, wrapper: :with_label
+
+  - unless Setting.hide_followers_count
+    .fields-group
+      = f.input :setting_hide_followers_count, as: :boolean, wrapper: :with_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/preferences/publishing/show.html.haml b/app/views/settings/preferences/publishing/show.html.haml
new file mode 100644
index 000000000..9fe76f385
--- /dev/null
+++ b/app/views/settings/preferences/publishing/show.html.haml
@@ -0,0 +1,23 @@
+- content_for :page_title do
+  = t('settings.preferences')
+
+- content_for :heading_actions do
+  = button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
+
+= simple_form_for current_user, url: settings_preferences_publishing_path, html: { method: :put, id: 'edit_preferences' } do |f|
+  = render 'shared/error_messages', object: current_user
+
+  %h4= t 'preferences.advanced_publishing'
+
+  .fields-row
+    .fields-group.fields-row__column.fields-row__column-6
+      = f.input :setting_manual_publish, as: :boolean, wrapper: :with_label
+      = f.input :setting_unpublish_on_delete, as: :boolean, wrapper: :with_label
+
+    .fields-group.fields-row__column.fields-row__column-6
+      = f.input :setting_publish_in, collection: Status::TIMER_VALUES, wrapper: :with_label, label_method: lambda { |m| I18n.t("timer.#{m}") }, required: false, include_blank: false, hint: false
+      = f.input :setting_unpublish_in, collection: Status::TIMER_VALUES, wrapper: :with_label, label_method: lambda { |m| I18n.t("timer.#{m}") }, required: false, include_blank: false, hint: false
+      = f.input :setting_unpublish_delete, as: :boolean, wrapper: :with_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 6061e9cfd..1b7765f32 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -24,14 +24,37 @@
   %hr.spacer/
 
   .fields-group
-    = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
+    = f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
+  
+  %h4= t 'settings.profiles.privacy'
+
+  %p.hint= t 'settings.profiles.privacy_html'
 
   .fields-group
-    = f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
+    = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
 
   - if Setting.profile_directory
     .fields-group
-      = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
+      = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable')
+
+  .fields-group
+    = f.input :show_replies, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.show_replies')
+  
+  .fields-group
+    = f.input :show_unlisted, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.show_unlisted')
+
+  .fields-group
+    = f.input :private, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.private')
+
+  .fields-group
+    = f.input :require_auth, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.require_auth')
+
+  %h4= t 'settings.profiles.advanced_privacy'
+
+  %p.hint= t 'settings.profiles.advanced_privacy_html'
+
+  .fields-group
+    = f.input :require_dereference, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.require_dereference_html')
 
   %hr.spacer/
 
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index a4dd8534f..b66a3c5ee 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -15,13 +15,16 @@
 
   = account_action_button(status.account)
 
-  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
-    - if status.spoiler_text?
-      %p<
-        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
+  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.title? || status.spoiler_text?) }
+    - if status.title? || status.spoiler_text?
+      %div.spoiler
+        = fa_icon 'info-circle fw'
+        %span.p-summary= Formatter.instance.format_spoiler(status, autoplay: autoplay)
+    - if status.title? || status.spoiler_text? || parent_status&.spoiler_text?
+      %div
         %button.status__content__spoiler-link= t('statuses.show_more')
     .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
-      = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
+      = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay, article_content: true)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
           = render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
@@ -29,17 +32,17 @@
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 670, height: 380, detailed: true, inline: true, alt: video.description do
+      = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), width: 670, height: 380, detailed: true, inline: true, alt: video.description do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - elsif status.media_attachments.first.audio?
       - audio = status.media_attachments.first
-      = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
+      = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta&.dig('colors', 'background'), foregroundColor: audio.file.meta&.dig('colors', 'foreground'), accentColor: audio.file.meta&.dig('colors', 'accent'), width: 670, height: 380, alt: audio.description, duration: audio.file.meta&.dig('original', 'duration') do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
-      = react_component :media_gallery, height: 380, sensitive: sensitized?(status, current_account), standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
+      = react_component :media_gallery, height: 380, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
   - elsif status.preview_card
-    = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
+    = react_component :card, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
 
   .detailed-status__meta
     %data.dt-published{ value: status.created_at.to_time.iso8601 }
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 199061c46..8a5b65b64 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -21,13 +21,20 @@
           %span.display-name__account
             = acct(status.account)
             = fa_icon('lock') if status.account.locked?
-  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
-    - if status.spoiler_text?
-      %p<
-        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
+  .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.title? || status.spoiler_text? || parent_status&.spoiler_text?) }<
+    - if parent_status&.spoiler_text?
+      %div.spoiler.reblog-spoiler
+        = fa_icon 'retweet fw'
+        %span.p-summary= Formatter.instance.format_spoiler(parent_status, autoplay: autoplay)
+    - if status.title? || status.spoiler_text?
+      %div.spoiler
+        = fa_icon 'info-circle fw'
+        %span.p-summary= Formatter.instance.format_spoiler(status, autoplay: autoplay)
+    - if status.title? || status.spoiler_text? || parent_status&.spoiler_text?
+      %div
         %button.status__content__spoiler-link= t('statuses.show_more')
     .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }<
-      = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
+      = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay, article_content: true)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
           = render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
@@ -35,17 +42,17 @@
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 610, height: 343, inline: true, alt: video.description do
+      = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), width: 610, height: 343, inline: true, alt: video.description do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - elsif status.media_attachments.first.audio?
       - audio = status.media_attachments.first
-      = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
+      = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta&.dig('colors', 'background'), foregroundColor: audio.file.meta&.dig('colors', 'foreground'), accentColor: audio.file.meta&.dig('colors', 'accent'), width: 610, height: 343, alt: audio.description, duration: audio.file.meta&.dig('original', 'duration') do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
-      = react_component :media_gallery, height: 343, sensitive: sensitized?(status, current_account), autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
+      = react_component :media_gallery, height: 343, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
         = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
   - elsif status.preview_card
-    = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
+    = react_component :card, sensitive: sensitized?(status, current_account) || sensitized?(parent_status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
 
   - if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id
     = link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do
diff --git a/app/views/statuses/_status.html.haml b/app/views/statuses/_status.html.haml
index 650f9b679..64f92782b 100644
--- a/app/views/statuses/_status.html.haml
+++ b/app/views/statuses/_status.html.haml
@@ -27,19 +27,12 @@
     .status__prepend
       .status__prepend-icon-wrapper
         %i.status__prepend-icon.fa.fa-fw.fa-retweet
-      %span
-        = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
-          %bdi
-            %strong.emojify= display_name(status.account, custom_emojify: true)
-        = t('stream_entries.reblogged')
   - elsif pinned
     .status__prepend
       .status__prepend-icon-wrapper
         %i.status__prepend-icon.fa.fa-fw.fa-thumb-tack
-      %span
-        = t('stream_entries.pinned')
 
-  = render (centered ? 'statuses/detailed_status' : 'statuses/simple_status'), status: status.proper, autoplay: autoplay
+  = render (centered ? 'statuses/detailed_status' : 'statuses/simple_status'), status: status.proper, autoplay: autoplay, parent_status: status
 
 - if include_threads
   - if @since_descendant_thread_id
diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml
index 7ef7b09a2..b7c2ed68d 100644
--- a/app/views/statuses/show.html.haml
+++ b/app/views/statuses/show.html.haml
@@ -17,6 +17,10 @@
   = render 'og_description', activity: @status
   = render 'og_image', activity: @status, account: @account
 
+- content_for :header_overrides do
+  - if @account&.user&.setting_style_css_profile.present?
+    = stylesheet_link_tag user_profile_css_path(id: @account.id), media: 'all'
+
 .grid
   .column-0
     .activity-stream.h-entry