From 0396acf39ea902688374fac65fa7ef5dc4c05512 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 25 Aug 2022 20:39:40 +0200
Subject: Add audit log entries for user roles (#19040)
* Refactor audit log schema
* Add audit log entries for user roles
---
app/helpers/admin/action_logs_helper.rb | 61 ++++++++-------------------------
1 file changed, 14 insertions(+), 47 deletions(-)
(limited to 'app/helpers')
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 47eeeaac3..3e9fe17f4 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -2,64 +2,31 @@
module Admin::ActionLogsHelper
def log_target(log)
- if log.target
- linkable_log_target(log.target)
- else
- log_target_from_history(log.target_type, log.recorded_changes)
- end
- end
-
- private
-
- def linkable_log_target(record)
- case record.class.name
+ case log.target_type
when 'Account'
- link_to record.acct, admin_account_path(record.id)
+ link_to log.human_identifier, admin_account_path(log.target_id)
when 'User'
- link_to record.account.acct, admin_account_path(record.account_id)
+ link_to log.human_identifier, admin_account_path(log.route_param)
+ when 'UserRole'
+ link_to log.human_identifier, admin_roles_path(log.target_id)
when 'CustomEmoji'
- record.shortcode
+ log.human_identifier
when 'Report'
- link_to "##{record.id}", admin_report_path(record)
+ link_to "##{log.human_identifier}", admin_report_path(log.target_id)
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
- link_to record.domain, "https://#{record.domain}"
+ link_to log.human_identifier, "https://#{log.human_identifier}"
when 'Status'
- link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
+ link_to log.human_identifier, log.permalink
when 'AccountWarning'
- link_to record.target_account.acct, admin_account_path(record.target_account_id)
+ link_to log.human_identifier, admin_account_path(log.target_id)
when 'Announcement'
- link_to truncate(record.text), edit_admin_announcement_path(record.id)
+ link_to truncate(log.human_identifier), edit_admin_announcement_path(log.target_id)
when 'IpBlock'
- "#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})"
+ log.human_identifier
when 'Instance'
- record.domain
+ log.human_identifier
when 'Appeal'
- link_to record.account.acct, disputes_strike_path(record.strike)
- end
- end
-
- def log_target_from_history(type, attributes)
- case type
- when 'User'
- attributes['username']
- when 'CustomEmoji'
- attributes['shortcode']
- when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
- link_to attributes['domain'], "https://#{attributes['domain']}"
- when 'Status'
- tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
-
- if tmp_status.account
- link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
- else
- I18n.t('admin.action_logs.deleted_status')
- end
- when 'Announcement'
- truncate(attributes['text'].is_a?(Array) ? attributes['text'].last : attributes['text'])
- when 'IpBlock'
- "#{attributes['ip']}/#{attributes['ip'].prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{attributes['severity']}")})"
- when 'Instance'
- attributes['domain']
+ link_to log.human_identifier, disputes_strike_path(log.route_param)
end
end
end
--
cgit
From c556c3a0d1e54a6b07bbdd8f76cbb43672a91fd1 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sun, 28 Aug 2022 03:31:54 +0200
Subject: Add admin API for managing canonical e-mail blocks (#19067)
---
.../v1/admin/canonical_email_blocks_controller.rb | 99 ++++++++++++++++++++++
app/helpers/admin/action_logs_helper.rb | 8 +-
app/models/admin/action_log_filter.rb | 5 ++
app/models/canonical_email_block.rb | 17 ++--
app/policies/canonical_email_block_policy.rb | 23 +++++
.../rest/admin/canonical_email_block_serializer.rb | 9 ++
config/locales/en.yml | 6 ++
config/routes.rb | 6 ++
...95229_change_canonical_email_blocks_nullable.rb | 5 ++
db/schema.rb | 4 +-
lib/mastodon/canonical_email_blocks_cli.rb | 31 ++-----
11 files changed, 177 insertions(+), 36 deletions(-)
create mode 100644 app/controllers/api/v1/admin/canonical_email_blocks_controller.rb
create mode 100644 app/policies/canonical_email_block_policy.rb
create mode 100644 app/serializers/rest/admin/canonical_email_block_serializer.rb
create mode 100644 db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
(limited to 'app/helpers')
diff --git a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb
new file mode 100644
index 000000000..bf8a6a131
--- /dev/null
+++ b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
+ include Authorization
+ include AccountableConcern
+
+ LIMIT = 100
+
+ before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:canonical_email_blocks' }, only: [:index, :show, :test]
+ before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:canonical_email_blocks' }, except: [:index, :show, :test]
+
+ before_action :set_canonical_email_blocks, only: :index
+ before_action :set_canonical_email_blocks_from_test, only: [:test]
+ before_action :set_canonical_email_block, only: [:show, :destroy]
+
+ after_action :verify_authorized
+ after_action :insert_pagination_headers, only: :index
+
+ PAGINATION_PARAMS = %i(limit).freeze
+
+ def index
+ authorize :canonical_email_block, :index?
+ render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
+ end
+
+ def show
+ authorize @canonical_email_block, :show?
+ render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
+ end
+
+ def test
+ authorize :canonical_email_block, :test?
+ render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
+ end
+
+ def create
+ authorize :canonical_email_block, :create?
+
+ @canonical_email_block = CanonicalEmailBlock.create!(resource_params)
+ log_action :create, @canonical_email_block
+
+ render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
+ end
+
+ def destroy
+ authorize @canonical_email_block, :destroy?
+
+ @canonical_email_block.destroy!
+ log_action :destroy, @canonical_email_block
+
+ render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
+ end
+
+ private
+
+ def resource_params
+ params.permit(:canonical_email_hash, :email)
+ end
+
+ def set_canonical_email_blocks
+ @canonical_email_blocks = CanonicalEmailBlock.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
+ end
+
+ def set_canonical_email_blocks_from_test
+ @canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email])
+ end
+
+ def set_canonical_email_block
+ @canonical_email_block = CanonicalEmailBlock.find(params[:id])
+ end
+
+ def insert_pagination_headers
+ set_pagination_headers(next_path, prev_path)
+ end
+
+ def next_path
+ api_v1_admin_canonical_email_blocks_url(pagination_params(max_id: pagination_max_id)) if records_continue?
+ end
+
+ def prev_path
+ api_v1_admin_canonical_email_blocks_url(pagination_params(min_id: pagination_since_id)) unless @canonical_email_blocks.empty?
+ end
+
+ def pagination_max_id
+ @canonical_email_blocks.last.id
+ end
+
+ def pagination_since_id
+ @canonical_email_blocks.first.id
+ end
+
+ def records_continue?
+ @canonical_email_blocks.size == limit_param(LIMIT)
+ end
+
+ def pagination_params(core_params)
+ params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+ end
+end
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index 3e9fe17f4..fd1977ac5 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -9,8 +9,6 @@ module Admin::ActionLogsHelper
link_to log.human_identifier, admin_account_path(log.route_param)
when 'UserRole'
link_to log.human_identifier, admin_roles_path(log.target_id)
- when 'CustomEmoji'
- log.human_identifier
when 'Report'
link_to "##{log.human_identifier}", admin_report_path(log.target_id)
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
@@ -21,10 +19,10 @@ module Admin::ActionLogsHelper
link_to log.human_identifier, admin_account_path(log.target_id)
when 'Announcement'
link_to truncate(log.human_identifier), edit_admin_announcement_path(log.target_id)
- when 'IpBlock'
- log.human_identifier
- when 'Instance'
+ when 'IpBlock', 'Instance', 'CustomEmoji'
log.human_identifier
+ when 'CanonicalEmailBlock'
+ content_tag(:samp, log.human_identifier[0...7], title: log.human_identifier)
when 'Appeal'
link_to log.human_identifier, disputes_strike_path(log.route_param)
end
diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb
index 6382cd782..c7a7e1a4c 100644
--- a/app/models/admin/action_log_filter.rb
+++ b/app/models/admin/action_log_filter.rb
@@ -22,18 +22,22 @@ class Admin::ActionLogFilter
create_domain_allow: { target_type: 'DomainAllow', action: 'create' }.freeze,
create_domain_block: { target_type: 'DomainBlock', action: 'create' }.freeze,
create_email_domain_block: { target_type: 'EmailDomainBlock', action: 'create' }.freeze,
+ create_ip_block: { target_type: 'IpBlock', action: 'create' }.freeze,
create_unavailable_domain: { target_type: 'UnavailableDomain', action: 'create' }.freeze,
create_user_role: { target_type: 'UserRole', action: 'create' }.freeze,
+ create_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'create' }.freeze,
demote_user: { target_type: 'User', action: 'demote' }.freeze,
destroy_announcement: { target_type: 'Announcement', action: 'destroy' }.freeze,
destroy_custom_emoji: { target_type: 'CustomEmoji', action: 'destroy' }.freeze,
destroy_domain_allow: { target_type: 'DomainAllow', action: 'destroy' }.freeze,
destroy_domain_block: { target_type: 'DomainBlock', action: 'destroy' }.freeze,
+ destroy_ip_block: { target_type: 'IpBlock', action: 'destroy' }.freeze,
destroy_email_domain_block: { target_type: 'EmailDomainBlock', action: 'destroy' }.freeze,
destroy_instance: { target_type: 'Instance', action: 'destroy' }.freeze,
destroy_unavailable_domain: { target_type: 'UnavailableDomain', action: 'destroy' }.freeze,
destroy_status: { target_type: 'Status', action: 'destroy' }.freeze,
destroy_user_role: { target_type: 'UserRole', action: 'destroy' }.freeze,
+ destroy_canonical_email_block: { target_type: 'CanonicalEmailBlock', action: 'destroy' }.freeze,
disable_2fa_user: { target_type: 'User', action: 'disable' }.freeze,
disable_custom_emoji: { target_type: 'CustomEmoji', action: 'disable' }.freeze,
disable_user: { target_type: 'User', action: 'disable' }.freeze,
@@ -56,6 +60,7 @@ class Admin::ActionLogFilter
update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
update_status: { target_type: 'Status', action: 'update' }.freeze,
update_user_role: { target_type: 'UserRole', action: 'update' }.freeze,
+ update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze,
unblock_email_account: { target_type: 'Account', action: 'unblock_email' }.freeze,
}.freeze
diff --git a/app/models/canonical_email_block.rb b/app/models/canonical_email_block.rb
index 94781386c..1eb69ac67 100644
--- a/app/models/canonical_email_block.rb
+++ b/app/models/canonical_email_block.rb
@@ -5,27 +5,30 @@
#
# id :bigint(8) not null, primary key
# canonical_email_hash :string default(""), not null
-# reference_account_id :bigint(8) not null
+# reference_account_id :bigint(8)
# created_at :datetime not null
# updated_at :datetime not null
#
class CanonicalEmailBlock < ApplicationRecord
include EmailHelper
+ include Paginable
- belongs_to :reference_account, class_name: 'Account'
+ belongs_to :reference_account, class_name: 'Account', optional: true
validates :canonical_email_hash, presence: true, uniqueness: true
+ scope :matching_email, ->(email) { where(canonical_email_hash: email_to_canonical_email_hash(email)) }
+
+ def to_log_human_identifier
+ canonical_email_hash
+ end
+
def email=(email)
self.canonical_email_hash = email_to_canonical_email_hash(email)
end
def self.block?(email)
- where(canonical_email_hash: email_to_canonical_email_hash(email)).exists?
- end
-
- def self.find_blocks(email)
- where(canonical_email_hash: email_to_canonical_email_hash(email))
+ matching_email(email).exists?
end
end
diff --git a/app/policies/canonical_email_block_policy.rb b/app/policies/canonical_email_block_policy.rb
new file mode 100644
index 000000000..8d76075c9
--- /dev/null
+++ b/app/policies/canonical_email_block_policy.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class CanonicalEmailBlockPolicy < ApplicationPolicy
+ def index?
+ role.can?(:manage_blocks)
+ end
+
+ def show?
+ role.can?(:manage_blocks)
+ end
+
+ def test?
+ role.can?(:manage_blocks)
+ end
+
+ def create?
+ role.can?(:manage_blocks)
+ end
+
+ def destroy?
+ role.can?(:manage_blocks)
+ end
+end
diff --git a/app/serializers/rest/admin/canonical_email_block_serializer.rb b/app/serializers/rest/admin/canonical_email_block_serializer.rb
new file mode 100644
index 000000000..fe385940a
--- /dev/null
+++ b/app/serializers/rest/admin/canonical_email_block_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::Admin::CanonicalEmailBlockSerializer < ActiveModel::Serializer
+ attributes :id, :canonical_email_hash
+
+ def id
+ object.id.to_s
+ end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 72ebfafba..0b721c163 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -239,6 +239,7 @@ en:
confirm_user: Confirm User
create_account_warning: Create Warning
create_announcement: Create Announcement
+ create_canonical_email_block: Create E-mail Block
create_custom_emoji: Create Custom Emoji
create_domain_allow: Create Domain Allow
create_domain_block: Create Domain Block
@@ -248,6 +249,7 @@ en:
create_user_role: Create Role
demote_user: Demote User
destroy_announcement: Delete Announcement
+ destroy_canonical_email_block: Delete E-mail Block
destroy_custom_emoji: Delete Custom Emoji
destroy_domain_allow: Delete Domain Allow
destroy_domain_block: Delete Domain Block
@@ -283,6 +285,7 @@ en:
update_announcement: Update Announcement
update_custom_emoji: Update Custom Emoji
update_domain_block: Update Domain Block
+ update_ip_block: Update IP rule
update_status: Update Post
update_user_role: Update Role
actions:
@@ -294,6 +297,7 @@ en:
confirm_user_html: "%{name} confirmed e-mail address of user %{target}"
create_account_warning_html: "%{name} sent a warning to %{target}"
create_announcement_html: "%{name} created new announcement %{target}"
+ create_canonical_email_block_html: "%{name} blocked e-mail with the hash %{target}"
create_custom_emoji_html: "%{name} uploaded new emoji %{target}"
create_domain_allow_html: "%{name} allowed federation with domain %{target}"
create_domain_block_html: "%{name} blocked domain %{target}"
@@ -303,6 +307,7 @@ en:
create_user_role_html: "%{name} created %{target} role"
demote_user_html: "%{name} demoted user %{target}"
destroy_announcement_html: "%{name} deleted announcement %{target}"
+ destroy_canonical_email_block_html: "%{name} unblocked e-mail with the hash %{target}"
destroy_custom_emoji_html: "%{name} deleted emoji %{target}"
destroy_domain_allow_html: "%{name} disallowed federation with domain %{target}"
destroy_domain_block_html: "%{name} unblocked domain %{target}"
@@ -338,6 +343,7 @@ en:
update_announcement_html: "%{name} updated announcement %{target}"
update_custom_emoji_html: "%{name} updated emoji %{target}"
update_domain_block_html: "%{name} updated domain block for %{target}"
+ update_ip_block_html: "%{name} changed rule for IP %{target}"
update_status_html: "%{name} updated post by %{target}"
update_user_role_html: "%{name} changed %{target} role"
empty: No logs found.
diff --git a/config/routes.rb b/config/routes.rb
index 1168c9aee..8694a6436 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -602,6 +602,12 @@ Rails.application.routes.draw do
post :measures, to: 'measures#create'
post :dimensions, to: 'dimensions#create'
post :retention, to: 'retention#create'
+
+ resources :canonical_email_blocks, only: [:index, :create, :show, :destroy] do
+ collection do
+ post :test
+ end
+ end
end
end
diff --git a/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb b/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
new file mode 100644
index 000000000..5b3ec4727
--- /dev/null
+++ b/db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb
@@ -0,0 +1,5 @@
+class ChangeCanonicalEmailBlocksNullable < ActiveRecord::Migration[6.1]
+ def change
+ safety_assured { change_column :canonical_email_blocks, :reference_account_id, :bigint, null: true, default: nil }
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 83fd9549c..db22f538a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_08_24_164532) do
+ActiveRecord::Schema.define(version: 2022_08_27_195229) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -296,7 +296,7 @@ ActiveRecord::Schema.define(version: 2022_08_24_164532) do
create_table "canonical_email_blocks", force: :cascade do |t|
t.string "canonical_email_hash", default: "", null: false
- t.bigint "reference_account_id", null: false
+ t.bigint "reference_account_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["canonical_email_hash"], name: "index_canonical_email_blocks_on_canonical_email_hash", unique: true
diff --git a/lib/mastodon/canonical_email_blocks_cli.rb b/lib/mastodon/canonical_email_blocks_cli.rb
index 64b72e603..ec228d466 100644
--- a/lib/mastodon/canonical_email_blocks_cli.rb
+++ b/lib/mastodon/canonical_email_blocks_cli.rb
@@ -18,17 +18,15 @@ module Mastodon
When suspending a local user, a hash of a "canonical" version of their e-mail
address is stored to prevent them from signing up again.
- This command can be used to find whether a known email address is blocked,
- and if so, which account it was attached to.
+ This command can be used to find whether a known email address is blocked.
LONG_DESC
def find(email)
- accts = CanonicalEmailBlock.find_blocks(email).map(&:reference_account).map(&:acct).to_a
+ accts = CanonicalEmailBlock.matching_email(email)
+
if accts.empty?
- say("#{email} is not blocked", :yellow)
+ say("#{email} is not blocked", :green)
else
- accts.each do |acct|
- say(acct, :white)
- end
+ say("#{email} is blocked", :red)
end
end
@@ -40,24 +38,13 @@ module Mastodon
This command allows removing a canonical email block.
LONG_DESC
def remove(email)
- blocks = CanonicalEmailBlock.find_blocks(email)
+ blocks = CanonicalEmailBlock.matching_email(email)
+
if blocks.empty?
- say("#{email} is not blocked", :yellow)
+ say("#{email} is not blocked", :green)
else
blocks.destroy_all
- say("Removed canonical email block for #{email}", :green)
- end
- end
-
- private
-
- def color(processed, failed)
- if !processed.zero? && failed.zero?
- :green
- elsif failed.zero?
- :yellow
- else
- :red
+ say("Unblocked #{email}", :green)
end
end
end
--
cgit
From 02ba9cfa35c7b2285950955619ae3431391e9625 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 4 Oct 2022 20:13:46 +0200
Subject: Remove code for rendering public and hashtag timelines outside the
web UI (#19257)
---
app/controllers/directories_controller.rb | 32 -------
app/controllers/public_timelines_controller.rb | 26 ------
app/controllers/tags_controller.rb | 2 +-
app/helpers/application_helper.rb | 5 +-
.../features/standalone/hashtag_timeline/index.js | 90 --------------------
.../features/standalone/public_timeline/index.js | 99 ----------------------
app/javascript/packs/about.js | 26 ------
app/models/form/admin_settings.rb | 2 -
app/views/about/show.html.haml | 25 ++----
app/views/admin/settings/edit.html.haml | 3 -
app/views/directories/index.html.haml | 54 ------------
app/views/layouts/public.html.haml | 1 -
app/views/public_timelines/show.html.haml | 17 ----
app/views/tags/_og.html.haml | 6 --
app/views/tags/show.html.haml | 16 ----
config/locales/en.yml | 11 ---
config/routes.rb | 5 +-
config/settings.yml | 1 -
package.json | 1 -
spec/controllers/tags_controller_spec.rb | 9 +-
yarn.lock | 28 ------
21 files changed, 13 insertions(+), 446 deletions(-)
delete mode 100644 app/controllers/directories_controller.rb
delete mode 100644 app/controllers/public_timelines_controller.rb
delete mode 100644 app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
delete mode 100644 app/javascript/mastodon/features/standalone/public_timeline/index.js
delete mode 100644 app/javascript/packs/about.js
delete mode 100644 app/views/directories/index.html.haml
delete mode 100644 app/views/public_timelines/show.html.haml
delete mode 100644 app/views/tags/_og.html.haml
delete mode 100644 app/views/tags/show.html.haml
(limited to 'app/helpers')
diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb
deleted file mode 100644
index f28c5b2af..000000000
--- a/app/controllers/directories_controller.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class DirectoriesController < ApplicationController
- layout 'public'
-
- before_action :authenticate_user!, if: :whitelist_mode?
- before_action :require_enabled!
- before_action :set_instance_presenter
- before_action :set_accounts
-
- skip_before_action :require_functional!, unless: :whitelist_mode?
-
- def index
- render :index
- end
-
- private
-
- def require_enabled!
- return not_found unless Setting.profile_directory
- end
-
- def set_accounts
- @accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
- query.merge!(Account.not_excluded_by_account(current_account)) if current_account
- end
- end
-
- def set_instance_presenter
- @instance_presenter = InstancePresenter.new
- end
-end
diff --git a/app/controllers/public_timelines_controller.rb b/app/controllers/public_timelines_controller.rb
deleted file mode 100644
index 1332ba16c..000000000
--- a/app/controllers/public_timelines_controller.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-class PublicTimelinesController < ApplicationController
- layout 'public'
-
- before_action :authenticate_user!, if: :whitelist_mode?
- before_action :require_enabled!
- before_action :set_body_classes
- before_action :set_instance_presenter
-
- def show; end
-
- private
-
- def require_enabled!
- not_found unless Setting.timeline_preview
- end
-
- def set_body_classes
- @body_classes = 'with-modals'
- end
-
- def set_instance_presenter
- @instance_presenter = InstancePresenter.new
- end
-end
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 6dbc2667a..2890c179d 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -21,7 +21,7 @@ class TagsController < ApplicationController
def show
respond_to do |format|
format.html do
- expires_in 0, public: true
+ redirect_to web_path("tags/#{@tag.name}")
end
format.rss do
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index db33292c1..14d27b148 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -198,10 +198,7 @@ module ApplicationHelper
def render_initial_state
state_params = {
- settings: {
- known_fediverse: Setting.show_known_fediverse_at_about_page,
- },
-
+ settings: {},
text: [params[:title], params[:text], params[:url]].compact.join(' '),
}
diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
deleted file mode 100644
index d3d8a6507..000000000
--- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { expandHashtagTimeline } from 'mastodon/actions/timelines';
-import Masonry from 'react-masonry-infinite';
-import { List as ImmutableList } from 'immutable';
-import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
-import { debounce } from 'lodash';
-import LoadingIndicator from 'mastodon/components/loading_indicator';
-
-const mapStateToProps = (state, { hashtag }) => ({
- statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
- isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false),
- hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false),
-});
-
-export default @connect(mapStateToProps)
-class HashtagTimeline extends React.PureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- statusIds: ImmutablePropTypes.list.isRequired,
- isLoading: PropTypes.bool.isRequired,
- hasMore: PropTypes.bool.isRequired,
- hashtag: PropTypes.string.isRequired,
- local: PropTypes.bool.isRequired,
- };
-
- static defaultProps = {
- local: false,
- };
-
- componentDidMount () {
- const { dispatch, hashtag, local } = this.props;
-
- dispatch(expandHashtagTimeline(hashtag, { local }));
- }
-
- handleLoadMore = () => {
- const { dispatch, hashtag, local, statusIds } = this.props;
- const maxId = statusIds.last();
-
- if (maxId) {
- dispatch(expandHashtagTimeline(hashtag, { maxId, local }));
- }
- }
-
- setRef = c => {
- this.masonry = c;
- }
-
- handleHeightChange = debounce(() => {
- if (!this.masonry) {
- return;
- }
-
- this.masonry.forcePack();
- }, 50)
-
- render () {
- const { statusIds, hasMore, isLoading } = this.props;
-
- const sizes = [
- { columns: 1, gutter: 0 },
- { mq: '415px', columns: 1, gutter: 10 },
- { mq: '640px', columns: 2, gutter: 10 },
- { mq: '960px', columns: 3, gutter: 10 },
- { mq: '1255px', columns: 3, gutter: 10 },
- ];
-
- const loader = (isLoading && statusIds.isEmpty()) ? : undefined;
-
- return (
-
- {statusIds.map(statusId => (
-
-
-
- )).toArray()}
-
- );
- }
-
-}
diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js
deleted file mode 100644
index 19b0b14be..000000000
--- a/app/javascript/mastodon/features/standalone/public_timeline/index.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
-import Masonry from 'react-masonry-infinite';
-import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
-import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
-import { debounce } from 'lodash';
-import LoadingIndicator from 'mastodon/components/loading_indicator';
-
-const mapStateToProps = (state, { local }) => {
- const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
-
- return {
- statusIds: timeline.get('items', ImmutableList()),
- isLoading: timeline.get('isLoading', false),
- hasMore: timeline.get('hasMore', false),
- };
-};
-
-export default @connect(mapStateToProps)
-class PublicTimeline extends React.PureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- statusIds: ImmutablePropTypes.list.isRequired,
- isLoading: PropTypes.bool.isRequired,
- hasMore: PropTypes.bool.isRequired,
- local: PropTypes.bool,
- };
-
- componentDidMount () {
- this._connect();
- }
-
- componentDidUpdate (prevProps) {
- if (prevProps.local !== this.props.local) {
- this._connect();
- }
- }
-
- _connect () {
- const { dispatch, local } = this.props;
-
- dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
- }
-
- handleLoadMore = () => {
- const { dispatch, statusIds, local } = this.props;
- const maxId = statusIds.last();
-
- if (maxId) {
- dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
- }
- }
-
- setRef = c => {
- this.masonry = c;
- }
-
- handleHeightChange = debounce(() => {
- if (!this.masonry) {
- return;
- }
-
- this.masonry.forcePack();
- }, 50)
-
- render () {
- const { statusIds, hasMore, isLoading } = this.props;
-
- const sizes = [
- { columns: 1, gutter: 0 },
- { mq: '415px', columns: 1, gutter: 10 },
- { mq: '640px', columns: 2, gutter: 10 },
- { mq: '960px', columns: 3, gutter: 10 },
- { mq: '1255px', columns: 3, gutter: 10 },
- ];
-
- const loader = (isLoading && statusIds.isEmpty()) ? : undefined;
-
- return (
-
- {statusIds.map(statusId => (
-
-
-
- )).toArray()}
-
- );
- }
-
-}
diff --git a/app/javascript/packs/about.js b/app/javascript/packs/about.js
deleted file mode 100644
index 892d825ec..000000000
--- a/app/javascript/packs/about.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import './public-path';
-import loadPolyfills from '../mastodon/load_polyfills';
-import { start } from '../mastodon/common';
-
-start();
-
-function loaded() {
- const TimelineContainer = require('../mastodon/containers/timeline_container').default;
- const React = require('react');
- const ReactDOM = require('react-dom');
- const mountNode = document.getElementById('mastodon-timeline');
-
- if (mountNode !== null) {
- const props = JSON.parse(mountNode.getAttribute('data-props'));
- ReactDOM.render(, mountNode);
- }
-}
-
-function main() {
- const ready = require('../mastodon/ready').default;
- ready(loaded);
-}
-
-loadPolyfills().then(main).catch(error => {
- console.error(error);
-});
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 1e6061277..e744344c5 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -19,7 +19,6 @@ class Form::AdminSettings
theme
activity_api_enabled
peers_api_enabled
- show_known_fediverse_at_about_page
preview_sensitive_media
custom_css
profile_directory
@@ -42,7 +41,6 @@ class Form::AdminSettings
timeline_preview
activity_api_enabled
peers_api_enabled
- show_known_fediverse_at_about_page
preview_sensitive_media
profile_directory
trends
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index fb292941b..d61b3cd51 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -17,25 +17,12 @@
= 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')
+ .directory__tag
+ = link_to web_path do
+ %h4
+ = fa_icon 'globe fw'
+ = t('about.see_whats_happening')
+ %small= t('about.browse_public_posts')
.directory__tag
= link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 1dfd21643..a00cd0222 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -57,9 +57,6 @@
.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 :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
deleted file mode 100644
index 48f8c4bc2..000000000
--- a/app/views/directories/index.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-- content_for :page_title do
- = t('directories.explore_mastodon', title: site_title)
-
-- content_for :header_tags do
- %meta{ name: 'description', content: t('directories.explanation') }
-
- = opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
- = opengraph 'og:type', 'website'
- = opengraph 'og:title', t('directories.explore_mastodon', title: site_title)
- = opengraph 'og:description', t('directories.explanation')
- = opengraph 'og:image', File.join(root_url, 'android-chrome-192x192.png')
-
-.page-header
- %h1= t('directories.explore_mastodon', title: site_title)
- %p= t('directories.explanation')
-
-- if @accounts.empty?
- = nothing_here
-- else
- .directory__list
- - @accounts.each do |account|
- .account-card
- = link_to TagManager.instance.url_for(account), class: 'account-card__permalink' do
- .account-card__header
- = image_tag account.header.url, alt: ''
- .account-card__title
- .account-card__title__avatar
- = image_tag account.avatar.url, alt: ''
- .display-name
- %bdi
- %strong.emojify.p-name= display_name(account, custom_emojify: true)
- %span
- = acct(account)
- = fa_icon('lock') if account.locked?
- - if account.note.present?
- .account-card__bio.emojify
- = prerender_custom_emojis(account_bio_format(account), account.emojis)
- - else
- .flex-spacer
- .account-card__actions
- .account-card__counters
- .account-card__counters__item
- = friendly_number_to_human account.statuses_count
- %small= t('accounts.posts', count: account.statuses_count).downcase
- .account-card__counters__item
- = friendly_number_to_human account.followers_count
- %small= t('accounts.followers', count: account.followers_count).downcase
- .account-card__counters__item
- = friendly_number_to_human account.following_count
- %small= t('accounts.following', count: account.following_count).downcase
- .account-card__actions__button
- = account_action_button(account)
-
- = paginate @accounts
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index 14f86c970..9b9e725e9 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -12,7 +12,6 @@
= logo_as_symbol(:wordmark)
- 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'
diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml
deleted file mode 100644
index 9254bd348..000000000
--- a/app/views/public_timelines/show.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- content_for :page_title do
- = t('about.see_whats_happening')
-
-- content_for :header_tags do
- %meta{ name: 'robots', content: 'noindex' }/
- = javascript_pack_tag 'about', crossorigin: 'anonymous'
-
-.page-header
- %h1= t('about.see_whats_happening')
-
- - if Setting.show_known_fediverse_at_about_page
- %p= t('about.browse_public_posts')
- - else
- %p= t('about.browse_local_posts')
-
-#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }}
-.notranslate#modal-container
diff --git a/app/views/tags/_og.html.haml b/app/views/tags/_og.html.haml
deleted file mode 100644
index 37f644cf2..000000000
--- a/app/views/tags/_og.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-= opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
-= opengraph 'og:url', tag_url(@tag)
-= opengraph 'og:type', 'website'
-= opengraph 'og:title', "##{@tag.display_name}"
-= opengraph 'og:description', strip_tags(t('about.about_hashtag_html', hashtag: @tag.display_name))
-= opengraph 'twitter:card', 'summary'
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
deleted file mode 100644
index 6dfb4f9b3..000000000
--- a/app/views/tags/show.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- content_for :page_title do
- = "##{@tag.display_name}"
-
-- content_for :header_tags do
- %meta{ name: 'robots', content: 'noindex' }/
- %link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
-
- = javascript_pack_tag 'about', crossorigin: 'anonymous'
- = render 'og'
-
-.page-header
- %h1= "##{@tag.display_name}"
- %p= t('about.about_hashtag_html', hashtag: @tag.display_name)
-
-#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name, local: @local)) }}
-.notranslate#modal-container
diff --git a/config/locales/en.yml b/config/locales/en.yml
index dd341e0c8..8f4ea652b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,7 +1,6 @@
---
en:
about:
- about_hashtag_html: These are public posts tagged with #%{hashtag}. You can interact with them if you have an account anywhere in the fediverse.
about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!'
about_this: About
active_count_after: active
@@ -10,14 +9,11 @@ en:
api: API
apps: Mobile apps
apps_platforms: Use Mastodon from iOS, Android and other platforms
- browse_directory: Browse a profile directory and filter by interests
- browse_local_posts: Browse a live stream of public posts from this server
browse_public_posts: Browse a live stream of public posts on Mastodon
contact: Contact
contact_missing: Not set
contact_unavailable: N/A
continue_to_web: Continue to web app
- discover_users: Discover users
documentation: Documentation
federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
get_apps: Try a mobile app
@@ -783,9 +779,6 @@ en:
none: Nobody can sign up
open: Anyone can sign up
title: Registrations mode
- show_known_fediverse_at_about_page:
- desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content
- title: Include federated content on unauthenticated public timeline page
site_description:
desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <a>
and <em>
.
title: Server description
@@ -1109,10 +1102,6 @@ en:
more_details_html: For more details, see the privacy policy.
username_available: Your username will become available again
username_unavailable: Your username will remain unavailable
- directories:
- directory: Profile directory
- explanation: Discover users based on their interests
- explore_mastodon: Explore %{title}
disputes:
strikes:
action_taken: Action taken
diff --git a/config/routes.rb b/config/routes.rb
index 5d0b3004b..d2ede87d3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -95,7 +95,6 @@ Rails.application.routes.draw do
get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
post '/interact/:id', to: 'remote_interaction#create'
- get '/explore', to: 'directories#index', as: :explore
get '/settings', to: redirect('/settings/profile')
namespace :settings do
@@ -188,7 +187,9 @@ Rails.application.routes.draw do
resource :relationships, only: [:show, :update]
resource :statuses_cleanup, controller: :statuses_cleanup, only: [:show, :update]
- get '/public', to: 'public_timelines#show', as: :public_timeline
+ get '/explore', to: redirect('/web/explore')
+ get '/public', to: redirect('/web/public')
+ get '/public/local', to: redirect('/web/public/local')
get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy
resource :authorize_interaction, only: [:show, :create]
diff --git a/config/settings.yml b/config/settings.yml
index 41742118b..ec8fead0f 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -66,7 +66,6 @@ defaults: &defaults
bootstrap_timeline_accounts: ''
activity_api_enabled: true
peers_api_enabled: true
- show_known_fediverse_at_about_page: true
show_domain_blocks: 'disabled'
show_domain_blocks_rationale: 'disabled'
require_invite_text: false
diff --git a/package.json b/package.json
index f7804616c..e3b06c5e7 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,6 @@
"react-immutable-proptypes": "^2.2.0",
"react-immutable-pure-component": "^2.2.2",
"react-intl": "^2.9.0",
- "react-masonry-infinite": "^1.2.2",
"react-motion": "^0.5.2",
"react-notification": "^6.8.5",
"react-overlays": "^0.9.3",
diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb
index 69def90cf..1fd8494d6 100644
--- a/spec/controllers/tags_controller_spec.rb
+++ b/spec/controllers/tags_controller_spec.rb
@@ -10,14 +10,9 @@ RSpec.describe TagsController, type: :controller do
let!(:late) { Fabricate(:status, tags: [tag], text: 'late #test') }
context 'when tag exists' do
- it 'returns http success' do
+ it 'redirects to web version' do
get :show, params: { id: 'test', max_id: late.id }
- expect(response).to have_http_status(200)
- end
-
- it 'renders application layout' do
- get :show, params: { id: 'test', max_id: late.id }
- expect(response).to render_template layout: 'public'
+ expect(response).to redirect_to('/web/tags/test')
end
end
diff --git a/yarn.lock b/yarn.lock
index 343e156f8..9bbf3cb10 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2934,13 +2934,6 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-bricks.js@^1.7.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2"
- integrity sha1-j96zwCJq8lH01XJ6fff5rACStLI=
- dependencies:
- knot.js "^1.1.5"
-
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -7110,11 +7103,6 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
-knot.js@^1.1.5:
- version "1.1.5"
- resolved "https://registry.yarnpkg.com/knot.js/-/knot.js-1.1.5.tgz#28e72522f703f50fe98812fde224dd72728fef5d"
- integrity sha1-KOclIvcD9Q/piBL94iTdcnKP710=
-
known-css-properties@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776"
@@ -9226,13 +9214,6 @@ react-immutable-pure-component@^2.2.2:
resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz#3014d3e20cd5a7a4db73b81f1f1464f4d351684b"
integrity sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==
-react-infinite-scroller@^1.0.12:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9"
- integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==
- dependencies:
- prop-types "^15.5.8"
-
react-intl-translations-manager@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/react-intl-translations-manager/-/react-intl-translations-manager-5.0.3.tgz#aee010ecf35975673e033ca5d7d3f4147894324d"
@@ -9274,15 +9255,6 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-react-masonry-infinite@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/react-masonry-infinite/-/react-masonry-infinite-1.2.2.tgz#20c1386f9ccdda9747527c8f42bc2c02dd2e7951"
- integrity sha1-IME4b5zN2pdHUnyPQrwsAt0ueVE=
- dependencies:
- bricks.js "^1.7.0"
- prop-types "^15.5.10"
- react-infinite-scroller "^1.0.12"
-
react-motion@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
--
cgit
From 93f340a4bf35082968118319448905b489b101a3 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 6 Oct 2022 10:16:47 +0200
Subject: Remove setting that disables account deletes (#17683)
---
app/controllers/settings/deletes_controller.rb | 5 -----
app/helpers/application_helper.rb | 4 ----
app/models/form/admin_settings.rb | 2 --
app/views/admin/settings/edit.html.haml | 3 ---
app/views/auth/registrations/edit.html.haml | 7 +++----
app/views/settings/profiles/show.html.haml | 7 +++----
config/locales/en.yml | 3 ---
spec/controllers/settings/deletes_controller_spec.rb | 14 --------------
8 files changed, 6 insertions(+), 39 deletions(-)
(limited to 'app/helpers')
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index e0dd5edcb..bb096567a 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -4,7 +4,6 @@ class Settings::DeletesController < Settings::BaseController
skip_before_action :require_functional!
before_action :require_not_suspended!
- before_action :check_enabled_deletion
def show
@confirmation = Form::DeleteConfirmation.new
@@ -21,10 +20,6 @@ class Settings::DeletesController < Settings::BaseController
private
- def check_enabled_deletion
- redirect_to root_path unless Setting.open_deletion
- end
-
def resource_params
params.require(:form_delete_confirmation).permit(:password, :username)
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 14d27b148..23884fbd4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -87,10 +87,6 @@ module ApplicationHelper
link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post
end
- def open_deletion?
- Setting.open_deletion
- end
-
def locale_direction
if RTL_LOCALES.include?(I18n.locale)
'rtl'
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index e744344c5..7bd9e3743 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -13,7 +13,6 @@ class Form::AdminSettings
site_terms
registrations_mode
closed_registrations_message
- open_deletion
timeline_preview
bootstrap_timeline_accounts
theme
@@ -37,7 +36,6 @@ class Form::AdminSettings
).freeze
BOOLEAN_KEYS = %i(
- open_deletion
timeline_preview
activity_api_enabled
peers_api_enabled
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index a00cd0222..79f73a60f 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -57,9 +57,6 @@
.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 :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'), recommended: true
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index a3445b421..df929e3e8 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -41,8 +41,7 @@
%h3= t('migrations.incoming_migrations')
%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
- - if open_deletion?
- %hr.spacer/
+ %hr.spacer/
- %h3= t('auth.delete_account')
- %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
+ %h3= t('auth.delete_account')
+ %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index fe9666d84..3067b3737 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -70,8 +70,7 @@
%h6= t 'migrations.incoming_migrations'
%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
-- if open_deletion?
- %hr.spacer/
+%hr.spacer/
- %h6= t('auth.delete_account')
- %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
+%h6= t('auth.delete_account')
+%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index b41e4f47b..505a2f9fc 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -754,9 +754,6 @@ en:
closed_message:
desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags
title: Closed registration message
- deletion:
- desc_html: Allow anyone to delete their account
- title: Open account deletion
require_invite_text:
desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
title: Require new users to enter a reason to join
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
index cd36ecc35..a94dc042a 100644
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -81,20 +81,6 @@ describe Settings::DeletesController do
expect(response).to redirect_to settings_delete_path
end
end
-
- context 'when account deletions are disabled' do
- around do |example|
- open_deletion = Setting.open_deletion
- example.run
- Setting.open_deletion = open_deletion
- end
-
- it 'redirects' do
- Setting.open_deletion = false
- delete :destroy
- expect(response).to redirect_to root_path
- end
- end
end
context 'when not signed in' do
--
cgit
From 7afc6a630c76fb071bd189af3ac1366efc82f819 Mon Sep 17 00:00:00 2001
From: Yamagishi Kazutoshi
Date: Thu, 13 Oct 2022 04:07:30 +0900
Subject: Redirect non-logged-in user to owner statuses on single user mode
(#19333)
---
app/helpers/application_helper.rb | 4 ++
app/javascript/mastodon/features/ui/index.js | 4 +-
app/javascript/mastodon/initial_state.js | 80 +++++++++++++++++++++-------
app/presenters/initial_state_presenter.rb | 2 +-
app/serializers/initial_state_serializer.rb | 6 +++
spec/views/statuses/show.html.haml_spec.rb | 1 +
6 files changed, 77 insertions(+), 20 deletions(-)
(limited to 'app/helpers')
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 23884fbd4..9cc34cab6 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -211,6 +211,10 @@ module ApplicationHelper
state_params[:admin] = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
end
+ if single_user_mode?
+ state_params[:owner] = Account.local.without_suspended.where('id > 0').first
+ end
+
json = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(state_params), serializer: InitialStateSerializer).to_json
# rubocop:disable Rails/OutputSafety
content_tag(:script, json_escape(json).html_safe, id: 'initial-state', type: 'application/json')
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 2edd3b9fe..8333ea282 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -54,7 +54,7 @@ import {
About,
PrivacyPolicy,
} from './util/async-components';
-import { me } from '../../initial_state';
+import initialState, { me, owner, singleUserMode } from '../../initial_state';
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import Header from './components/header';
@@ -161,6 +161,8 @@ class SwitchingColumnsArea extends React.PureComponent {
} else {
redirect = ;
}
+ } else if (singleUserMode && owner && initialState?.accounts[owner]) {
+ redirect = ;
} else {
redirect = ;
}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 031c748cf..f9843f7f8 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -1,5 +1,44 @@
// @ts-check
+/**
+ * @typedef Emoji
+ * @property {string} shortcode
+ * @property {string} static_url
+ * @property {string} url
+ */
+
+/**
+ * @typedef AccountField
+ * @property {string} name
+ * @property {string} value
+ * @property {string} verified_at
+ */
+
+/**
+ * @typedef Account
+ * @property {string} acct
+ * @property {string} avatar
+ * @property {string} avatar_static
+ * @property {boolean} bot
+ * @property {string} created_at
+ * @property {boolean=} discoverable
+ * @property {string} display_name
+ * @property {Emoji[]} emojis
+ * @property {AccountField[]} fields
+ * @property {number} followers_count
+ * @property {number} following_count
+ * @property {boolean} group
+ * @property {string} header
+ * @property {string} header_static
+ * @property {string} id
+ * @property {string=} last_status_at
+ * @property {boolean} locked
+ * @property {string} note
+ * @property {number} statuses_count
+ * @property {string} url
+ * @property {string} username
+ */
+
/**
* @typedef {[code: string, name: string, localName: string]} InitialStateLanguage
*/
@@ -22,11 +61,13 @@
* @property {string} locale
* @property {string | null} mascot
* @property {string=} me
+ * @property {string=} owner
* @property {boolean} profile_directory
* @property {boolean} registrations_open
* @property {boolean} reduce_motion
* @property {string} repository
* @property {boolean} search_enabled
+ * @property {boolean} single_user_mode
* @property {string} source_url
* @property {string} streaming_api_base_url
* @property {boolean} timeline_preview
@@ -40,13 +81,14 @@
/**
* @typedef InitialState
+ * @property {Record} accounts
* @property {InitialStateLanguage[]} languages
* @property {InitialStateMeta} meta
*/
const element = document.getElementById('initial-state');
/** @type {InitialState | undefined} */
-const initialState = element && JSON.parse(element.textContent);
+const initialState = element?.textContent && JSON.parse(element.textContent);
/**
* @template {keyof InitialStateMeta} K
@@ -55,32 +97,34 @@ const initialState = element && JSON.parse(element.textContent);
*/
const getMeta = (prop) => initialState?.meta && initialState.meta[prop];
-export const domain = getMeta('domain');
-export const reduceMotion = getMeta('reduce_motion');
+export const activityApiEnabled = getMeta('activity_api_enabled');
export const autoPlayGif = getMeta('auto_play_gif');
-export const displayMedia = getMeta('display_media');
-export const expandSpoilers = getMeta('expand_spoilers');
-export const unfollowModal = getMeta('unfollow_modal');
export const boostModal = getMeta('boost_modal');
+export const cropImages = getMeta('crop_images');
export const deleteModal = getMeta('delete_modal');
-export const me = getMeta('me');
-export const searchEnabled = getMeta('search_enabled');
+export const disableSwiping = getMeta('disable_swiping');
+export const displayMedia = getMeta('display_media');
+export const domain = getMeta('domain');
+export const expandSpoilers = getMeta('expand_spoilers');
+export const forceSingleColumn = !getMeta('advanced_layout');
export const limitedFederationMode = getMeta('limited_federation_mode');
+export const mascot = getMeta('mascot');
+export const me = getMeta('me');
+export const owner = getMeta('owner');
+export const profile_directory = getMeta('profile_directory');
+export const reduceMotion = getMeta('reduce_motion');
export const registrationsOpen = getMeta('registrations_open');
export const repository = getMeta('repository');
+export const searchEnabled = getMeta('search_enabled');
+export const showTrends = getMeta('trends');
+export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
-export const version = getMeta('version');
-export const mascot = getMeta('mascot');
-export const profile_directory = getMeta('profile_directory');
-export const forceSingleColumn = !getMeta('advanced_layout');
+export const timelinePreview = getMeta('timeline_preview');
+export const title = getMeta('title');
+export const unfollowModal = getMeta('unfollow_modal');
export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');
-export const showTrends = getMeta('trends');
-export const title = getMeta('title');
-export const cropImages = getMeta('crop_images');
-export const disableSwiping = getMeta('disable_swiping');
-export const timelinePreview = getMeta('timeline_preview');
-export const activityApiEnabled = getMeta('activity_api_enabled');
+export const version = getMeta('version');
export const languages = initialState?.languages;
export default initialState;
diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb
index 129ea2a46..ed0479211 100644
--- a/app/presenters/initial_state_presenter.rb
+++ b/app/presenters/initial_state_presenter.rb
@@ -2,7 +2,7 @@
class InitialStatePresenter < ActiveModelSerializers::Model
attributes :settings, :push_subscription, :token,
- :current_account, :admin, :text, :visibility
+ :current_account, :admin, :owner, :text, :visibility
def role
current_account&.user_role
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index bec725e1b..ba446854c 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -30,6 +30,7 @@ class InitialStateSerializer < ActiveModel::Serializer
registrations_open: Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode,
timeline_preview: Setting.timeline_preview,
activity_api_enabled: Setting.activity_api_enabled,
+ single_user_mode: Rails.configuration.x.single_user_mode,
}
if object.current_account
@@ -55,6 +56,10 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:crop_images] = Setting.crop_images
end
+ if Rails.configuration.x.single_user_mode
+ store[:owner] = object.owner&.id&.to_s
+ end
+
store
end
# rubocop:enable Metrics/AbcSize
@@ -78,6 +83,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store = {}
store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account
store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin
+ store[object.owner.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner
store
end
diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb
index a69843216..eeea2f698 100644
--- a/spec/views/statuses/show.html.haml_spec.rb
+++ b/spec/views/statuses/show.html.haml_spec.rb
@@ -12,6 +12,7 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
allow(view).to receive(:local_time)
allow(view).to receive(:local_time_ago)
allow(view).to receive(:current_account).and_return(nil)
+ allow(view).to receive(:single_user_mode?).and_return(false)
assign(:instance_presenter, InstancePresenter.new)
end
--
cgit
From 839f893168ab221b08fa439012189e6c29a2721a Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 20 Oct 2022 14:35:29 +0200
Subject: Change public accounts pages to mount the web UI (#19319)
* Change public accounts pages to mount the web UI
* Fix handling of remote usernames in routes
- When logged in, serve web app
- When logged out, redirect to permalink
- Fix `app-body` class not being set sometimes due to name conflict
* Fix missing `multiColumn` prop
* Fix failing test
* Use `discoverable` attribute to control indexing directives
* Fix `` not using `multiColumn`
* Add `noindex` to accounts in REST API
* Change noindex directive to not be rendered by default before a route is mounted
* Add loading indicator for detailed status in web UI
* Fix missing indicator appearing while account is loading in web UI
---
app/controllers/about_controller.rb | 8 +
app/controllers/account_follow_controller.rb | 12 -
app/controllers/account_unfollow_controller.rb | 12 -
app/controllers/accounts_controller.rb | 58 --
.../concerns/account_controller_concern.rb | 3 +-
.../concerns/web_app_controller_concern.rb | 13 +-
app/controllers/follower_accounts_controller.rb | 5 +-
app/controllers/following_accounts_controller.rb | 5 +-
app/controllers/home_controller.rb | 13 +-
app/controllers/privacy_controller.rb | 8 +
app/controllers/remote_follow_controller.rb | 41 --
app/controllers/remote_interaction_controller.rb | 55 --
app/controllers/statuses_controller.rb | 2 +-
app/controllers/tags_controller.rb | 10 +-
app/helpers/accounts_helper.rb | 50 +-
.../mastodon/components/error_boundary.js | 7 +
.../mastodon/components/missing_indicator.js | 5 +
app/javascript/mastodon/containers/mastodon.js | 2 +-
app/javascript/mastodon/features/about/index.js | 6 +-
.../mastodon/features/account/components/header.js | 5 +-
.../mastodon/features/account_timeline/index.js | 12 +-
.../mastodon/features/bookmarked_statuses/index.js | 1 +
.../mastodon/features/community_timeline/index.js | 1 +
app/javascript/mastodon/features/compose/index.js | 5 +
.../mastodon/features/direct_timeline/index.js | 1 +
.../mastodon/features/directory/index.js | 1 +
.../mastodon/features/domain_blocks/index.js | 6 +
app/javascript/mastodon/features/explore/index.js | 1 +
.../mastodon/features/favourited_statuses/index.js | 1 +
.../mastodon/features/favourites/index.js | 5 +
.../features/follow_recommendations/index.js | 5 +
.../mastodon/features/follow_requests/index.js | 5 +
.../mastodon/features/getting_started/index.js | 1 +
.../mastodon/features/hashtag_timeline/index.js | 1 +
.../mastodon/features/home_timeline/index.js | 3 +-
.../mastodon/features/keyboard_shortcuts/index.js | 5 +
.../mastodon/features/list_timeline/index.js | 1 +
app/javascript/mastodon/features/lists/index.js | 1 +
app/javascript/mastodon/features/mutes/index.js | 5 +
.../mastodon/features/notifications/index.js | 1 +
.../mastodon/features/pinned_statuses/index.js | 4 +
.../mastodon/features/privacy_policy/index.js | 6 +-
.../mastodon/features/public_timeline/index.js | 1 +
app/javascript/mastodon/features/reblogs/index.js | 5 +
app/javascript/mastodon/features/status/index.js | 17 +-
.../features/ui/components/bundle_column_error.js | 27 +-
.../features/ui/components/column_loading.js | 6 +-
.../features/ui/components/columns_area.js | 4 +-
.../mastodon/features/ui/components/modal_root.js | 21 +-
app/javascript/mastodon/features/ui/index.js | 4 +-
.../mastodon/features/ui/util/async-components.js | 8 +
.../features/ui/util/react_router_helpers.js | 4 +-
app/javascript/mastodon/main.js | 8 -
app/javascript/mastodon/reducers/statuses.js | 6 +
app/javascript/mastodon/selectors/index.js | 2 +-
.../service_worker/web_push_notifications.js | 26 +-
app/javascript/packs/public.js | 29 -
app/javascript/styles/application.scss | 1 -
app/javascript/styles/contrast/diff.scss | 4 -
app/javascript/styles/mastodon-light/diff.scss | 89 ---
app/javascript/styles/mastodon/containers.scss | 782 ---------------------
app/javascript/styles/mastodon/footer.scss | 152 ----
app/javascript/styles/mastodon/rtl.scss | 74 --
app/javascript/styles/mastodon/statuses.scss | 3 +-
app/lib/permalink_redirector.rb | 36 +-
app/models/account.rb | 1 +
app/models/user.rb | 4 +
app/serializers/rest/account_serializer.rb | 7 +-
app/views/about/show.html.haml | 3 +
app/views/accounts/_bio.html.haml | 21 -
app/views/accounts/_header.html.haml | 43 --
app/views/accounts/_moved.html.haml | 20 -
app/views/accounts/show.html.haml | 76 +-
app/views/follower_accounts/index.html.haml | 18 +-
app/views/following_accounts/index.html.haml | 18 +-
app/views/home/index.html.haml | 3 +
app/views/layouts/public.html.haml | 60 --
app/views/privacy/show.html.haml | 3 +
app/views/remote_follow/new.html.haml | 20 -
app/views/remote_interaction/new.html.haml | 24 -
app/views/statuses/_detailed_status.html.haml | 6 +-
app/views/statuses/_simple_status.html.haml | 6 +-
app/views/statuses/show.html.haml | 2 +-
app/views/tags/show.html.haml | 5 +
config/locales/en.yml | 40 --
config/routes.rb | 57 +-
package.json | 1 -
spec/controllers/account_follow_controller_spec.rb | 64 --
.../account_unfollow_controller_spec.rb | 64 --
spec/controllers/accounts_controller_spec.rb | 194 -----
.../authorize_interactions_controller_spec.rb | 4 +-
.../follower_accounts_controller_spec.rb | 21 -
.../following_accounts_controller_spec.rb | 21 -
spec/controllers/remote_follow_controller_spec.rb | 135 ----
.../remote_interaction_controller_spec.rb | 39 -
spec/controllers/tags_controller_spec.rb | 7 +-
spec/features/profile_spec.rb | 26 +-
spec/lib/permalink_redirector_spec.rb | 31 +-
spec/requests/account_show_page_spec.rb | 15 -
spec/routing/accounts_routing_spec.rb | 88 ++-
yarn.lock | 5 -
101 files changed, 389 insertions(+), 2464 deletions(-)
delete mode 100644 app/controllers/account_follow_controller.rb
delete mode 100644 app/controllers/account_unfollow_controller.rb
delete mode 100644 app/controllers/remote_follow_controller.rb
delete mode 100644 app/controllers/remote_interaction_controller.rb
delete mode 100644 app/javascript/styles/mastodon/footer.scss
delete mode 100644 app/views/accounts/_bio.html.haml
delete mode 100644 app/views/accounts/_header.html.haml
delete mode 100644 app/views/accounts/_moved.html.haml
delete mode 100644 app/views/layouts/public.html.haml
delete mode 100644 app/views/remote_follow/new.html.haml
delete mode 100644 app/views/remote_interaction/new.html.haml
create mode 100644 app/views/tags/show.html.haml
delete mode 100644 spec/controllers/account_follow_controller_spec.rb
delete mode 100644 spec/controllers/account_unfollow_controller_spec.rb
delete mode 100644 spec/controllers/remote_follow_controller_spec.rb
delete mode 100644 spec/controllers/remote_interaction_controller_spec.rb
(limited to 'app/helpers')
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 0fbc6a800..104348614 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -5,7 +5,15 @@ class AboutController < ApplicationController
skip_before_action :require_functional!
+ before_action :set_instance_presenter
+
def show
expires_in 0, public: true unless user_signed_in?
end
+
+ private
+
+ def set_instance_presenter
+ @instance_presenter = InstancePresenter.new
+ end
end
diff --git a/app/controllers/account_follow_controller.rb b/app/controllers/account_follow_controller.rb
deleted file mode 100644
index 33394074d..000000000
--- a/app/controllers/account_follow_controller.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-class AccountFollowController < ApplicationController
- include AccountControllerConcern
-
- before_action :authenticate_user!
-
- def create
- FollowService.new.call(current_user.account, @account, with_rate_limit: true)
- redirect_to account_path(@account)
- end
-end
diff --git a/app/controllers/account_unfollow_controller.rb b/app/controllers/account_unfollow_controller.rb
deleted file mode 100644
index 378ec86dc..000000000
--- a/app/controllers/account_unfollow_controller.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-class AccountUnfollowController < ApplicationController
- include AccountControllerConcern
-
- before_action :authenticate_user!
-
- def create
- UnfollowService.new.call(current_user.account, @account)
- redirect_to account_path(@account)
- end
-end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index d92f91b30..5ceea5d3c 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -9,7 +9,6 @@ class AccountsController < ApplicationController
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
- before_action :set_body_classes
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional!, unless: :whitelist_mode?
@@ -18,24 +17,6 @@ class AccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
-
- @pinned_statuses = []
- @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
- @featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
-
- if current_account && @account.blocking?(current_account)
- @statuses = []
- return
- end
-
- @pinned_statuses = cached_filtered_status_pins if show_pinned_statuses?
- @statuses = cached_filtered_status_page
- @rss_url = rss_url
-
- unless @statuses.empty?
- @older_url = older_url if @statuses.last.id > filtered_statuses.last.id
- @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
- end
end
format.rss do
@@ -55,18 +36,6 @@ class AccountsController < ApplicationController
private
- def set_body_classes
- @body_classes = 'with-modals'
- end
-
- def show_pinned_statuses?
- [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
- end
-
- def filtered_pinned_statuses
- @account.pinned_statuses.where(visibility: [:public, :unlisted])
- end
-
def filtered_statuses
default_statuses.tap do |statuses|
statuses.merge!(hashtag_scope) if tag_requested?
@@ -113,26 +82,6 @@ class AccountsController < ApplicationController
end
end
- def older_url
- pagination_url(max_id: @statuses.last.id)
- end
-
- def newer_url
- pagination_url(min_id: @statuses.first.id)
- end
-
- def pagination_url(max_id: nil, min_id: nil)
- if tag_requested?
- short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
- elsif media_requested?
- short_account_media_url(@account, max_id: max_id, min_id: min_id)
- elsif replies_requested?
- short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
- else
- short_account_url(@account, max_id: max_id, min_id: min_id)
- end
- end
-
def media_requested?
request.path.split('.').first.end_with?('/media') && !tag_requested?
end
@@ -145,13 +94,6 @@ class AccountsController < ApplicationController
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
- def cached_filtered_status_pins
- cache_collection(
- filtered_pinned_statuses,
- Status
- )
- end
-
def cached_filtered_status_page
cache_collection_paginated_by_id(
filtered_statuses,
diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb
index 11eac0eb6..2f7d84df0 100644
--- a/app/controllers/concerns/account_controller_concern.rb
+++ b/app/controllers/concerns/account_controller_concern.rb
@@ -3,13 +3,12 @@
module AccountControllerConcern
extend ActiveSupport::Concern
+ include WebAppControllerConcern
include AccountOwnedConcern
FOLLOW_PER_PAGE = 12
included do
- layout 'public'
-
before_action :set_instance_presenter
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
end
diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb
index 8a6c73af3..c671ce785 100644
--- a/app/controllers/concerns/web_app_controller_concern.rb
+++ b/app/controllers/concerns/web_app_controller_concern.rb
@@ -4,15 +4,24 @@ module WebAppControllerConcern
extend ActiveSupport::Concern
included do
- before_action :set_body_classes
+ before_action :redirect_unauthenticated_to_permalinks!
+ before_action :set_app_body_class
before_action :set_referrer_policy_header
end
- def set_body_classes
+ def set_app_body_class
@body_classes = 'app-body'
end
def set_referrer_policy_header
response.headers['Referrer-Policy'] = 'origin'
end
+
+ def redirect_unauthenticated_to_permalinks!
+ return if user_signed_in?
+
+ redirect_path = PermalinkRedirector.new(request.path).redirect_path
+
+ redirect_to(redirect_path) if redirect_path.present?
+ end
end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index da7bb4ed2..e4d8cc495 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -3,6 +3,7 @@
class FollowerAccountsController < ApplicationController
include AccountControllerConcern
include SignatureVerification
+ include WebAppControllerConcern
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
@@ -14,10 +15,6 @@ class FollowerAccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
-
- next if @account.hide_collections?
-
- follows
end
format.json do
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index c37e3b68c..f84dca1e5 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -3,6 +3,7 @@
class FollowingAccountsController < ApplicationController
include AccountControllerConcern
include SignatureVerification
+ include WebAppControllerConcern
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
@@ -14,10 +15,6 @@ class FollowingAccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
-
- next if @account.hide_collections?
-
- follows
end
format.json do
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index b4d6578b9..d8ee82a7a 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -3,21 +3,14 @@
class HomeController < ApplicationController
include WebAppControllerConcern
- before_action :redirect_unauthenticated_to_permalinks!
before_action :set_instance_presenter
- def index; end
+ def index
+ expires_in 0, public: true unless user_signed_in?
+ end
private
- def redirect_unauthenticated_to_permalinks!
- return if user_signed_in?
-
- redirect_path = PermalinkRedirector.new(request.path).redirect_path
-
- redirect_to(redirect_path) if redirect_path.present?
- end
-
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
diff --git a/app/controllers/privacy_controller.rb b/app/controllers/privacy_controller.rb
index bc98bca51..2c98bf3bf 100644
--- a/app/controllers/privacy_controller.rb
+++ b/app/controllers/privacy_controller.rb
@@ -5,7 +5,15 @@ class PrivacyController < ApplicationController
skip_before_action :require_functional!
+ before_action :set_instance_presenter
+
def show
expires_in 0, public: true if current_account.nil?
end
+
+ private
+
+ def set_instance_presenter
+ @instance_presenter = InstancePresenter.new
+ end
end
diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
deleted file mode 100644
index db1604644..000000000
--- a/app/controllers/remote_follow_controller.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-class RemoteFollowController < ApplicationController
- include AccountOwnedConcern
-
- layout 'modal'
-
- before_action :set_body_classes
-
- skip_before_action :require_functional!
-
- def new
- @remote_follow = RemoteFollow.new(session_params)
- end
-
- def create
- @remote_follow = RemoteFollow.new(resource_params)
-
- if @remote_follow.valid?
- session[:remote_follow] = @remote_follow.acct
- redirect_to @remote_follow.subscribe_address_for(@account)
- else
- render :new
- end
- end
-
- private
-
- def resource_params
- params.require(:remote_follow).permit(:acct)
- end
-
- def session_params
- { acct: session[:remote_follow] || current_account&.username }
- end
-
- def set_body_classes
- @body_classes = 'modal-layout'
- @hide_header = true
- end
-end
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
deleted file mode 100644
index 6c29a2b9f..000000000
--- a/app/controllers/remote_interaction_controller.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-class RemoteInteractionController < ApplicationController
- include Authorization
-
- layout 'modal'
-
- before_action :authenticate_user!, if: :whitelist_mode?
- before_action :set_interaction_type
- before_action :set_status
- before_action :set_body_classes
-
- skip_before_action :require_functional!, unless: :whitelist_mode?
-
- def new
- @remote_follow = RemoteFollow.new(session_params)
- end
-
- def create
- @remote_follow = RemoteFollow.new(resource_params)
-
- if @remote_follow.valid?
- session[:remote_follow] = @remote_follow.acct
- redirect_to @remote_follow.interact_address_for(@status)
- else
- render :new
- end
- end
-
- private
-
- def resource_params
- params.require(:remote_follow).permit(:acct)
- end
-
- def session_params
- { acct: session[:remote_follow] || current_account&.username }
- end
-
- def set_status
- @status = Status.find(params[:id])
- authorize @status, :show?
- rescue Mastodon::NotPermittedError
- not_found
- end
-
- def set_body_classes
- @body_classes = 'modal-layout'
- @hide_header = true
- end
-
- def set_interaction_type
- @interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
- end
-end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 181c76c9a..bb4e5b01f 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
class StatusesController < ApplicationController
+ include WebAppControllerConcern
include StatusControllerConcern
include SignatureAuthentication
include Authorization
include AccountOwnedConcern
- include WebAppControllerConcern
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_status
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 2890c179d..f0a099350 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -2,18 +2,16 @@
class TagsController < ApplicationController
include SignatureVerification
+ include WebAppControllerConcern
PAGE_SIZE = 20
PAGE_SIZE_MAX = 200
- layout 'public'
-
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_local
before_action :set_tag
before_action :set_statuses
- before_action :set_body_classes
before_action :set_instance_presenter
skip_before_action :require_functional!, unless: :whitelist_mode?
@@ -21,7 +19,7 @@ class TagsController < ApplicationController
def show
respond_to do |format|
format.html do
- redirect_to web_path("tags/#{@tag.name}")
+ expires_in 0, public: true unless user_signed_in?
end
format.rss do
@@ -54,10 +52,6 @@ class TagsController < ApplicationController
end
end
- def set_body_classes
- @body_classes = 'with-modals'
- end
-
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index 59664373d..6301919a9 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -20,54 +20,10 @@ module AccountsHelper
end
def account_action_button(account)
- if user_signed_in?
- if account.id == current_user.account_id
- link_to settings_profile_url, class: 'button logo-button' do
- safe_join([logo_as_symbol, t('settings.edit_profile')])
- end
- elsif current_account.following?(account) || current_account.requested?(account)
- link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
- safe_join([logo_as_symbol, t('accounts.unfollow')])
- end
- elsif !(account.memorial? || account.moved?)
- link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
- safe_join([logo_as_symbol, t('accounts.follow')])
- end
- end
- elsif !(account.memorial? || account.moved?)
- link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
- safe_join([logo_as_symbol, t('accounts.follow')])
- end
- end
- end
-
- def minimal_account_action_button(account)
- if user_signed_in?
- return if account.id == current_user.account_id
-
- if current_account.following?(account) || current_account.requested?(account)
- link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do
- fa_icon('user-times fw')
- end
- elsif !(account.memorial? || account.moved?)
- link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do
- fa_icon('user-plus fw')
- end
- end
- elsif !(account.memorial? || account.moved?)
- link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do
- fa_icon('user-plus fw')
- end
- end
- end
+ return if account.memorial? || account.moved?
- def account_badge(account)
- if account.bot?
- content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
- elsif account.group?
- content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles')
- elsif account.user_role&.highlighted?
- content_tag(:div, content_tag(:div, account.user_role.name, class: "account-role user-role-#{account.user_role.id}"), class: 'roles')
+ link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
+ safe_join([logo_as_symbol, t('accounts.follow')])
end
end
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index ca4a2cfe1..02d5616d6 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { version, source_url } from 'mastodon/initial_state';
import StackTrace from 'stacktrace-js';
+import { Helmet } from 'react-helmet';
export default class ErrorBoundary extends React.PureComponent {
@@ -84,6 +85,7 @@ export default class ErrorBoundary extends React.PureComponent {
)}
+
{ likelyBrowserAddonIssue ? (
@@ -91,8 +93,13 @@ export default class ErrorBoundary extends React.PureComponent {
)}
+
Mastodon v{version} · ·
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js
index 7b0101bab..05e0d653d 100644
--- a/app/javascript/mastodon/components/missing_indicator.js
+++ b/app/javascript/mastodon/components/missing_indicator.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
import classNames from 'classnames';
+import { Helmet } from 'react-helmet';
const MissingIndicator = ({ fullPage }) => (
@@ -14,6 +15,10 @@ const MissingIndicator = ({ fullPage }) => (
+
+
+
+
);
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 8e5a1fa3a..730695c49 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -78,7 +78,7 @@ export default class Mastodon extends React.PureComponent {
-
+
diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.js
index e9212565a..75fed9b95 100644
--- a/app/javascript/mastodon/features/about/index.js
+++ b/app/javascript/mastodon/features/about/index.js
@@ -94,6 +94,7 @@ class About extends React.PureComponent {
}),
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
};
componentDidMount () {
@@ -108,11 +109,11 @@ class About extends React.PureComponent {
}
render () {
- const { intl, server, extendedDescription, domainBlocks } = this.props;
+ const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
const isLoading = server.get('isLoading');
return (
-
+
`${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
@@ -212,6 +213,7 @@ class About extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 44c53f9ce..954cb0ee7 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -270,7 +270,9 @@ class Header extends ImmutablePureComponent {
const content = { __html: account.get('note_emojified') };
const displayNameHtml = { __html: account.get('display_name_html') };
const fields = account.get('fields');
- const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
+ const isLocal = account.get('acct').indexOf('@') === -1;
+ const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
+ const isIndexable = !account.get('noindex');
let badge;
@@ -373,6 +375,7 @@ class Header extends ImmutablePureComponent {
{titleFromAccount(account)}
+
);
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index 51fb76f1f..437cee95c 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -142,19 +142,17 @@ class AccountTimeline extends ImmutablePureComponent {
render () {
const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
- if (!isAccount) {
+ if (isLoading && statusIds.isEmpty()) {
return (
-
-
+
);
- }
-
- if (!statusIds && isLoading) {
+ } else if (!isLoading && !isAccount) {
return (
-
+
+
);
}
diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.js b/app/javascript/mastodon/features/bookmarked_statuses/index.js
index 0e466e5ed..097be17c9 100644
--- a/app/javascript/mastodon/features/bookmarked_statuses/index.js
+++ b/app/javascript/mastodon/features/bookmarked_statuses/index.js
@@ -99,6 +99,7 @@ class Bookmarks extends ImmutablePureComponent {
{intl.formatMessage(messages.heading)}
+
);
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index 757521802..7b3f8845f 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -151,6 +151,7 @@ class CommunityTimeline extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index c27556a0e..763c715de 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -18,6 +18,7 @@ import { mascot } from '../../initial_state';
import Icon from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';
import Column from 'mastodon/components/column';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -145,6 +146,10 @@ class Compose extends React.PureComponent {
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index cfaa9c4c5..8dcc43e28 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -98,6 +98,7 @@ class DirectTimeline extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js
index 0ce7919b6..b45faa049 100644
--- a/app/javascript/mastodon/features/directory/index.js
+++ b/app/javascript/mastodon/features/directory/index.js
@@ -169,6 +169,7 @@ class Directory extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js
index edb80aef4..43b275c2d 100644
--- a/app/javascript/mastodon/features/domain_blocks/index.js
+++ b/app/javascript/mastodon/features/domain_blocks/index.js
@@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import DomainContainer from '../../containers/domain_container';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ScrollableList from '../../components/scrollable_list';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
@@ -59,6 +60,7 @@ class Blocks extends ImmutablePureComponent {
return (
+
,
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js
index 566be631e..1c7049e97 100644
--- a/app/javascript/mastodon/features/explore/index.js
+++ b/app/javascript/mastodon/features/explore/index.js
@@ -84,6 +84,7 @@ class Explore extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
)}
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index f1d32eff1..3741f68f6 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -99,6 +99,7 @@ class Favourites extends ImmutablePureComponent {
{intl.formatMessage(messages.heading)}
+
);
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
index 673317f04..ad10744da 100644
--- a/app/javascript/mastodon/features/favourites/index.js
+++ b/app/javascript/mastodon/features/favourites/index.js
@@ -11,6 +11,7 @@ import LoadingIndicator from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import AccountContainer from 'mastodon/containers/account_container';
import Column from 'mastodon/features/ui/components/column';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
@@ -80,6 +81,10 @@ class Favourites extends ImmutablePureComponent {
,
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/follow_recommendations/index.js b/app/javascript/mastodon/features/follow_recommendations/index.js
index 32b55eeb3..5f7baa64c 100644
--- a/app/javascript/mastodon/features/follow_recommendations/index.js
+++ b/app/javascript/mastodon/features/follow_recommendations/index.js
@@ -12,6 +12,7 @@ import Column from 'mastodon/features/ui/components/column';
import Account from './components/account';
import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
import Button from 'mastodon/components/button';
+import { Helmet } from 'react-helmet';
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
@@ -104,6 +105,10 @@ class FollowRecommendations extends ImmutablePureComponent {
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index 1f9b635bb..d16aa7737 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -12,6 +12,7 @@ import AccountAuthorizeContainer from './containers/account_authorize_container'
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ScrollableList from '../../components/scrollable_list';
import { me } from '../../initial_state';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@@ -87,6 +88,10 @@ class FollowRequests extends ImmutablePureComponent {
,
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index 42a5b581f..f002ffc77 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -138,6 +138,7 @@ class GettingStarted extends ImmutablePureComponent {
{intl.formatMessage(messages.menu)}
+
);
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index 0f7df5036..ec524be8f 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -228,6 +228,7 @@ class HashtagTimeline extends React.PureComponent {
#{id}
+
);
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index 68770b739..838ed7dd8 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -20,7 +20,7 @@ const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
-});
+});
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
@@ -167,6 +167,7 @@ class HomeTimeline extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
index 2a32577ba..9a870478d 100644
--- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js
+++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ColumnHeader from 'mastodon/components/column_header';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
@@ -164,6 +165,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js
index f0a7a0c7f..fd9d33df7 100644
--- a/app/javascript/mastodon/features/list_timeline/index.js
+++ b/app/javascript/mastodon/features/list_timeline/index.js
@@ -212,6 +212,7 @@ class ListTimeline extends React.PureComponent {
{title}
+
);
diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js
index 389a0c5c8..017595ba0 100644
--- a/app/javascript/mastodon/features/lists/index.js
+++ b/app/javascript/mastodon/features/lists/index.js
@@ -80,6 +80,7 @@ class Lists extends ImmutablePureComponent {
{intl.formatMessage(messages.heading)}
+
);
diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js
index c21433cc4..65df6149f 100644
--- a/app/javascript/mastodon/features/mutes/index.js
+++ b/app/javascript/mastodon/features/mutes/index.js
@@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container';
import { fetchMutes, expandMutes } from '../../actions/mutes';
import ScrollableList from '../../components/scrollable_list';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
@@ -72,6 +73,10 @@ class Mutes extends ImmutablePureComponent {
,
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 4577bcb2d..f1bc5f160 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -281,6 +281,7 @@ class Notifications extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js
index 67b13f10a..c6790ea06 100644
--- a/app/javascript/mastodon/features/pinned_statuses/index.js
+++ b/app/javascript/mastodon/features/pinned_statuses/index.js
@@ -8,6 +8,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import StatusList from '../../components/status_list';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
heading: { id: 'column.pins', defaultMessage: 'Pinned post' },
@@ -54,6 +55,9 @@ class PinnedStatuses extends ImmutablePureComponent {
hasMore={hasMore}
bindToDocument={!multiColumn}
/>
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/privacy_policy/index.js b/app/javascript/mastodon/features/privacy_policy/index.js
index eee4255f4..3df487e8f 100644
--- a/app/javascript/mastodon/features/privacy_policy/index.js
+++ b/app/javascript/mastodon/features/privacy_policy/index.js
@@ -15,6 +15,7 @@ class PrivacyPolicy extends React.PureComponent {
static propTypes = {
intl: PropTypes.object,
+ multiColumn: PropTypes.bool,
};
state = {
@@ -32,11 +33,11 @@ class PrivacyPolicy extends React.PureComponent {
}
render () {
- const { intl } = this.props;
+ const { intl, multiColumn } = this.props;
const { isLoading, content, lastUpdated } = this.state;
return (
-
+
@@ -51,6 +52,7 @@ class PrivacyPolicy extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index 8dbef98c0..a41be07e1 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -153,6 +153,7 @@ class PublicTimeline extends React.PureComponent {
{intl.formatMessage(messages.title)}
+
);
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
index 7704a049f..70d338ef1 100644
--- a/app/javascript/mastodon/features/reblogs/index.js
+++ b/app/javascript/mastodon/features/reblogs/index.js
@@ -11,6 +11,7 @@ import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list';
import Icon from 'mastodon/components/icon';
import ColumnHeader from '../../components/column_header';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
@@ -80,6 +81,10 @@ class Reblogs extends ImmutablePureComponent {
,
)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index f9a97c9b5..02f390c6a 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { createSelector } from 'reselect';
import { fetchStatus } from '../../actions/statuses';
import MissingIndicator from '../../components/missing_indicator';
+import LoadingIndicator from 'mastodon/components/loading_indicator';
import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar';
import Column from '../ui/components/column';
@@ -145,6 +146,7 @@ const makeMapStateToProps = () => {
}
return {
+ isLoading: state.getIn(['statuses', props.params.statusId, 'isLoading']),
status,
ancestorsIds,
descendantsIds,
@@ -187,6 +189,7 @@ class Status extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
+ isLoading: PropTypes.bool,
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
@@ -566,9 +569,17 @@ class Status extends ImmutablePureComponent {
render () {
let ancestors, descendants;
- const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
+ const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
const { fullscreen } = this.state;
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
if (status === null) {
return (
@@ -586,6 +597,9 @@ class Status extends ImmutablePureComponent {
descendants = {this.renderChildren(descendantsIds)}
;
}
+ const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1;
+ const isIndexable = !status.getIn(['account', 'noindex']);
+
const handlers = {
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
@@ -659,6 +673,7 @@ class Status extends ImmutablePureComponent {
{titleFromStatus(status)}
+
);
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
index f39ebd900..ab6d4aa44 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_column_error.js
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
@@ -1,11 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
-
-import Column from './column';
-import ColumnHeader from './column_header';
-import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
-import IconButton from '../../../components/icon_button';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import IconButton from 'mastodon/components/icon_button';
+import { Helmet } from 'react-helmet';
const messages = defineMessages({
title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
@@ -18,6 +17,7 @@ class BundleColumnError extends React.PureComponent {
static propTypes = {
onRetry: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
}
handleRetry = () => {
@@ -25,16 +25,25 @@ class BundleColumnError extends React.PureComponent {
}
render () {
- const { intl: { formatMessage } } = this.props;
+ const { multiColumn, intl: { formatMessage } } = this.props;
return (
-
-
-
+
+
+
{formatMessage(messages.body)}
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js
index 0cdfd05d8..e5ed22584 100644
--- a/app/javascript/mastodon/features/ui/components/column_loading.js
+++ b/app/javascript/mastodon/features/ui/components/column_loading.js
@@ -10,6 +10,7 @@ export default class ColumnLoading extends ImmutablePureComponent {
static propTypes = {
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
icon: PropTypes.string,
+ multiColumn: PropTypes.bool,
};
static defaultProps = {
@@ -18,10 +19,11 @@ export default class ColumnLoading extends ImmutablePureComponent {
};
render() {
- let { title, icon } = this.props;
+ let { title, icon, multiColumn } = this.props;
+
return (
-
+
);
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index cc1bc83e0..9ee6fca43 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -139,11 +139,11 @@ class ColumnsArea extends ImmutablePureComponent {
}
renderLoading = columnId => () => {
- return columnId === 'COMPOSE' ? : ;
+ return columnId === 'COMPOSE' ? : ;
}
renderError = (props) => {
- return ;
+ return ;
}
render () {
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 5c273ffa4..2224a8207 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -11,9 +11,7 @@ import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import AudioModal from './audio_modal';
import ConfirmationModal from './confirmation_modal';
-import SubscribedLanguagesModal from 'mastodon/features/subscribed_languages_modal';
import FocalPointModal from './focal_point_modal';
-import InteractionModal from 'mastodon/features/interaction_modal';
import {
MuteModal,
BlockModal,
@@ -23,7 +21,10 @@ import {
ListAdder,
CompareHistoryModal,
FilterModal,
+ InteractionModal,
+ SubscribedLanguagesModal,
} from 'mastodon/features/ui/util/async-components';
+import { Helmet } from 'react-helmet';
const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
@@ -41,8 +42,8 @@ const MODAL_COMPONENTS = {
'LIST_ADDER': ListAdder,
'COMPARE_HISTORY': CompareHistoryModal,
'FILTER': FilterModal,
- 'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
- 'INTERACTION': () => Promise.resolve({ default: InteractionModal }),
+ 'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
+ 'INTERACTION': InteractionModal,
};
export default class ModalRoot extends React.PureComponent {
@@ -111,9 +112,15 @@ export default class ModalRoot extends React.PureComponent {
return (
{visible && (
-
- {(SpecificComponent) => }
-
+ <>
+
+ {(SpecificComponent) => }
+
+
+
+
+
+ >
)}
);
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 8f9f38036..003991857 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -197,8 +197,8 @@ class SwitchingColumnsArea extends React.PureComponent {
-
-
+
+
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index c79dc014c..7686a69ea 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -166,6 +166,14 @@ export function FilterModal () {
return import(/*webpackChunkName: "modals/filter_modal" */'../components/filter_modal');
}
+export function InteractionModal () {
+ return import(/*webpackChunkName: "modals/interaction_modal" */'../../interaction_modal');
+}
+
+export function SubscribedLanguagesModal () {
+ return import(/*webpackChunkName: "modals/subscribed_languages_modal" */'../../subscribed_languages_modal');
+}
+
export function About () {
return import(/*webpackChunkName: "features/about" */'../../about');
}
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index d452b871f..a65d79def 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -53,7 +53,9 @@ export class WrappedRoute extends React.Component {
}
renderLoading = () => {
- return ;
+ const { multiColumn } = this.props;
+
+ return ;
}
renderError = (props) => {
diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js
index f33375b50..d0337ce0c 100644
--- a/app/javascript/mastodon/main.js
+++ b/app/javascript/mastodon/main.js
@@ -12,14 +12,6 @@ const perf = require('mastodon/performance');
function main() {
perf.start('main()');
- if (window.history && history.replaceState) {
- const { pathname, search, hash } = window.location;
- const path = pathname + search + hash;
- if (!(/^\/web($|\/)/).test(path)) {
- history.replaceState(null, document.title, `/web${path}`);
- }
- }
-
return ready(async () => {
const mountNode = document.getElementById('mastodon');
const props = JSON.parse(mountNode.getAttribute('data-props'));
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 7efb49d85..c30c1e2cc 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -15,6 +15,8 @@ import {
STATUS_COLLAPSE,
STATUS_TRANSLATE_SUCCESS,
STATUS_TRANSLATE_UNDO,
+ STATUS_FETCH_REQUEST,
+ STATUS_FETCH_FAIL,
} from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
@@ -37,6 +39,10 @@ const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
switch(action.type) {
+ case STATUS_FETCH_REQUEST:
+ return state.setIn([action.id, 'isLoading'], true);
+ case STATUS_FETCH_FAIL:
+ return state.delete(action.id);
case STATUS_IMPORT:
return importStatus(state, action.status);
case STATUSES_IMPORT:
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index 3dd7f4897..bf46c810e 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -41,7 +41,7 @@ export const makeGetStatus = () => {
],
(statusBase, statusReblog, accountBase, accountReblog, filters) => {
- if (!statusBase) {
+ if (!statusBase || statusBase.get('isLoading')) {
return null;
}
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index 9b75e9b9d..f12595777 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -15,7 +15,7 @@ const notify = options =>
icon: '/android-chrome-192x192.png',
tag: GROUP_TAG,
data: {
- url: (new URL('/web/notifications', self.location)).href,
+ url: (new URL('/notifications', self.location)).href,
count: notifications.length + 1,
preferred_locale: options.data.preferred_locale,
},
@@ -90,7 +90,7 @@ export const handlePush = (event) => {
options.tag = notification.id;
options.badge = '/badge.png';
options.image = notification.status && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url || undefined;
- options.data = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/web/@${notification.account.acct}/${notification.status.id}` : `/web/@${notification.account.acct}` };
+ options.data = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/@${notification.account.acct}/${notification.status.id}` : `/@${notification.account.acct}` };
if (notification.status && notification.status.spoiler_text || notification.status.sensitive) {
options.data.hiddenBody = htmlToPlainText(notification.status.content);
@@ -115,7 +115,7 @@ export const handlePush = (event) => {
tag: notification_id,
timestamp: new Date(),
badge: '/badge.png',
- data: { access_token, preferred_locale, url: '/web/notifications' },
+ data: { access_token, preferred_locale, url: '/notifications' },
});
}),
);
@@ -166,24 +166,10 @@ const removeActionFromNotification = (notification, action) => {
const openUrl = url =>
self.clients.matchAll({ type: 'window' }).then(clientList => {
- if (clientList.length !== 0) {
- const webClients = clientList.filter(client => /\/web\//.test(client.url));
-
- if (webClients.length !== 0) {
- const client = findBestClient(webClients);
- const { pathname } = new URL(url, self.location);
-
- if (pathname.startsWith('/web/')) {
- return client.focus().then(client => client.postMessage({
- type: 'navigate',
- path: pathname.slice('/web/'.length - 1),
- }));
- }
- } else if ('navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
- const client = findBestClient(clientList);
+ if (clientList.length !== 0 && 'navigate' in clientList[0]) { // Chrome 42-48 does not support navigate
+ const client = findBestClient(clientList);
- return client.navigate(url).then(client => client.focus());
- }
+ return client.navigate(url).then(client => client.focus());
}
return self.clients.openWindow(url);
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index e42468e0c..5ff45fa55 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -33,7 +33,6 @@ function main() {
const { messages } = getLocale();
const React = require('react');
const ReactDOM = require('react-dom');
- const Rellax = require('rellax');
const { createBrowserHistory } = require('history');
const scrollToDetailedStatus = () => {
@@ -112,12 +111,6 @@ function main() {
scrollToDetailedStatus();
}
- const parallaxComponents = document.querySelectorAll('.parallax');
-
- if (parallaxComponents.length > 0 ) {
- new Rellax('.parallax', { speed: -1 });
- }
-
delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => {
const password = document.getElementById('registration_user_password');
const confirmation = document.getElementById('registration_user_password_confirmation');
@@ -168,28 +161,6 @@ function main() {
});
});
- delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
- if (button !== 0) {
- return true;
- }
- window.location.href = target.href;
- return false;
- });
-
- delegate(document, '.modal-button', 'click', e => {
- e.preventDefault();
-
- let href;
-
- if (e.target.nodeName !== 'A') {
- href = e.target.parentNode.href;
- } else {
- href = e.target.href;
- }
-
- window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
- });
-
delegate(document, '#account_display_name', 'input', ({ target }) => {
const name = document.querySelector('.card .display-name strong');
if (name) {
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index e9f596e2f..81a040108 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -8,7 +8,6 @@
@import 'mastodon/branding';
@import 'mastodon/containers';
@import 'mastodon/lists';
-@import 'mastodon/footer';
@import 'mastodon/widgets';
@import 'mastodon/forms';
@import 'mastodon/accounts';
diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss
index 22f5bcc94..27eb837df 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/contrast/diff.scss
@@ -68,10 +68,6 @@
color: $darker-text-color;
}
-.public-layout .public-account-header__tabs__tabs .counter.active::after {
- border-bottom: 4px solid $ui-highlight-color;
-}
-
.compose-form .autosuggest-textarea__textarea::placeholder,
.compose-form .spoiler-input__input::placeholder {
color: $inverted-text-color;
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 4b27e6b4f..20e973b8b 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -655,95 +655,6 @@ html {
}
}
-.public-layout {
- .account__section-headline {
- border: 1px solid lighten($ui-base-color, 8%);
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border-top: 0;
- }
- }
-
- .header,
- .public-account-header,
- .public-account-bio {
- box-shadow: none;
- }
-
- .public-account-bio,
- .hero-widget__text {
- background: $account-background-color;
- }
-
- .header {
- background: $ui-base-color;
- border: 1px solid lighten($ui-base-color, 8%);
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border: 0;
- }
-
- .brand {
- &:hover,
- &:focus,
- &:active {
- background: lighten($ui-base-color, 4%);
- }
- }
- }
-
- .public-account-header {
- &__image {
- background: lighten($ui-base-color, 12%);
-
- &::after {
- box-shadow: none;
- }
- }
-
- &__bar {
- &::before {
- background: $account-background-color;
- border: 1px solid lighten($ui-base-color, 8%);
- border-top: 0;
- }
-
- .avatar img {
- border-color: $account-background-color;
- }
-
- @media screen and (max-width: $no-columns-breakpoint) {
- background: $account-background-color;
- border: 1px solid lighten($ui-base-color, 8%);
- border-top: 0;
- }
- }
-
- &__tabs {
- &__name {
- h1,
- h1 small {
- color: $white;
-
- @media screen and (max-width: $no-columns-breakpoint) {
- color: $primary-text-color;
- }
- }
- }
- }
-
- &__extra {
- .public-account-bio {
- border: 0;
- }
-
- .public-account-bio .account__header__fields {
- border-color: lighten($ui-base-color, 8%);
- }
- }
- }
-}
-
.notification__filter-bar button.active::after,
.account__section-headline a.active::after {
border-color: transparent transparent $white;
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 8e5ed03f0..b49b93984 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -104,785 +104,3 @@
margin-left: 10px;
}
}
-
-.grid-3 {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: 3fr 1fr;
- grid-auto-columns: 25%;
- grid-auto-rows: max-content;
-
- .column-0 {
- grid-column: 1 / 3;
- grid-row: 1;
- }
-
- .column-1 {
- grid-column: 1;
- grid-row: 2;
- }
-
- .column-2 {
- grid-column: 2;
- grid-row: 2;
- }
-
- .column-3 {
- grid-column: 1 / 3;
- grid-row: 3;
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- grid-gap: 0;
- grid-template-columns: minmax(0, 100%);
-
- .column-0 {
- grid-column: 1;
- }
-
- .column-1 {
- grid-column: 1;
- grid-row: 3;
- }
-
- .column-2 {
- grid-column: 1;
- grid-row: 2;
- }
-
- .column-3 {
- grid-column: 1;
- grid-row: 4;
- }
- }
-}
-
-.grid-4 {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: repeat(4, minmax(0, 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%;
- }
-
- .flash-message {
- margin-bottom: 10px;
- }
-
- @media screen and (max-width: 738px) {
- grid-template-columns: minmax(0, 50%) minmax(0, 50%);
-
- .landing-page__call-to-action {
- padding: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .row__information-board {
- width: 100%;
- justify-content: center;
- align-items: center;
- }
-
- .row__mascot {
- display: none;
- }
- }
-
- @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: 5;
- }
-
- .column-4 {
- grid-column: 1;
- grid-row: 4;
- }
- }
-}
-
-.public-layout {
- @media screen and (max-width: $no-gap-breakpoint) {
- padding-top: 48px;
- }
-
- .container {
- max-width: 960px;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- padding: 0;
- }
- }
-
- .header {
- background: lighten($ui-base-color, 8%);
- box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
- border-radius: 4px;
- height: 48px;
- margin: 10px 0;
- display: flex;
- align-items: stretch;
- justify-content: center;
- flex-wrap: nowrap;
- overflow: hidden;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- position: fixed;
- width: 100%;
- top: 0;
- left: 0;
- margin: 0;
- border-radius: 0;
- box-shadow: none;
- z-index: 110;
- }
-
- & > div {
- flex: 1 1 33.3%;
- min-height: 1px;
- }
-
- .nav-left {
- display: flex;
- align-items: stretch;
- justify-content: flex-start;
- flex-wrap: nowrap;
- }
-
- .nav-center {
- display: flex;
- align-items: stretch;
- justify-content: center;
- flex-wrap: nowrap;
- }
-
- .nav-right {
- display: flex;
- align-items: stretch;
- justify-content: flex-end;
- flex-wrap: nowrap;
- }
-
- .brand {
- display: block;
- padding: 15px;
-
- .logo {
- display: block;
- height: 18px;
- width: auto;
- position: relative;
- bottom: -2px;
- fill: $primary-text-color;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- height: 20px;
- }
- }
-
- &:hover,
- &:focus,
- &:active {
- background: lighten($ui-base-color, 12%);
- }
- }
-
- .nav-link {
- display: flex;
- align-items: center;
- padding: 0 1rem;
- font-size: 12px;
- font-weight: 500;
- text-decoration: none;
- color: $darker-text-color;
- white-space: nowrap;
- text-align: center;
-
- &:hover,
- &:focus,
- &:active {
- text-decoration: underline;
- color: $primary-text-color;
- }
-
- @media screen and (max-width: 550px) {
- &.optional {
- display: none;
- }
- }
- }
-
- .nav-button {
- background: lighten($ui-base-color, 16%);
- margin: 8px;
- margin-left: 0;
- border-radius: 4px;
-
- &:hover,
- &:focus,
- &:active {
- text-decoration: none;
- background: lighten($ui-base-color, 20%);
- }
- }
- }
-
- $no-columns-breakpoint: 600px;
-
- .grid {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr);
- grid-auto-columns: 25%;
- grid-auto-rows: max-content;
-
- .column-0 {
- grid-row: 1;
- grid-column: 1;
- }
-
- .column-1 {
- grid-row: 1;
- grid-column: 2;
- }
-
- @media screen and (max-width: $no-columns-breakpoint) {
- grid-template-columns: 100%;
- grid-gap: 0;
-
- .column-1 {
- display: none;
- }
- }
- }
-
- .page-header {
- @media screen and (max-width: $no-gap-breakpoint) {
- border-bottom: 0;
- }
- }
-
- .public-account-header {
- overflow: hidden;
- margin-bottom: 10px;
- box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
-
- &.inactive {
- opacity: 0.5;
-
- .public-account-header__image,
- .avatar {
- filter: grayscale(100%);
- }
-
- .logo-button {
- background-color: $secondary-text-color;
- }
- }
-
- .logo-button {
- padding: 3px 15px;
- }
-
- &__image {
- border-radius: 4px 4px 0 0;
- overflow: hidden;
- height: 300px;
- position: relative;
- background: darken($ui-base-color, 12%);
-
- &::after {
- content: "";
- display: block;
- position: absolute;
- width: 100%;
- height: 100%;
- box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15);
- top: 0;
- left: 0;
- }
-
- img {
- object-fit: cover;
- display: block;
- width: 100%;
- height: 100%;
- margin: 0;
- border-radius: 4px 4px 0 0;
- }
-
- @media screen and (max-width: 600px) {
- height: 200px;
- }
- }
-
- &--no-bar {
- margin-bottom: 0;
-
- .public-account-header__image,
- .public-account-header__image img {
- border-radius: 4px;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border-radius: 0;
- }
- }
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- margin-bottom: 0;
- box-shadow: none;
-
- &__image::after {
- display: none;
- }
-
- &__image,
- &__image img {
- border-radius: 0;
- }
- }
-
- &__bar {
- position: relative;
- margin-top: -80px;
- display: flex;
- justify-content: flex-start;
-
- &::before {
- content: "";
- display: block;
- background: lighten($ui-base-color, 4%);
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 60px;
- border-radius: 0 0 4px 4px;
- z-index: -1;
- }
-
- .avatar {
- display: block;
- width: 120px;
- height: 120px;
- padding-left: 20px - 4px;
- flex: 0 0 auto;
-
- img {
- display: block;
- width: 100%;
- height: 100%;
- margin: 0;
- border-radius: 50%;
- border: 4px solid lighten($ui-base-color, 4%);
- background: darken($ui-base-color, 8%);
- }
- }
-
- @media screen and (max-width: 600px) {
- margin-top: 0;
- background: lighten($ui-base-color, 4%);
- border-radius: 0 0 4px 4px;
- padding: 5px;
-
- &::before {
- display: none;
- }
-
- .avatar {
- width: 48px;
- height: 48px;
- padding: 7px 0;
- padding-left: 10px;
-
- img {
- border: 0;
- border-radius: 4px;
- }
-
- @media screen and (max-width: 360px) {
- display: none;
- }
- }
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border-radius: 0;
- }
-
- @media screen and (max-width: $no-columns-breakpoint) {
- flex-wrap: wrap;
- }
- }
-
- &__tabs {
- flex: 1 1 auto;
- margin-left: 20px;
-
- &__name {
- padding-top: 20px;
- padding-bottom: 8px;
-
- h1 {
- font-size: 20px;
- line-height: 18px * 1.5;
- color: $primary-text-color;
- font-weight: 500;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- text-shadow: 1px 1px 1px $base-shadow-color;
-
- small {
- display: block;
- font-size: 14px;
- color: $primary-text-color;
- font-weight: 400;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
-
- @media screen and (max-width: 600px) {
- margin-left: 15px;
- display: flex;
- justify-content: space-between;
- align-items: center;
-
- &__name {
- padding-top: 0;
- padding-bottom: 0;
-
- h1 {
- font-size: 16px;
- line-height: 24px;
- text-shadow: none;
-
- small {
- color: $darker-text-color;
- }
- }
- }
- }
-
- &__tabs {
- display: flex;
- justify-content: flex-start;
- align-items: stretch;
- height: 58px;
-
- .details-counters {
- display: flex;
- flex-direction: row;
- min-width: 300px;
- }
-
- @media screen and (max-width: $no-columns-breakpoint) {
- .details-counters {
- display: none;
- }
- }
-
- .counter {
- min-width: 33.3%;
- box-sizing: border-box;
- flex: 0 0 auto;
- color: $darker-text-color;
- padding: 10px;
- border-right: 1px solid lighten($ui-base-color, 4%);
- cursor: default;
- text-align: center;
- position: relative;
-
- a {
- display: block;
- }
-
- &:last-child {
- border-right: 0;
- }
-
- &::after {
- display: block;
- content: "";
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- border-bottom: 4px solid $ui-primary-color;
- opacity: 0.5;
- transition: all 400ms ease;
- }
-
- &.active {
- &::after {
- border-bottom: 4px solid $highlight-text-color;
- opacity: 1;
- }
-
- &.inactive::after {
- border-bottom-color: $secondary-text-color;
- }
- }
-
- &:hover {
- &::after {
- opacity: 1;
- transition-duration: 100ms;
- }
- }
-
- a {
- text-decoration: none;
- color: inherit;
- }
-
- .counter-label {
- font-size: 12px;
- display: block;
- }
-
- .counter-number {
- font-weight: 500;
- font-size: 18px;
- margin-bottom: 5px;
- color: $primary-text-color;
- font-family: $font-display, sans-serif;
- }
- }
-
- .spacer {
- flex: 1 1 auto;
- height: 1px;
- }
-
- &__buttons {
- padding: 7px 8px;
- }
- }
- }
-
- &__extra {
- display: none;
- margin-top: 4px;
-
- .public-account-bio {
- border-radius: 0;
- box-shadow: none;
- background: transparent;
- margin: 0 -5px;
-
- .account__header__fields {
- border-top: 1px solid lighten($ui-base-color, 12%);
- }
-
- .roles {
- display: none;
- }
- }
-
- &__links {
- margin-top: -15px;
- font-size: 14px;
- color: $darker-text-color;
-
- a {
- display: inline-block;
- color: $darker-text-color;
- text-decoration: none;
- padding: 15px;
- font-weight: 500;
-
- strong {
- font-weight: 700;
- color: $primary-text-color;
- }
- }
- }
-
- @media screen and (max-width: $no-columns-breakpoint) {
- display: block;
- flex: 100%;
- }
- }
- }
-
- .account__section-headline {
- border-radius: 4px 4px 0 0;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- border-radius: 0;
- }
- }
-
- .detailed-status__meta {
- margin-top: 25px;
- }
-
- .public-account-bio {
- background: lighten($ui-base-color, 8%);
- box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
- border-radius: 4px;
- overflow: hidden;
- margin-bottom: 10px;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- box-shadow: none;
- margin-bottom: 0;
- border-radius: 0;
- }
-
- .account__header__fields {
- margin: 0;
- border-top: 0;
-
- a {
- color: $highlight-text-color;
- }
-
- dl:first-child .verified {
- border-radius: 0 4px 0 0;
- }
-
- .verified a {
- color: $valid-value-color;
- }
- }
-
- .account__header__content {
- padding: 20px;
- padding-bottom: 0;
- color: $primary-text-color;
- }
-
- &__extra,
- .roles {
- padding: 20px;
- font-size: 14px;
- color: $darker-text-color;
- }
-
- .roles {
- padding-bottom: 0;
- }
- }
-
- .directory__list {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: minmax(0, 50%) minmax(0, 50%);
-
- .account-card {
- display: flex;
- flex-direction: column;
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- display: block;
-
- .account-card {
- margin-bottom: 10px;
- display: block;
- }
- }
- }
-
- .card-grid {
- display: flex;
- flex-wrap: wrap;
- min-width: 100%;
- margin: 0 -5px;
-
- & > div {
- box-sizing: border-box;
- flex: 1 0 auto;
- width: 300px;
- padding: 0 5px;
- margin-bottom: 10px;
- max-width: 33.333%;
-
- @media screen and (max-width: 900px) {
- max-width: 50%;
- }
-
- @media screen and (max-width: 600px) {
- max-width: 100%;
- }
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- margin: 0;
- border-top: 1px solid lighten($ui-base-color, 8%);
-
- & > div {
- width: 100%;
- padding: 0;
- margin-bottom: 0;
- border-bottom: 1px solid lighten($ui-base-color, 8%);
-
- &:last-child {
- border-bottom: 0;
- }
-
- .card__bar {
- background: $ui-base-color;
-
- &:hover,
- &:active,
- &:focus {
- background: lighten($ui-base-color, 4%);
- }
- }
- }
- }
- }
-}
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
deleted file mode 100644
index 0c3e42033..000000000
--- a/app/javascript/styles/mastodon/footer.scss
+++ /dev/null
@@ -1,152 +0,0 @@
-.public-layout {
- .footer {
- text-align: left;
- padding-top: 20px;
- padding-bottom: 60px;
- font-size: 12px;
- color: lighten($ui-base-color, 34%);
-
- @media screen and (max-width: $no-gap-breakpoint) {
- padding-left: 20px;
- padding-right: 20px;
- }
-
- .grid {
- display: grid;
- grid-gap: 10px;
- grid-template-columns: 1fr 1fr 2fr 1fr 1fr;
-
- .column-0 {
- grid-column: 1;
- grid-row: 1;
- min-width: 0;
- }
-
- .column-1 {
- grid-column: 2;
- grid-row: 1;
- min-width: 0;
- }
-
- .column-2 {
- grid-column: 3;
- grid-row: 1;
- min-width: 0;
- text-align: center;
-
- h4 a {
- color: lighten($ui-base-color, 34%);
- }
- }
-
- .column-3 {
- grid-column: 4;
- grid-row: 1;
- min-width: 0;
- }
-
- .column-4 {
- grid-column: 5;
- grid-row: 1;
- min-width: 0;
- }
-
- @media screen and (max-width: 690px) {
- grid-template-columns: 1fr 2fr 1fr;
-
- .column-0,
- .column-1 {
- grid-column: 1;
- }
-
- .column-1 {
- grid-row: 2;
- }
-
- .column-2 {
- grid-column: 2;
- }
-
- .column-3,
- .column-4 {
- grid-column: 3;
- }
-
- .column-4 {
- grid-row: 2;
- }
- }
-
- @media screen and (max-width: 600px) {
- .column-1 {
- display: block;
- }
- }
-
- @media screen and (max-width: $no-gap-breakpoint) {
- .column-0,
- .column-1,
- .column-3,
- .column-4 {
- display: none;
- }
-
- .column-2 h4 {
- display: none;
- }
- }
- }
-
- .legal-xs {
- display: none;
- text-align: center;
- padding-top: 20px;
-
- @media screen and (max-width: $no-gap-breakpoint) {
- display: block;
- }
- }
-
- h4 {
- text-transform: uppercase;
- font-weight: 700;
- margin-bottom: 8px;
- color: $darker-text-color;
-
- a {
- color: inherit;
- text-decoration: none;
- }
- }
-
- ul a,
- .legal-xs a {
- text-decoration: none;
- color: lighten($ui-base-color, 34%);
-
- &:hover,
- &:active,
- &:focus {
- text-decoration: underline;
- }
- }
-
- .brand {
- .logo {
- display: block;
- height: 36px;
- width: auto;
- margin: 0 auto;
- color: lighten($ui-base-color, 34%);
- }
-
- &:hover,
- &:focus,
- &:active {
- .logo {
- color: lighten($ui-base-color, 38%);
- }
- }
- }
- }
-}
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 98eb1511c..ccec8e95e 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -53,16 +53,6 @@ body.rtl {
right: -26px;
}
- .landing-page__logo {
- margin-right: 0;
- margin-left: 20px;
- }
-
- .landing-page .features-list .features-list__row .visual {
- margin-left: 0;
- margin-right: 15px;
- }
-
.column-link__icon,
.column-header__icon {
margin-right: 0;
@@ -350,44 +340,6 @@ body.rtl {
margin-left: 45px;
}
- .landing-page .header-wrapper .mascot {
- right: 60px;
- left: auto;
- }
-
- .landing-page__call-to-action .row__information-board {
- direction: rtl;
- }
-
- .landing-page .header .hero .floats .float-1 {
- left: -120px;
- right: auto;
- }
-
- .landing-page .header .hero .floats .float-2 {
- left: 210px;
- right: auto;
- }
-
- .landing-page .header .hero .floats .float-3 {
- left: 110px;
- right: auto;
- }
-
- .landing-page .header .links .brand img {
- left: 0;
- }
-
- .landing-page .fa-external-link {
- padding-right: 5px;
- padding-left: 0 !important;
- }
-
- .landing-page .features #mastodon-timeline {
- margin-right: 0;
- margin-left: 30px;
- }
-
@media screen and (min-width: 631px) {
.column,
.drawer {
@@ -415,32 +367,6 @@ body.rtl {
padding-right: 0;
}
- .public-layout {
- .header {
- .nav-button {
- margin-left: 8px;
- margin-right: 0;
- }
- }
-
- .public-account-header__tabs {
- margin-left: 0;
- margin-right: 20px;
- }
- }
-
- .landing-page__information {
- .account__display-name {
- margin-right: 0;
- margin-left: 5px;
- }
-
- .account__avatar-wrapper {
- margin-left: 12px;
- margin-right: 0;
- }
- }
-
.card__bar .display-name {
margin-left: 0;
margin-right: 15px;
diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss
index a3237a630..ce71d11e4 100644
--- a/app/javascript/styles/mastodon/statuses.scss
+++ b/app/javascript/styles/mastodon/statuses.scss
@@ -137,8 +137,7 @@ a.button.logo-button {
justify-content: center;
}
-.embed,
-.public-layout {
+.embed {
.status__content[data-spoiler="folded"] {
.e-content {
display: none;
diff --git a/app/lib/permalink_redirector.rb b/app/lib/permalink_redirector.rb
index 6d15f3963..cf1a37625 100644
--- a/app/lib/permalink_redirector.rb
+++ b/app/lib/permalink_redirector.rb
@@ -8,16 +8,14 @@ class PermalinkRedirector
end
def redirect_path
- if path_segments[0] == 'web'
- if path_segments[1].present? && path_segments[1].start_with?('@') && path_segments[2] =~ /\d/
- find_status_url_by_id(path_segments[2])
- elsif path_segments[1].present? && path_segments[1].start_with?('@')
- find_account_url_by_name(path_segments[1])
- elsif path_segments[1] == 'statuses' && path_segments[2] =~ /\d/
- find_status_url_by_id(path_segments[2])
- elsif path_segments[1] == 'accounts' && path_segments[2] =~ /\d/
- find_account_url_by_id(path_segments[2])
- end
+ if path_segments[0].present? && path_segments[0].start_with?('@') && path_segments[1] =~ /\d/
+ find_status_url_by_id(path_segments[1])
+ elsif path_segments[0].present? && path_segments[0].start_with?('@')
+ find_account_url_by_name(path_segments[0])
+ elsif path_segments[0] == 'statuses' && path_segments[1] =~ /\d/
+ find_status_url_by_id(path_segments[1])
+ elsif path_segments[0] == 'accounts' && path_segments[1] =~ /\d/
+ find_account_url_by_id(path_segments[1])
end
end
@@ -29,18 +27,12 @@ class PermalinkRedirector
def find_status_url_by_id(id)
status = Status.find_by(id: id)
-
- return unless status&.distributable?
-
- ActivityPub::TagManager.instance.url_for(status)
+ ActivityPub::TagManager.instance.url_for(status) if status&.distributable? && !status.account.local?
end
def find_account_url_by_id(id)
account = Account.find_by(id: id)
-
- return unless account
-
- ActivityPub::TagManager.instance.url_for(account)
+ ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local?
end
def find_account_url_by_name(name)
@@ -48,12 +40,6 @@ class PermalinkRedirector
domain = nil if TagManager.instance.local_domain?(domain)
account = Account.find_remote(username, domain)
- return unless account
-
- ActivityPub::TagManager.instance.url_for(account)
- end
-
- def find_tag_url_by_name(name)
- tag_path(CGI.unescape(name))
+ ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local?
end
end
diff --git a/app/models/account.rb b/app/models/account.rb
index 1be7b4d12..df7fa8d50 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -134,6 +134,7 @@ class Account < ApplicationRecord
:role,
:locale,
:shows_application?,
+ :prefers_noindex?,
to: :user,
prefix: true,
allow_nil: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 4767189a0..6d566b1c2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -281,6 +281,10 @@ class User < ApplicationRecord
save!
end
+ def prefers_noindex?
+ setting_noindex
+ end
+
def preferred_posting_language
valid_locale_cascade(settings.default_language, locale, I18n.locale)
end
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index c52a89d87..e521dacaa 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -14,6 +14,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
attribute :suspended, if: :suspended?
attribute :silenced, key: :limited, if: :silenced?
+ attribute :noindex, if: :local?
class FieldSerializer < ActiveModel::Serializer
include FormattingHelper
@@ -103,7 +104,11 @@ class REST::AccountSerializer < ActiveModel::Serializer
object.silenced?
end
- delegate :suspended?, :silenced?, to: :object
+ def noindex
+ object.user_prefers_noindex?
+ end
+
+ delegate :suspended?, :silenced?, :local?, to: :object
def moved_and_not_nested?
object.moved? && object.moved_to_account.moved_to_account_id.nil?
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index aff28b9a9..05d8989ad 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -1,4 +1,7 @@
- content_for :page_title do
= t('about.title')
+- content_for :header_tags do
+ = render partial: 'shared/og'
+
= render partial: 'shared/web_app'
diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml
deleted file mode 100644
index e2539b1d4..000000000
--- a/app/views/accounts/_bio.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-- fields = account.fields
-
-.public-account-bio
- - unless fields.empty?
- .account__header__fields
- - fields.each do |field|
- %dl
- %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
- %dd{ title: field.value, class: custom_field_classes(field) }
- - if field.verified?
- %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
- = fa_icon 'check'
- = prerender_custom_emojis(account_field_value_format(field), account.emojis)
-
- = account_badge(account)
-
- - if account.note.present?
- .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
-
- .public-account-bio__extra
- = t 'accounts.joined', date: l(account.created_at, format: :month)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
deleted file mode 100644
index d9966723a..000000000
--- a/app/views/accounts/_header.html.haml
+++ /dev/null
@@ -1,43 +0,0 @@
-.public-account-header{:class => ("inactive" if account.moved?)}
- .public-account-header__image
- = image_tag (prefers_autoplay? ? account.header_original_url : account.header_static_url), class: 'parallax'
- .public-account-header__bar
- = link_to short_account_url(account), class: 'avatar' do
- = image_tag (prefers_autoplay? ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: { original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: prefers_autoplay? }
- .public-account-header__tabs
- .public-account-header__tabs__name
- %h1
- = display_name(account, custom_emojify: true)
- %small
- = acct(account)
- = 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)) }
- = link_to short_account_url(account), class: 'u-url u-uid', title: number_with_delimiter(account.statuses_count) do
- %span.counter-number= friendly_number_to_human account.statuses_count
- %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= friendly_number_to_human account.following_count
- %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: number_with_delimiter(account.followers_count) do
- %span.counter-number= friendly_number_to_human account.followers_count
- %span.counter-label= t('accounts.followers', count: account.followers_count)
- .spacer
- .public-account-header__tabs__tabs__buttons
- = account_action_button(account)
-
- .public-account-header__extra
- = render 'accounts/bio', account: account
-
- .public-account-header__extra__links
- = link_to account_following_index_url(account) do
- %strong= friendly_number_to_human account.following_count
- = t('accounts.following', count: account.following_count)
- = link_to account_followers_url(account) do
- %strong= friendly_number_to_human account.followers_count
- = t('accounts.followers', count: account.followers_count)
diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml
deleted file mode 100644
index 2f46e0dd0..000000000
--- a/app/views/accounts/_moved.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- moved_to_account = account.moved_to_account
-
-.moved-account-widget
- .moved-account-widget__message
- = fa_icon 'suitcase'
- = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.pretty_acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
-
- .moved-account-widget__card
- = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
- .detailed-status__display-avatar
- .account__avatar-overlay
- .account__avatar-overlay-base
- = image_tag moved_to_account.avatar_static_url
- .account__avatar-overlay-overlay
- = image_tag account.avatar_static_url
-
- %span.display-name
- %bdi
- %strong.emojify= display_name(moved_to_account, custom_emojify: true)
- %span @#{moved_to_account.pretty_acct}
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 7fa688bd3..a51dcd7be 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -2,85 +2,13 @@
= "#{display_name(@account)} (#{acct(@account)})"
- content_for :header_tags do
- - if @account.user&.setting_noindex
+ - if @account.user_prefers_noindex?
%meta{ name: 'robots', content: 'noindex, noarchive' }/
%link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/
%link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
- - if @older_url
- %link{ rel: 'next', href: @older_url }/
- - if @newer_url
- %link{ rel: 'prev', href: @newer_url }/
-
= opengraph 'og:type', 'profile'
= render 'og', account: @account, url: short_account_url(@account, only_path: false)
-
-= render 'header', account: @account, with_bio: true
-
-.grid
- .column-0
- .h-feed
- %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
-
- .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.media'), short_account_media_url(@account)
-
- - if user_signed_in? && @account.blocking?(current_account)
- .nothing-here.nothing-here--under-tabs= t('accounts.unavailable')
- - elsif @statuses.empty?
- = nothing_here 'nothing-here--under-tabs'
- - else
- .activity-stream.activity-stream--under-tabs
- - if params[:page].to_i.zero?
- = render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
-
- - if @newer_url
- .entry= link_to_newer @newer_url
-
- = render partial: 'statuses/status', collection: @statuses, as: :status
-
- - if @older_url
- .entry= link_to_older @older_url
-
- .column-1
- - if @account.memorial?
- .memoriam-widget= t('in_memoriam_html')
- - elsif @account.moved?
- = render 'moved', account: @account
-
- = render 'bio', account: @account
-
- - if @endorsed_accounts.empty? && @account.id == current_account&.id
- .placeholder-widget= t('accounts.endorsements_hint')
- - elsif !@endorsed_accounts.empty?
- .endorsements-widget
- %h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true))
-
- - @endorsed_accounts.each do |account|
- = account_link_to account
-
- - if @featured_hashtags.empty? && @account.id == current_account&.id
- .placeholder-widget
- = t('accounts.featured_tags_hint')
- = link_to settings_featured_tags_path do
- = t('featured_tags.add_new')
- = fa_icon 'chevron-right fw'
- - else
- - @featured_hashtags.each do |featured_tag|
- .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
- = link_to short_account_tag_path(@account, featured_tag.tag) do
- %h4
- = fa_icon 'hashtag'
- = featured_tag.display_name
- %small
- - if featured_tag.last_status_at.nil?
- = t('accounts.nothing_here')
- - else
- %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
- .trends__item__current= friendly_number_to_human featured_tag.statuses_count
-
- = render 'application/sidebar'
+= render partial: 'shared/web_app'
diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml
index 92de35a9f..d93540c02 100644
--- a/app/views/follower_accounts/index.html.haml
+++ b/app/views/follower_accounts/index.html.haml
@@ -1,20 +1,6 @@
-- content_for :page_title do
- = t('accounts.people_who_follow', name: display_name(@account))
-
- content_for :header_tags do
%meta{ name: 'robots', content: 'noindex' }/
- = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
-= render 'accounts/header', account: @account
-
-- if @account.hide_collections?
- .nothing-here= t('accounts.network_hidden')
-- elsif user_signed_in? && @account.blocking?(current_account)
- .nothing-here= t('accounts.unavailable')
-- elsif @follows.empty?
- = nothing_here
-- else
- .card-grid
- = render partial: 'application/card', collection: @follows.map(&:account), as: :account
+ = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
- = paginate @follows
+= render 'shared/web_app'
diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml
index 9bb1a9edd..d93540c02 100644
--- a/app/views/following_accounts/index.html.haml
+++ b/app/views/following_accounts/index.html.haml
@@ -1,20 +1,6 @@
-- content_for :page_title do
- = t('accounts.people_followed_by', name: display_name(@account))
-
- content_for :header_tags do
%meta{ name: 'robots', content: 'noindex' }/
- = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
-= render 'accounts/header', account: @account
-
-- if @account.hide_collections?
- .nothing-here= t('accounts.network_hidden')
-- elsif user_signed_in? && @account.blocking?(current_account)
- .nothing-here= t('accounts.unavailable')
-- elsif @follows.empty?
- = nothing_here
-- else
- .card-grid
- = render partial: 'application/card', collection: @follows.map(&:target_account), as: :account
+ = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
- = paginate @follows
+= render 'shared/web_app'
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 76a02e0f0..45990cd10 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1,4 +1,7 @@
- content_for :header_tags do
+ - unless request.path == '/'
+ %meta{ name: 'robots', content: 'noindex' }/
+
= render partial: 'shared/og'
= render 'shared/web_app'
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
deleted file mode 100644
index 9b9e725e9..000000000
--- a/app/views/layouts/public.html.haml
+++ /dev/null
@@ -1,60 +0,0 @@
-- content_for :header_tags do
- = render_initial_state
- = javascript_pack_tag 'public', crossorigin: 'anonymous'
-
-- content_for :content do
- .public-layout
- - unless @hide_navbar
- .container
- %nav.header
- .nav-left
- = link_to root_url, class: 'brand' do
- = logo_as_symbol(:wordmark)
-
- - unless whitelist_mode?
- = 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
-
- .nav-right
- - if user_signed_in?
- = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'
- - else
- = link_to_login t('auth.login'), class: 'webapp-btn nav-link nav-button'
- = link_to t('auth.register'), available_sign_up_path, class: 'webapp-btn nav-link nav-button'
-
- .container= yield
-
- .container
- .footer
- .grid
- .column-0
- %h4= t 'footer.resources'
- %ul
- %li= link_to t('about.privacy_policy'), privacy_policy_path
- .column-1
- %h4= t 'footer.developers'
- %ul
- %li= link_to t('about.documentation'), 'https://docs.joinmastodon.org/'
- %li= link_to t('about.api'), 'https://docs.joinmastodon.org/client/intro/'
- .column-2
- %h4= link_to t('about.what_is_mastodon'), 'https://joinmastodon.org/'
- = link_to logo_as_symbol, root_url, class: 'brand'
- .column-3
- %h4= site_hostname
- %ul
- - unless whitelist_mode?
- %li= link_to t('about.about_this'), about_more_path
- %li= "v#{Mastodon::Version.to_s}"
- .column-4
- %h4= t 'footer.more'
- %ul
- %li= link_to t('about.source_code'), Mastodon::Version.source_url
- %li= link_to t('about.apps'), 'https://joinmastodon.org/apps'
- .legal-xs
- = link_to "v#{Mastodon::Version.to_s}", Mastodon::Version.source_url
- ·
- = link_to t('about.privacy_policy'), privacy_policy_path
-
-= render template: 'layouts/application'
diff --git a/app/views/privacy/show.html.haml b/app/views/privacy/show.html.haml
index cfc285925..95e506641 100644
--- a/app/views/privacy/show.html.haml
+++ b/app/views/privacy/show.html.haml
@@ -1,4 +1,7 @@
- content_for :page_title do
= t('privacy_policy.title')
+- content_for :header_tags do
+ = render partial: 'shared/og'
+
= render 'shared/web_app'
diff --git a/app/views/remote_follow/new.html.haml b/app/views/remote_follow/new.html.haml
deleted file mode 100644
index 4e9601f6a..000000000
--- a/app/views/remote_follow/new.html.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for :header_tags do
- %meta{ name: 'robots', content: 'noindex' }/
-
-.form-container
- .follow-prompt
- %h2= t('remote_follow.prompt')
-
- = render partial: 'application/card', locals: { account: @account }
-
- = simple_form_for @remote_follow, as: :remote_follow, url: account_remote_follow_path(@account) do |f|
- = render 'shared/error_messages', object: @remote_follow
-
- = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
-
- .actions
- = f.button :button, t('remote_follow.proceed'), type: :submit
-
- %p.hint.subtle-hint
- = t('remote_follow.reason_html', instance: site_hostname)
- = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml
deleted file mode 100644
index 2cc0fcb93..000000000
--- a/app/views/remote_interaction/new.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- content_for :header_tags do
- %meta{ name: 'robots', content: 'noindex' }/
-
-.form-container
- .follow-prompt
- %h2= t("remote_interaction.#{@interaction_type}.prompt")
-
- .public-layout
- .activity-stream.activity-stream--highlighted
- = render 'statuses/status', status: @status
-
- = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f|
- = render 'shared/error_messages', object: @remote_follow
-
- = hidden_field_tag :type, @interaction_type
-
- = f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
-
- .actions
- = f.button :button, t("remote_interaction.#{@interaction_type}.proceed"), type: :submit
-
- %p.hint.subtle-hint
- = t('remote_follow.reason_html', instance: site_hostname)
- = t('remote_follow.no_account_html', sign_up_path: available_sign_up_path)
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index c67f0e4d9..37001b022 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -56,7 +56,7 @@
- else
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer'
·
- = link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do
+ %span.detailed-status__link
- if status.in_reply_to_id.nil?
= fa_icon('reply')
- else
@@ -65,12 +65,12 @@
= " "
·
- if status.public_visibility? || status.unlisted_visibility?
- = link_to remote_interaction_path(status, type: :reblog), class: 'modal-button detailed-status__link' do
+ %span.detailed-status__link
= fa_icon('retweet')
%span.detailed-status__reblogs>= friendly_number_to_human status.reblogs_count
= " "
·
- = link_to remote_interaction_path(status, type: :favourite), class: 'modal-button detailed-status__link' do
+ %span.detailed-status__link
= fa_icon('star')
%span.detailed-status__favorites>= friendly_number_to_human status.favourites_count
= " "
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index f16d2c186..bfde3a260 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -53,18 +53,18 @@
= t 'statuses.show_thread'
.status__action-bar
- = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button icon-button--with-counter modal-button' do
+ %span.status__action-bar-button.icon-button.icon-button--with-counter
- if status.in_reply_to_id.nil?
= fa_icon 'reply fw'
- else
= fa_icon 'reply-all fw'
%span.icon-button__counter= obscured_counter status.replies_count
- = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
+ %span.status__action-bar-button.icon-button
- if status.distributable?
= fa_icon 'retweet fw'
- elsif status.private_visibility? || status.limited_visibility?
= fa_icon 'lock fw'
- else
= fa_icon 'at fw'
- = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do
+ %span.status__action-bar-button.icon-button
= fa_icon 'star fw'
diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml
index 5a3c94b84..106c41725 100644
--- a/app/views/statuses/show.html.haml
+++ b/app/views/statuses/show.html.haml
@@ -2,7 +2,7 @@
= t('statuses.title', name: display_name(@account), quote: truncate(@status.spoiler_text.presence || @status.text, length: 50, omission: '…', escape: false))
- content_for :header_tags do
- - if @account.user&.setting_noindex
+ - if @account.user_prefers_noindex?
%meta{ name: 'robots', content: 'noindex, noarchive' }/
%link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: short_account_status_url(@account, @status), format: 'json') }/
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
new file mode 100644
index 000000000..4b4967a8f
--- /dev/null
+++ b/app/views/tags/show.html.haml
@@ -0,0 +1,5 @@
+- content_for :header_tags do
+ %meta{ name: 'robots', content: 'noindex' }/
+ = render partial: 'shared/og'
+
+= render partial: 'shared/web_app'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 504f1b364..412178ca3 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2,47 +2,26 @@
en:
about:
about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!'
- api: API
- apps: Mobile apps
contact_missing: Not set
contact_unavailable: N/A
- documentation: Documentation
hosted_on: Mastodon hosted on %{domain}
- privacy_policy: Privacy Policy
- source_code: Source code
title: About
- what_is_mastodon: What is Mastodon?
accounts:
- choices_html: "%{name}'s choices:"
- endorsements_hint: You can endorse people you follow from the web interface, and they will show up here.
- featured_tags_hint: You can feature specific hashtags that will be displayed here.
follow: Follow
followers:
one: Follower
other: Followers
following: Following
instance_actor_flash: This account is a virtual actor used to represent the server itself and not any individual user. It is used for federation purposes and should not be suspended.
- joined: Joined %{date}
last_active: last active
link_verified_on: Ownership of this link was checked on %{date}
- media: Media
- moved_html: "%{name} has moved to %{new_profile_link}:"
- network_hidden: This information is not available
nothing_here: There is nothing here!
- people_followed_by: People whom %{name} follows
- people_who_follow: People who follow %{name}
pin_errors:
following: You must be already following the person you want to endorse
posts:
one: Post
other: Posts
posts_tab_heading: Posts
- posts_with_replies: Posts and replies
- roles:
- bot: Bot
- group: Group
- unavailable: Profile unavailable
- unfollow: Unfollow
admin:
account_actions:
action: Perform action
@@ -1176,9 +1155,6 @@ en:
hint: This filter applies to select individual posts regardless of other criteria. You can add more posts to this filter from the web interface.
title: Filtered posts
footer:
- developers: Developers
- more: More…
- resources: Resources
trending_now: Trending now
generic:
all: All
@@ -1221,7 +1197,6 @@ en:
following: Following list
muting: Muting list
upload: Upload
- in_memoriam_html: In Memoriam.
invites:
delete: Deactivate
expired: Expired
@@ -1402,22 +1377,7 @@ en:
remove_selected_follows: Unfollow selected users
status: Account status
remote_follow:
- acct: Enter your username@domain you want to act from
missing_resource: Could not find the required redirect URL for your account
- no_account_html: Don't have an account? You can sign up here
- proceed: Proceed to follow
- prompt: 'You are going to follow:'
- reason_html: "Why is this step necessary? %{instance}
might not be the server where you are registered, so we need to redirect you to your home server first."
- remote_interaction:
- favourite:
- proceed: Proceed to favourite
- prompt: 'You want to favourite this post:'
- reblog:
- proceed: Proceed to boost
- prompt: 'You want to boost this post:'
- reply:
- proceed: Proceed to reply
- prompt: 'You want to reply to this post:'
reports:
errors:
invalid_rules: does not reference valid rules
diff --git a/config/routes.rb b/config/routes.rb
index 29ec0f8a5..1ed585f19 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,6 +3,31 @@
require 'sidekiq_unique_jobs/web'
require 'sidekiq-scheduler/web'
+# Paths of routes on the web app that to not require to be indexed or
+# have alternative format representations requiring separate controllers
+WEB_APP_PATHS = %w(
+ /getting-started
+ /keyboard-shortcuts
+ /home
+ /public
+ /public/local
+ /conversations
+ /lists/(*any)
+ /notifications
+ /favourites
+ /bookmarks
+ /pinned
+ /start
+ /directory
+ /explore/(*any)
+ /search
+ /publish
+ /follow_requests
+ /blocks
+ /domain_blocks
+ /mutes
+).freeze
+
Rails.application.routes.draw do
root 'home#index'
@@ -59,9 +84,6 @@ Rails.application.routes.draw do
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
resources :accounts, path: 'users', only: [:show], param: :username do
- get :remote_follow, to: 'remote_follow#new'
- post :remote_follow, to: 'remote_follow#create'
-
resources :statuses, only: [:show] do
member do
get :activity
@@ -85,16 +107,21 @@ Rails.application.routes.draw do
resource :inbox, only: [:create], module: :activitypub
- get '/@:username', to: 'accounts#show', as: :short_account
- get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
- get '/@:username/media', to: 'accounts#show', as: :short_account_media
- get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag
- get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
- get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
+ constraints(username: /[^@\/.]+/) do
+ get '/@:username', to: 'accounts#show', as: :short_account
+ get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
+ get '/@:username/media', to: 'accounts#show', as: :short_account_media
+ get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag
+ end
- get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
- post '/interact/:id', to: 'remote_interaction#create'
+ constraints(account_username: /[^@\/.]+/) do
+ get '/@:account_username/following', to: 'following_accounts#index'
+ get '/@:account_username/followers', to: 'follower_accounts#index'
+ get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
+ get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
+ end
+ get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: /([^\/])+?/ }, format: false
get '/settings', to: redirect('/settings/profile')
namespace :settings do
@@ -187,9 +214,6 @@ Rails.application.routes.draw do
resource :relationships, only: [:show, :update]
resource :statuses_cleanup, controller: :statuses_cleanup, only: [:show, :update]
- get '/explore', to: redirect('/web/explore')
- get '/public', to: redirect('/web/public')
- get '/public/local', to: redirect('/web/public/local')
get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy
resource :authorize_interaction, only: [:show, :create]
@@ -642,8 +666,11 @@ Rails.application.routes.draw do
end
end
- get '/web/(*any)', to: 'home#index', as: :web
+ WEB_APP_PATHS.each do |path|
+ get path, to: 'home#index'
+ end
+ get '/web/(*any)', to: redirect('/%{any}', status: 302), as: :web
get '/about', to: 'about#show'
get '/about/more', to: redirect('/about')
diff --git a/package.json b/package.json
index 5d8f20abf..0a57336d6 100644
--- a/package.json
+++ b/package.json
@@ -115,7 +115,6 @@
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.4.1",
"regenerator-runtime": "^0.13.9",
- "rellax": "^1.12.1",
"requestidlecallback": "^0.3.0",
"reselect": "^4.1.6",
"rimraf": "^3.0.2",
diff --git a/spec/controllers/account_follow_controller_spec.rb b/spec/controllers/account_follow_controller_spec.rb
deleted file mode 100644
index d33cd0499..000000000
--- a/spec/controllers/account_follow_controller_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'rails_helper'
-
-describe AccountFollowController do
- render_views
-
- let(:user) { Fabricate(:user) }
- let(:alice) { Fabricate(:account, username: 'alice') }
-
- describe 'POST #create' do
- let(:service) { double }
-
- subject { post :create, params: { account_username: alice.username } }
-
- before do
- allow(FollowService).to receive(:new).and_return(service)
- allow(service).to receive(:call)
- end
-
- context 'when account is permanently suspended' do
- before do
- alice.suspend!
- alice.deletion_request.destroy
- subject
- end
-
- it 'returns http gone' do
- expect(response).to have_http_status(410)
- end
- end
-
- context 'when account is temporarily suspended' do
- before do
- alice.suspend!
- subject
- end
-
- it 'returns http forbidden' do
- expect(response).to have_http_status(403)
- end
- end
-
- context 'when signed out' do
- before do
- subject
- end
-
- it 'does not follow' do
- expect(FollowService).not_to receive(:new)
- end
- end
-
- context 'when signed in' do
- before do
- sign_in(user)
- subject
- end
-
- it 'redirects to account path' do
- expect(service).to have_received(:call).with(user.account, alice, with_rate_limit: true)
- expect(response).to redirect_to(account_path(alice))
- end
- end
- end
-end
diff --git a/spec/controllers/account_unfollow_controller_spec.rb b/spec/controllers/account_unfollow_controller_spec.rb
deleted file mode 100644
index a11f7aa68..000000000
--- a/spec/controllers/account_unfollow_controller_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'rails_helper'
-
-describe AccountUnfollowController do
- render_views
-
- let(:user) { Fabricate(:user) }
- let(:alice) { Fabricate(:account, username: 'alice') }
-
- describe 'POST #create' do
- let(:service) { double }
-
- subject { post :create, params: { account_username: alice.username } }
-
- before do
- allow(UnfollowService).to receive(:new).and_return(service)
- allow(service).to receive(:call)
- end
-
- context 'when account is permanently suspended' do
- before do
- alice.suspend!
- alice.deletion_request.destroy
- subject
- end
-
- it 'returns http gone' do
- expect(response).to have_http_status(410)
- end
- end
-
- context 'when account is temporarily suspended' do
- before do
- alice.suspend!
- subject
- end
-
- it 'returns http forbidden' do
- expect(response).to have_http_status(403)
- end
- end
-
- context 'when signed out' do
- before do
- subject
- end
-
- it 'does not unfollow' do
- expect(UnfollowService).not_to receive(:new)
- end
- end
-
- context 'when signed in' do
- before do
- sign_in(user)
- subject
- end
-
- it 'redirects to account path' do
- expect(service).to have_received(:call).with(user.account, alice)
- expect(response).to redirect_to(account_path(alice))
- end
- end
- end
-end
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 12266c800..defa8b2d3 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -99,100 +99,6 @@ RSpec.describe AccountsController, type: :controller do
end
it_behaves_like 'common response characteristics'
-
- it 'renders public status' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
- end
-
- it 'renders self-reply' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
- end
-
- it 'renders status with media' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
- end
-
- it 'renders reblog' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
- end
-
- it 'renders pinned status' do
- expect(response.body).to include(I18n.t('stream_entries.pinned'))
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
-
- it 'does not render direct status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
- end
-
- it 'does not render reply to someone else' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
- end
- end
-
- context 'when signed-in' do
- let(:user) { Fabricate(:user) }
-
- before do
- sign_in(user)
- end
-
- context 'when user follows account' do
- before do
- user.account.follow!(account)
- get :show, params: { username: account.username, format: format }
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
- end
-
- context 'when user is blocked' do
- before do
- account.block!(user.account)
- get :show, params: { username: account.username, format: format }
- end
-
- it 'renders unavailable message' do
- expect(response.body).to include(I18n.t('accounts.unavailable'))
- end
-
- it 'does not render public status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
- end
-
- it 'does not render self-reply' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
- end
-
- it 'does not render status with media' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
- end
-
- it 'does not render reblog' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
- end
-
- it 'does not render pinned status' do
- expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
-
- it 'does not render direct status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
- end
-
- it 'does not render reply to someone else' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
- end
- end
end
context 'with replies' do
@@ -202,38 +108,6 @@ RSpec.describe AccountsController, type: :controller do
end
it_behaves_like 'common response characteristics'
-
- it 'renders public status' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
- end
-
- it 'renders self-reply' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
- end
-
- it 'renders status with media' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
- end
-
- it 'renders reblog' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
- end
-
- it 'does not render pinned status' do
- expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
-
- it 'does not render direct status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
- end
-
- it 'renders reply to someone else' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
- end
end
context 'with media' do
@@ -243,38 +117,6 @@ RSpec.describe AccountsController, type: :controller do
end
it_behaves_like 'common response characteristics'
-
- it 'does not render public status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
- end
-
- it 'does not render self-reply' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
- end
-
- it 'renders status with media' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
- end
-
- it 'does not render reblog' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
- end
-
- it 'does not render pinned status' do
- expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
-
- it 'does not render direct status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
- end
-
- it 'does not render reply to someone else' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
- end
end
context 'with tag' do
@@ -289,42 +131,6 @@ RSpec.describe AccountsController, type: :controller do
end
it_behaves_like 'common response characteristics'
-
- it 'does not render public status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
- end
-
- it 'does not render self-reply' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
- end
-
- it 'does not render status with media' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
- end
-
- it 'does not render reblog' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
- end
-
- it 'does not render pinned status' do
- expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
- end
-
- it 'does not render private status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
- end
-
- it 'does not render direct status' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
- end
-
- it 'does not render reply to someone else' do
- expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
- end
-
- it 'renders status with tag' do
- expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
- end
end
end
diff --git a/spec/controllers/authorize_interactions_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb
index 99f3f6ffc..44f52df69 100644
--- a/spec/controllers/authorize_interactions_controller_spec.rb
+++ b/spec/controllers/authorize_interactions_controller_spec.rb
@@ -39,7 +39,7 @@ describe AuthorizeInteractionsController do
end
it 'sets resource from url' do
- account = Account.new
+ account = Fabricate(:account)
service = double
allow(ResolveURLService).to receive(:new).and_return(service)
allow(service).to receive(:call).with('http://example.com').and_return(account)
@@ -51,7 +51,7 @@ describe AuthorizeInteractionsController do
end
it 'sets resource from acct uri' do
- account = Account.new
+ account = Fabricate(:account)
service = double
allow(ResolveAccountService).to receive(:new).and_return(service)
allow(service).to receive(:call).with('found@hostname').and_return(account)
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
index 4d2a6e01a..ab2e82e85 100644
--- a/spec/controllers/follower_accounts_controller_spec.rb
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -34,27 +34,6 @@ describe FollowerAccountsController do
expect(response).to have_http_status(403)
end
end
-
- it 'assigns follows' do
- expect(response).to have_http_status(200)
-
- assigned = assigns(:follows).to_a
- expect(assigned.size).to eq 2
- expect(assigned[0]).to eq follow1
- expect(assigned[1]).to eq follow0
- end
-
- it 'does not assign blocked users' do
- user = Fabricate(:user)
- user.account.block!(follower0)
- sign_in(user)
-
- expect(response).to have_http_status(200)
-
- assigned = assigns(:follows).to_a
- expect(assigned.size).to eq 1
- expect(assigned[0]).to eq follow1
- end
end
context 'when format is json' do
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
index bb6d221ca..e43dbf882 100644
--- a/spec/controllers/following_accounts_controller_spec.rb
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -34,27 +34,6 @@ describe FollowingAccountsController do
expect(response).to have_http_status(403)
end
end
-
- it 'assigns follows' do
- expect(response).to have_http_status(200)
-
- assigned = assigns(:follows).to_a
- expect(assigned.size).to eq 2
- expect(assigned[0]).to eq follow1
- expect(assigned[1]).to eq follow0
- end
-
- it 'does not assign blocked users' do
- user = Fabricate(:user)
- user.account.block!(followee0)
- sign_in(user)
-
- expect(response).to have_http_status(200)
-
- assigned = assigns(:follows).to_a
- expect(assigned.size).to eq 1
- expect(assigned[0]).to eq follow1
- end
end
context 'when format is json' do
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
deleted file mode 100644
index 01d43f48c..000000000
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe RemoteFollowController do
- render_views
-
- describe '#new' do
- it 'returns success when session is empty' do
- account = Fabricate(:account)
- get :new, params: { account_username: account.to_param }
-
- expect(response).to have_http_status(200)
- expect(response).to render_template(:new)
- expect(assigns(:remote_follow).acct).to be_nil
- end
-
- it 'populates the remote follow with session data when session exists' do
- session[:remote_follow] = 'user@example.com'
- account = Fabricate(:account)
- get :new, params: { account_username: account.to_param }
-
- expect(response).to have_http_status(200)
- expect(response).to render_template(:new)
- expect(assigns(:remote_follow).acct).to eq 'user@example.com'
- end
- end
-
- describe '#create' do
- before do
- @account = Fabricate(:account, username: 'test_user')
- end
-
- context 'with a valid acct' do
- context 'when webfinger values are wrong' do
- it 'renders new when redirect url is nil' do
- resource_with_nil_link = double(link: nil)
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_nil_link)
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
-
- expect(response).to render_template(:new)
- expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
- end
-
- it 'renders new when template is nil' do
- resource_with_link = double(link: nil)
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
-
- expect(response).to render_template(:new)
- expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
- end
- end
-
- context 'when webfinger values are good' do
- before do
- resource_with_link = double(link: 'http://example.com/follow_me?acct={uri}')
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
- end
-
- it 'saves the session' do
- expect(session[:remote_follow]).to eq 'user@example.com'
- end
-
- it 'redirects to the remote location' do
- expect(response).to redirect_to("http://example.com/follow_me?acct=https%3A%2F%2F#{Rails.configuration.x.local_domain}%2Fusers%2Ftest_user")
- end
- end
- end
-
- context 'with an invalid acct' do
- it 'renders new when acct is missing' do
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: '' } }
-
- expect(response).to render_template(:new)
- end
-
- it 'renders new with error when webfinger fails' do
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Webfinger::Error)
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
-
- expect(response).to render_template(:new)
- expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
- end
-
- it 'renders new when occur HTTP::ConnectionError' do
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
- post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
-
- expect(response).to render_template(:new)
- expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
- end
- end
- end
-
- context 'with a permanently suspended account' do
- before do
- @account = Fabricate(:account)
- @account.suspend!
- @account.deletion_request.destroy
- end
-
- it 'returns http gone on GET to #new' do
- get :new, params: { account_username: @account.to_param }
-
- expect(response).to have_http_status(410)
- end
-
- it 'returns http gone on POST to #create' do
- post :create, params: { account_username: @account.to_param }
-
- expect(response).to have_http_status(410)
- end
- end
-
- context 'with a temporarily suspended account' do
- before do
- @account = Fabricate(:account)
- @account.suspend!
- end
-
- it 'returns http forbidden on GET to #new' do
- get :new, params: { account_username: @account.to_param }
-
- expect(response).to have_http_status(403)
- end
-
- it 'returns http forbidden on POST to #create' do
- post :create, params: { account_username: @account.to_param }
-
- expect(response).to have_http_status(403)
- end
- end
-end
diff --git a/spec/controllers/remote_interaction_controller_spec.rb b/spec/controllers/remote_interaction_controller_spec.rb
deleted file mode 100644
index bb0074b11..000000000
--- a/spec/controllers/remote_interaction_controller_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe RemoteInteractionController, type: :controller do
- render_views
-
- let(:status) { Fabricate(:status) }
-
- describe 'GET #new' do
- it 'returns 200' do
- get :new, params: { id: status.id }
- expect(response).to have_http_status(200)
- end
- end
-
- describe 'POST #create' do
- context '@remote_follow is valid' do
- it 'returns 302' do
- allow_any_instance_of(RemoteFollow).to receive(:valid?) { true }
- allow_any_instance_of(RemoteFollow).to receive(:addressable_template) do
- Addressable::Template.new('https://hoge.com')
- end
-
- post :create, params: { id: status.id, remote_follow: { acct: '@hoge' } }
- expect(response).to have_http_status(302)
- end
- end
-
- context '@remote_follow is invalid' do
- it 'returns 200' do
- allow_any_instance_of(RemoteFollow).to receive(:valid?) { false }
- post :create, params: { id: status.id, remote_follow: { acct: '@hoge' } }
-
- expect(response).to have_http_status(200)
- end
- end
- end
-end
diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb
index 1fd8494d6..547bcfb39 100644
--- a/spec/controllers/tags_controller_spec.rb
+++ b/spec/controllers/tags_controller_spec.rb
@@ -10,16 +10,15 @@ RSpec.describe TagsController, type: :controller do
let!(:late) { Fabricate(:status, tags: [tag], text: 'late #test') }
context 'when tag exists' do
- it 'redirects to web version' do
+ it 'returns http success' do
get :show, params: { id: 'test', max_id: late.id }
- expect(response).to redirect_to('/web/tags/test')
+ expect(response).to have_http_status(200)
end
end
context 'when tag does not exist' do
- it 'returns http missing for non-existent tag' do
+ it 'returns http not found' do
get :show, params: { id: 'none' }
-
expect(response).to have_http_status(404)
end
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index b6de3e9d1..ec4f9a53f 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -18,36 +18,16 @@ feature 'Profile' do
visit account_path('alice')
is_expected.to have_title("alice (@alice@#{local_domain})")
-
- within('.public-account-header h1') do
- is_expected.to have_content("alice @alice@#{local_domain}")
- end
-
- bio_elem = first('.public-account-bio')
- expect(bio_elem).to have_content(alice_bio)
- # The bio has hashtags made clickable
- expect(bio_elem).to have_link('cryptology')
- expect(bio_elem).to have_link('science')
- # Nicknames are make clickable
- expect(bio_elem).to have_link('@alice')
- expect(bio_elem).to have_link('@bob')
- # Nicknames not on server are not clickable
- expect(bio_elem).not_to have_link('@pepe')
end
scenario 'I can change my account' do
visit settings_profile_path
+
fill_in 'Display name', with: 'Bob'
fill_in 'Bio', with: 'Bob is silent'
- first('.btn[type=submit]').click
- is_expected.to have_content 'Changes successfully saved!'
- # View my own public profile and see the changes
- click_link "Bob @bob@#{local_domain}"
+ first('button[type=submit]').click
- within('.public-account-header h1') do
- is_expected.to have_content("Bob @bob@#{local_domain}")
- end
- expect(first('.public-account-bio')).to have_content('Bob is silent')
+ is_expected.to have_content 'Changes successfully saved!'
end
end
diff --git a/spec/lib/permalink_redirector_spec.rb b/spec/lib/permalink_redirector_spec.rb
index abda57da4..a00913656 100644
--- a/spec/lib/permalink_redirector_spec.rb
+++ b/spec/lib/permalink_redirector_spec.rb
@@ -3,40 +3,31 @@
require 'rails_helper'
describe PermalinkRedirector do
+ let(:remote_account) { Fabricate(:account, username: 'alice', domain: 'example.com', url: 'https://example.com/@alice', id: 2) }
+
describe '#redirect_url' do
before do
- account = Fabricate(:account, username: 'alice', id: 1)
- Fabricate(:status, account: account, id: 123)
+ Fabricate(:status, account: remote_account, id: 123, url: 'https://example.com/status-123')
end
it 'returns path for legacy account links' do
- redirector = described_class.new('web/accounts/1')
- expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
+ redirector = described_class.new('accounts/2')
+ expect(redirector.redirect_path).to eq 'https://example.com/@alice'
end
it 'returns path for legacy status links' do
- redirector = described_class.new('web/statuses/123')
- expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
- end
-
- it 'returns path for legacy tag links' do
- redirector = described_class.new('web/timelines/tag/hoge')
- expect(redirector.redirect_path).to be_nil
+ redirector = described_class.new('statuses/123')
+ expect(redirector.redirect_path).to eq 'https://example.com/status-123'
end
it 'returns path for pretty account links' do
- redirector = described_class.new('web/@alice')
- expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
+ redirector = described_class.new('@alice@example.com')
+ expect(redirector.redirect_path).to eq 'https://example.com/@alice'
end
it 'returns path for pretty status links' do
- redirector = described_class.new('web/@alice/123')
- expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
- end
-
- it 'returns path for pretty tag links' do
- redirector = described_class.new('web/tags/hoge')
- expect(redirector.redirect_path).to be_nil
+ redirector = described_class.new('@alice/123')
+ expect(redirector.redirect_path).to eq 'https://example.com/status-123'
end
end
end
diff --git a/spec/requests/account_show_page_spec.rb b/spec/requests/account_show_page_spec.rb
index 4e51cf7ef..e84c46c47 100644
--- a/spec/requests/account_show_page_spec.rb
+++ b/spec/requests/account_show_page_spec.rb
@@ -3,17 +3,6 @@
require 'rails_helper'
describe 'The account show page' do
- it 'Has an h-feed with correct number of h-entry objects in it' do
- alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
- _status = Fabricate(:status, account: alice, text: 'Hello World')
- _status2 = Fabricate(:status, account: alice, text: 'Hello World Again')
- _status3 = Fabricate(:status, account: alice, text: 'Are You Still There World?')
-
- get '/@alice'
-
- expect(h_feed_entries.size).to eq(3)
- end
-
it 'has valid opengraph tags' do
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
_status = Fabricate(:status, account: alice, text: 'Hello World')
@@ -33,8 +22,4 @@ describe 'The account show page' do
def head_section
Nokogiri::Slop(response.body).html.head
end
-
- def h_feed_entries
- Nokogiri::HTML(response.body).search('.h-feed .h-entry')
- end
end
diff --git a/spec/routing/accounts_routing_spec.rb b/spec/routing/accounts_routing_spec.rb
index d04cb27f0..3f0e9b3e9 100644
--- a/spec/routing/accounts_routing_spec.rb
+++ b/spec/routing/accounts_routing_spec.rb
@@ -1,31 +1,83 @@
require 'rails_helper'
describe 'Routes under accounts/' do
- describe 'the route for accounts who are followers of an account' do
- it 'routes to the followers action with the right username' do
- expect(get('/users/name/followers')).
- to route_to('follower_accounts#index', account_username: 'name')
+ context 'with local username' do
+ let(:username) { 'alice' }
+
+ it 'routes /@:username' do
+ expect(get("/@#{username}")).to route_to('accounts#show', username: username)
end
- end
- describe 'the route for accounts who are followed by an account' do
- it 'routes to the following action with the right username' do
- expect(get('/users/name/following')).
- to route_to('following_accounts#index', account_username: 'name')
+ it 'routes /@:username.json' do
+ expect(get("/@#{username}.json")).to route_to('accounts#show', username: username, format: 'json')
+ end
+
+ it 'routes /@:username.rss' do
+ expect(get("/@#{username}.rss")).to route_to('accounts#show', username: username, format: 'rss')
+ end
+
+ it 'routes /@:username/:id' do
+ expect(get("/@#{username}/123")).to route_to('statuses#show', account_username: username, id: '123')
+ end
+
+ it 'routes /@:username/:id/embed' do
+ expect(get("/@#{username}/123/embed")).to route_to('statuses#embed', account_username: username, id: '123')
+ end
+
+ it 'routes /@:username/following' do
+ expect(get("/@#{username}/following")).to route_to('following_accounts#index', account_username: username)
+ end
+
+ it 'routes /@:username/followers' do
+ expect(get("/@#{username}/followers")).to route_to('follower_accounts#index', account_username: username)
+ end
+
+ it 'routes /@:username/with_replies' do
+ expect(get("/@#{username}/with_replies")).to route_to('accounts#show', username: username)
+ end
+
+ it 'routes /@:username/media' do
+ expect(get("/@#{username}/media")).to route_to('accounts#show', username: username)
end
- end
- describe 'the route for following an account' do
- it 'routes to the follow create action with the right username' do
- expect(post('/users/name/follow')).
- to route_to('account_follow#create', account_username: 'name')
+ it 'routes /@:username/tagged/:tag' do
+ expect(get("/@#{username}/tagged/foo")).to route_to('accounts#show', username: username, tag: 'foo')
end
end
- describe 'the route for unfollowing an account' do
- it 'routes to the unfollow create action with the right username' do
- expect(post('/users/name/unfollow')).
- to route_to('account_unfollow#create', account_username: 'name')
+ context 'with remote username' do
+ let(:username) { 'alice@example.com' }
+
+ it 'routes /@:username' do
+ expect(get("/@#{username}")).to route_to('home#index', username_with_domain: username)
+ end
+
+ it 'routes /@:username/:id' do
+ expect(get("/@#{username}/123")).to route_to('home#index', username_with_domain: username, any: '123')
+ end
+
+ it 'routes /@:username/:id/embed' do
+ expect(get("/@#{username}/123/embed")).to route_to('home#index', username_with_domain: username, any: '123/embed')
+ end
+
+ it 'routes /@:username/following' do
+ expect(get("/@#{username}/following")).to route_to('home#index', username_with_domain: username, any: 'following')
+ end
+
+ it 'routes /@:username/followers' do
+ expect(get("/@#{username}/followers")).to route_to('home#index', username_with_domain: username, any: 'followers')
+ end
+
+ it 'routes /@:username/with_replies' do
+ expect(get("/@#{username}/with_replies")).to route_to('home#index', username_with_domain: username, any: 'with_replies')
+ end
+
+ it 'routes /@:username/media' do
+ expect(get("/@#{username}/media")).to route_to('home#index', username_with_domain: username, any: 'media')
+ end
+
+ it 'routes /@:username/tagged/:tag' do
+ expect(get("/@#{username}/tagged/foo")).to route_to('home#index', username_with_domain: username, any: 'tagged/foo')
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 6ae965464..98666f23d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9663,11 +9663,6 @@ regjsparser@^0.8.2:
dependencies:
jsesc "~0.5.0"
-rellax@^1.12.1:
- version "1.12.1"
- resolved "https://registry.yarnpkg.com/rellax/-/rellax-1.12.1.tgz#1b433ef7ac4aa3573449a33efab391c112f6b34d"
- integrity sha512-XBIi0CDpW5FLTujYjYBn1CIbK2CJL6TsAg/w409KghP2LucjjzBjsujXDAjyBLWgsfupfUcL5WzdnIPcGfK7XA==
-
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
--
cgit
From 7c152acb2cc545a87610de349a94e14f45fbed5d Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 22 Oct 2022 11:44:41 +0200
Subject: Change settings area to be separated into categories in admin UI
(#19407)
And update all descriptions
---
app/controllers/admin/settings/about_controller.rb | 9 ++
.../admin/settings/appearance_controller.rb | 9 ++
.../admin/settings/branding_controller.rb | 9 ++
.../admin/settings/content_retention_controller.rb | 9 ++
.../admin/settings/discovery_controller.rb | 9 ++
.../admin/settings/registrations_controller.rb | 9 ++
app/controllers/admin/settings_controller.rb | 10 +-
app/controllers/admin/site_uploads_controller.rb | 2 +-
app/helpers/admin/settings_helper.rb | 7 --
app/javascript/styles/mastodon/admin.scss | 82 +++++++++++++----
app/javascript/styles/mastodon/components.scss | 5 +
app/javascript/styles/mastodon/forms.scss | 16 +++-
app/models/form/admin_settings.rb | 57 +++++++-----
.../rest/extended_description_serializer.rb | 12 ++-
app/views/admin/settings/about/show.html.haml | 33 +++++++
app/views/admin/settings/appearance/show.html.haml | 34 +++++++
app/views/admin/settings/branding/show.html.haml | 39 ++++++++
.../settings/content_retention/show.html.haml | 22 +++++
app/views/admin/settings/discovery/show.html.haml | 40 ++++++++
app/views/admin/settings/edit.html.haml | 102 ---------------------
.../admin/settings/registrations/show.html.haml | 27 ++++++
app/views/admin/settings/shared/_links.html.haml | 8 ++
app/views/layouts/admin.html.haml | 11 ++-
config/locales/en.yml | 87 +++++-------------
config/locales/simple_form.en.yml | 37 ++++++++
config/navigation.rb | 2 +-
config/routes.rb | 13 ++-
.../admin/settings/branding_controller_spec.rb | 53 +++++++++++
spec/controllers/admin/settings_controller_spec.rb | 71 --------------
29 files changed, 528 insertions(+), 296 deletions(-)
create mode 100644 app/controllers/admin/settings/about_controller.rb
create mode 100644 app/controllers/admin/settings/appearance_controller.rb
create mode 100644 app/controllers/admin/settings/branding_controller.rb
create mode 100644 app/controllers/admin/settings/content_retention_controller.rb
create mode 100644 app/controllers/admin/settings/discovery_controller.rb
create mode 100644 app/controllers/admin/settings/registrations_controller.rb
create mode 100644 app/views/admin/settings/about/show.html.haml
create mode 100644 app/views/admin/settings/appearance/show.html.haml
create mode 100644 app/views/admin/settings/branding/show.html.haml
create mode 100644 app/views/admin/settings/content_retention/show.html.haml
create mode 100644 app/views/admin/settings/discovery/show.html.haml
delete mode 100644 app/views/admin/settings/edit.html.haml
create mode 100644 app/views/admin/settings/registrations/show.html.haml
create mode 100644 app/views/admin/settings/shared/_links.html.haml
create mode 100644 spec/controllers/admin/settings/branding_controller_spec.rb
delete mode 100644 spec/controllers/admin/settings_controller_spec.rb
(limited to 'app/helpers')
diff --git a/app/controllers/admin/settings/about_controller.rb b/app/controllers/admin/settings/about_controller.rb
new file mode 100644
index 000000000..86327fe39
--- /dev/null
+++ b/app/controllers/admin/settings/about_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AboutController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_about_path
+ end
+end
diff --git a/app/controllers/admin/settings/appearance_controller.rb b/app/controllers/admin/settings/appearance_controller.rb
new file mode 100644
index 000000000..39b2448d8
--- /dev/null
+++ b/app/controllers/admin/settings/appearance_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::AppearanceController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_appearance_path
+ end
+end
diff --git a/app/controllers/admin/settings/branding_controller.rb b/app/controllers/admin/settings/branding_controller.rb
new file mode 100644
index 000000000..4a4d76f49
--- /dev/null
+++ b/app/controllers/admin/settings/branding_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::BrandingController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_branding_path
+ end
+end
diff --git a/app/controllers/admin/settings/content_retention_controller.rb b/app/controllers/admin/settings/content_retention_controller.rb
new file mode 100644
index 000000000..b88336a2c
--- /dev/null
+++ b/app/controllers/admin/settings/content_retention_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::ContentRetentionController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_content_retention_path
+ end
+end
diff --git a/app/controllers/admin/settings/discovery_controller.rb b/app/controllers/admin/settings/discovery_controller.rb
new file mode 100644
index 000000000..be4b57f79
--- /dev/null
+++ b/app/controllers/admin/settings/discovery_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::DiscoveryController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_discovery_path
+ end
+end
diff --git a/app/controllers/admin/settings/registrations_controller.rb b/app/controllers/admin/settings/registrations_controller.rb
new file mode 100644
index 000000000..b4a74349c
--- /dev/null
+++ b/app/controllers/admin/settings/registrations_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Admin::Settings::RegistrationsController < Admin::SettingsController
+ private
+
+ def after_update_redirect_path
+ admin_settings_registrations_path
+ end
+end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index dc1c79b7f..338a3638c 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -2,7 +2,7 @@
module Admin
class SettingsController < BaseController
- def edit
+ def show
authorize :settings, :show?
@admin_settings = Form::AdminSettings.new
@@ -15,14 +15,18 @@ module Admin
if @admin_settings.save
flash[:notice] = I18n.t('generic.changes_saved_msg')
- redirect_to edit_admin_settings_path
+ redirect_to after_update_redirect_path
else
- render :edit
+ render :show
end
end
private
+ def after_update_redirect_path
+ raise NotImplementedError
+ end
+
def settings_params
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
end
diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb
index cacecedb0..a5d2cf41c 100644
--- a/app/controllers/admin/site_uploads_controller.rb
+++ b/app/controllers/admin/site_uploads_controller.rb
@@ -9,7 +9,7 @@ module Admin
@site_upload.destroy!
- redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
+ redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
end
private
diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb
index baf14ab25..a133b4e7d 100644
--- a/app/helpers/admin/settings_helper.rb
+++ b/app/helpers/admin/settings_helper.rb
@@ -1,11 +1,4 @@
# frozen_string_literal: true
module Admin::SettingsHelper
- def site_upload_delete_hint(hint, var)
- upload = SiteUpload.find_by(var: var.to_s)
- return hint unless upload
-
- link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete }
- safe_join([hint, link], '
'.html_safe)
- end
end
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 1c5494cde..affe1c79c 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -188,21 +188,70 @@ $content-width: 840px;
padding-top: 30px;
}
- &-heading {
- display: flex;
+ &__heading {
padding-bottom: 36px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
- margin: -15px -15px 40px 0;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
+ margin-bottom: 40px;
- & > * {
- margin-top: 15px;
- margin-right: 15px;
+ &__row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ margin: -15px -15px 0 0;
+
+ & > * {
+ margin-top: 15px;
+ margin-right: 15px;
+ }
}
- &-actions {
+ &__tabs {
+ margin-top: 30px;
+ margin-bottom: -31px;
+
+ & > div {
+ display: flex;
+ gap: 10px;
+ }
+
+ a {
+ font-size: 14px;
+ display: inline-flex;
+ align-items: center;
+ padding: 7px 15px;
+ border-radius: 4px;
+ color: $darker-text-color;
+ text-decoration: none;
+ position: relative;
+ font-weight: 500;
+ gap: 5px;
+ white-space: nowrap;
+
+ &.selected {
+ font-weight: 700;
+ color: $primary-text-color;
+
+ &::after {
+ content: "";
+ display: block;
+ width: 100%;
+ border-bottom: 1px solid $ui-highlight-color;
+ position: absolute;
+ bottom: -5px;
+ left: 0;
+ }
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 4%);
+ }
+ }
+ }
+
+ &__actions {
display: inline-flex;
& > :not(:first-child) {
@@ -228,11 +277,7 @@ $content-width: 840px;
color: $secondary-text-color;
font-size: 24px;
line-height: 36px;
- font-weight: 400;
-
- @media screen and (max-width: $no-columns-breakpoint) {
- font-weight: 700;
- }
+ font-weight: 700;
}
h3 {
@@ -437,6 +482,11 @@ body,
}
}
+ & > div {
+ display: flex;
+ gap: 5px;
+ }
+
strong {
font-weight: 500;
text-transform: uppercase;
@@ -1143,7 +1193,7 @@ a.name-tag,
path:first-child {
fill: rgba($highlight-text-color, 0.25) !important;
- fill-opacity: 1 !important;
+ fill-opacity: 100% !important;
}
path:last-child {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 587eba663..5d0ff8536 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -29,6 +29,11 @@
background: transparent;
padding: 0;
cursor: pointer;
+ text-decoration: none;
+
+ &--destructive {
+ color: $error-value-color;
+ }
&:hover,
&:active {
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 69a0b22d6..25c9c9fe5 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -254,7 +254,7 @@ code {
& > label {
font-family: inherit;
- font-size: 16px;
+ font-size: 14px;
color: $primary-text-color;
display: block;
font-weight: 500;
@@ -291,6 +291,20 @@ code {
.input:last-child {
margin-bottom: 0;
}
+
+ &__thumbnail {
+ display: block;
+ margin: 0;
+ margin-bottom: 10px;
+ max-width: 100%;
+ height: auto;
+ border-radius: 4px;
+ background: url("images/void.png");
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
}
.fields-row {
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index b6bb3d795..957a32b7c 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -8,7 +8,6 @@ class Form::AdminSettings
site_contact_email
site_title
site_short_description
- site_description
site_extended_description
site_terms
registrations_mode
@@ -53,45 +52,55 @@ class Form::AdminSettings
attr_accessor(*KEYS)
- validates :site_short_description, :site_description, html: { wrap_with: :p }
- validates :site_extended_description, :site_terms, :closed_registrations_message, html: true
- validates :registrations_mode, inclusion: { in: %w(open approved none) }
- validates :site_contact_email, :site_contact_username, presence: true
- validates :site_contact_username, existing_username: true
- validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
- validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
- validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
- validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true
+ validates :registrations_mode, inclusion: { in: %w(open approved none) }, if: -> { defined?(@registrations_mode) }
+ validates :site_contact_email, :site_contact_username, presence: true, if: -> { defined?(@site_contact_username) || defined?(@site_contact_email) }
+ validates :site_contact_username, existing_username: true, if: -> { defined?(@site_contact_username) }
+ validates :bootstrap_timeline_accounts, existing_username: { multiple: true }, if: -> { defined?(@bootstrap_timeline_accounts) }
+ validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks) }
+ validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
+ validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
+ validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) }
- def initialize(_attributes = {})
- super
- initialize_attributes
+ KEYS.each do |key|
+ define_method(key) do
+ return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}")
+
+ stored_value = begin
+ if UPLOAD_KEYS.include?(key)
+ SiteUpload.where(var: key).first_or_initialize(var: key)
+ else
+ Setting.public_send(key)
+ end
+ end
+
+ instance_variable_set("@#{key}", stored_value)
+ end
+ end
+
+ UPLOAD_KEYS.each do |key|
+ define_method("#{key}=") do |file|
+ value = public_send(key)
+ value.file = file
+ end
end
def save
return false unless valid?
KEYS.each do |key|
- value = instance_variable_get("@#{key}")
+ next unless instance_variable_defined?("@#{key}")
- if UPLOAD_KEYS.include?(key) && !value.nil?
- upload = SiteUpload.where(var: key).first_or_initialize(var: key)
- upload.update(file: value)
+ if UPLOAD_KEYS.include?(key)
+ public_send(key).save
else
setting = Setting.where(var: key).first_or_initialize(var: key)
- setting.update(value: typecast_value(key, value))
+ setting.update(value: typecast_value(key, instance_variable_get("@#{key}")))
end
end
end
private
- def initialize_attributes
- KEYS.each do |key|
- instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
- end
- end
-
def typecast_value(key, value)
if BOOLEAN_KEYS.include?(key)
value == '1'
diff --git a/app/serializers/rest/extended_description_serializer.rb b/app/serializers/rest/extended_description_serializer.rb
index 0c3649033..c0fa3450d 100644
--- a/app/serializers/rest/extended_description_serializer.rb
+++ b/app/serializers/rest/extended_description_serializer.rb
@@ -8,6 +8,16 @@ class REST::ExtendedDescriptionSerializer < ActiveModel::Serializer
end
def content
- object.text
+ if object.text.present?
+ markdown.render(object.text)
+ else
+ ''
+ end
+ end
+
+ private
+
+ def markdown
+ @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
end
end
diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml
new file mode 100644
index 000000000..366d213f6
--- /dev/null
+++ b/app/views/admin/settings/about/show.html.haml
@@ -0,0 +1,33 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.about.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_about_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.about.preamble')
+
+ .fields-group
+ = f.input :site_extended_description, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+ %p.hint
+ = t 'admin.settings.about.rules_hint'
+ = link_to t('admin.settings.about.manage_rules'), admin_rules_path
+
+ .fields-row
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
+ .fields-group
+ = f.input :site_terms, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml
new file mode 100644
index 000000000..d321c4b04
--- /dev/null
+++ b/app/views/admin/settings/appearance/show.html.haml
@@ -0,0 +1,34 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.appearance.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_appearance_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.appearance.preamble')
+
+ .fields-group
+ = f.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false
+
+ .fields-group
+ = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }
+
+ .fields-row
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :mascot, as: :file, wrapper: :with_block_label
+
+ .fields-row__column.fields-row__column-6.fields-group
+ - if @admin_settings.mascot.persisted?
+ = image_tag @admin_settings.mascot.file.url, class: 'fields-group__thumbnail'
+ = link_to admin_site_upload_path(@admin_settings.mascot), data: { method: :delete }, class: 'link-button link-button--destructive' do
+ = fa_icon 'trash fw'
+ = t('admin.site_uploads.delete')
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml
new file mode 100644
index 000000000..74a6fadf9
--- /dev/null
+++ b/app/views/admin/settings/branding/show.html.haml
@@ -0,0 +1,39 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.branding.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.branding.preamble')
+
+ .fields-group
+ = f.input :site_title, wrapper: :with_label
+
+ .fields-row
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :site_contact_username, wrapper: :with_label
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :site_contact_email, wrapper: :with_label
+
+ .fields-group
+ = f.input :site_short_description, wrapper: :with_block_label, as: :text, input_html: { rows: 2, maxlength: 200 }
+
+ .fields-row
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :thumbnail, as: :file, wrapper: :with_block_label
+ .fields-row__column.fields-row__column-6.fields-group
+ - if @admin_settings.thumbnail.persisted?
+ = image_tag @admin_settings.thumbnail.file.url(:'@1x'), class: 'fields-group__thumbnail'
+ = link_to admin_site_upload_path(@admin_settings.thumbnail), data: { method: :delete }, class: 'link-button link-button--destructive' do
+ = fa_icon 'trash fw'
+ = t('admin.site_uploads.delete')
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml
new file mode 100644
index 000000000..36856127f
--- /dev/null
+++ b/app/views/admin/settings/content_retention/show.html.haml
@@ -0,0 +1,22 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.content_retention.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_content_retention_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.content_retention.preamble')
+
+ .fields-group
+ = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+ = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+ = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml
new file mode 100644
index 000000000..e63c853fb
--- /dev/null
+++ b/app/views/admin/settings/discovery/show.html.haml
@@ -0,0 +1,40 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.discovery.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_discovery_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.discovery.preamble')
+
+ %h4= t('admin.settings.discovery.trends')
+
+ .fields-group
+ = f.input :trends, as: :boolean, wrapper: :with_label
+
+ .fields-group
+ = f.input :trendable_by_default, as: :boolean, wrapper: :with_label
+
+ %h4= t('admin.settings.discovery.public_timelines')
+
+ .fields-group
+ = f.input :timeline_preview, as: :boolean, wrapper: :with_label
+
+ %h4= t('admin.settings.discovery.follow_recommendations')
+
+ .fields-group
+ = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label
+
+ %h4= t('admin.settings.discovery.profile_directory')
+
+ .fields-group
+ = f.input :profile_directory, as: :boolean, wrapper: :with_label
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
deleted file mode 100644
index 15b1a2498..000000000
--- a/app/views/admin/settings/edit.html.haml
+++ /dev/null
@@ -1,102 +0,0 @@
-- content_for :header_tags do
- = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
-
-- content_for :page_title do
- = t('admin.settings.title')
-
- - content_for :heading_actions do
- = button_tag t('generic.save_changes'), class: 'button', form: 'edit_admin'
-
-= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch, id: 'edit_admin' } do |f|
- = render 'shared/error_messages', object: @admin_settings
-
- .fields-group
- = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')
-
- .fields-row
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :theme, collection: Themes.instance.names, label: t('simple_form.labels.defaults.setting_theme'), label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, label: t('admin.settings.registrations_mode.title'), include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
-
- .fields-row
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :site_contact_username, wrapper: :with_label, label: t('admin.settings.contact_information.username')
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
-
- .fields-group
- = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
-
- .fields-group
- = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
-
- .fields-row
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: site_upload_delete_hint(t('admin.settings.thumbnail.desc_html'), :thumbnail)
-
- .fields-row
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :mascot, as: :file, wrapper: :with_block_label, label: t('admin.settings.mascot.title'), hint: site_upload_delete_hint(t('admin.settings.mascot.desc_html'), :mascot)
-
- %hr.spacer/
-
- .fields-group
- = f.input :require_invite_text, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.require_invite_text.title'), hint: t('admin.settings.registrations.require_invite_text.desc_html'), disabled: !approved_registrations?
-
- %hr.spacer/
-
- .fields-group
- = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label, label: t('admin.settings.bootstrap_timeline_accounts.title'), hint: t('admin.settings.bootstrap_timeline_accounts.desc_html')
-
- %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')
-
- - 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'), recommended: true
-
- .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'), recommended: true
-
- .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 :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'), recommended: :not_recommended
-
- .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')
-
- %hr.spacer/
-
- .fields-row
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
- .fields-row__column.fields-row__column-6.fields-group
- = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
-
- .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 :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')
-
- %hr.spacer/
-
- .fields-group
- = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
- = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
- = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }
-
- .actions
- = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml
new file mode 100644
index 000000000..0129332d7
--- /dev/null
+++ b/app/views/admin/settings/registrations/show.html.haml
@@ -0,0 +1,27 @@
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+ = t('admin.settings.registrations.title')
+
+- content_for :heading do
+ %h2= t('admin.settings.title')
+ = render partial: 'admin/settings/shared/links'
+
+= simple_form_for @admin_settings, url: admin_settings_branding_path, html: { method: :patch } do |f|
+ = render 'shared/error_messages', object: @admin_settings
+
+ %p.lead= t('admin.settings.registrations.preamble')
+
+ .fields-row
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: lambda { |mode| I18n.t("admin.settings.registrations_mode.modes.#{mode}") }
+
+ .fields-row__column.fields-row__column-6.fields-group
+ = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations?
+
+ .fields-group
+ = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 }
+
+ .actions
+ = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml
new file mode 100644
index 000000000..1294c26ce
--- /dev/null
+++ b/app/views/admin/settings/shared/_links.html.haml
@@ -0,0 +1,8 @@
+.content__heading__tabs
+ = render_navigation renderer: :links do |primary|
+ - primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path
+ - primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path
+ - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path
+ - primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path
+ - primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path
+ - primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index e577b9803..59021ad88 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -22,15 +22,16 @@
.content-wrapper
.content
- .content-heading
+ .content__heading
- if content_for?(:heading)
= yield :heading
- else
- %h2= yield :page_title
+ .content__heading__row
+ %h2= yield :page_title
- - if :heading_actions
- .content-heading-actions
- = yield :heading_actions
+ - if content_for?(:heading_actions)
+ .content__heading__actions
+ = yield :heading_actions
= render 'application/flashes'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 412178ca3..70850d478 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -667,79 +667,40 @@ en:
empty: No server rules have been defined yet.
title: Server rules
settings:
- activity_api_enabled:
- desc_html: Counts of locally published posts, active users, and new registrations in weekly buckets
- title: Publish aggregate statistics about user activity in the API
- bootstrap_timeline_accounts:
- desc_html: Separate multiple usernames by comma. These accounts will be guaranteed to be shown in follow recommendations
- title: Recommend these accounts to new users
- contact_information:
- email: Business e-mail
- username: Contact username
- custom_css:
- desc_html: Modify the look with CSS loaded on every page
- title: Custom CSS
- default_noindex:
- desc_html: Affects all users who have not changed this setting themselves
- title: Opt users out of search engine indexing by default
+ about:
+ manage_rules: Manage server rules
+ preamble: Provide in-depth information about how the server is operated, moderated, funded.
+ rules_hint: There is a dedicated area for rules that your users are expected to adhere to.
+ title: About
+ appearance:
+ preamble: Customize Mastodon's web interface.
+ title: Appearance
+ branding:
+ preamble: Your server's branding differentiates it from other servers in the network. This information may be displayed across a variety of environments, such as Mastodon's web interface, native applications, in link previews on other websites and within messaging apps, and so on. For this reason, it is best to keep this information clear, short and concise.
+ title: Branding
+ content_retention:
+ preamble: Control how user-generated content is stored in Mastodon.
+ title: Content retention
+ discovery:
+ follow_recommendations: Follow recommendations
+ preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server.
+ profile_directory: Profile directory
+ public_timelines: Public timelines
+ title: Discovery
+ trends: Trends
domain_blocks:
all: To everyone
disabled: To no one
- title: Show domain blocks
users: To logged-in local users
- domain_blocks_rationale:
- title: Show rationale
- mascot:
- desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot
- title: Mascot image
- peers_api_enabled:
- desc_html: Domain names this server has encountered in the fediverse
- title: Publish list of discovered servers in the API
- preview_sensitive_media:
- desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
- title: Show sensitive media in OpenGraph previews
- profile_directory:
- desc_html: Allow users to be discoverable
- title: Enable profile directory
registrations:
- closed_message:
- desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags
- title: Closed registration message
- require_invite_text:
- desc_html: When registrations require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
- title: Require new users to enter a reason to join
+ preamble: Control who can create an account on your server.
+ title: Registrations
registrations_mode:
modes:
approved: Approval required for sign up
none: Nobody can sign up
open: Anyone can sign up
- title: Registrations mode
- site_description:
- desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <a>
and <em>
.
- title: Server description
- site_description_extended:
- desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags
- title: Custom extended information
- site_short_description:
- desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph.
- title: Short server description
- site_terms:
- desc_html: You can write your own privacy policy. You can use HTML tags
- title: Custom privacy policy
- site_title: Server name
- thumbnail:
- desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
- title: Server thumbnail
- timeline_preview:
- desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication
- title: Allow unauthenticated access to public timeline
- title: Site settings
- trendable_by_default:
- desc_html: Specific trending content can still be explicitly disallowed
- title: Allow trends without prior review
- trends:
- desc_html: Publicly display previously reviewed content that is currently trending
- title: Trends
+ title: Server Settings
site_uploads:
delete: Delete uploaded file
destroyed_msg: Site upload successfully deleted!
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index db5b45e41..64281d7b7 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -75,8 +75,25 @@ en:
warn: Hide the filtered content behind a warning mentioning the filter's title
form_admin_settings:
backups_retention_period: Keep generated user archives for the specified number of days.
+ bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations.
+ closed_registrations_message: Displayed when sign-ups are closed
content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible.
+ custom_css: You can apply custom styles on the web version of Mastodon.
+ mascot: Overrides the illustration in the advanced web interface.
media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
+ profile_directory: The profile directory lists all users who have opted-in to be discoverable.
+ require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
+ site_contact_email: How people can reach you for legal or support inquiries.
+ site_contact_username: How people can reach you on Mastodon.
+ site_extended_description: Any additional information that may be useful to visitors and your users. Can be structured with Markdown syntax.
+ site_short_description: A short description to help uniquely identify your server. Who is running it, who is it for?
+ site_terms: Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax.
+ site_title: How people may refer to your server besides its domain name.
+ theme: Theme that logged out visitors and new users see.
+ thumbnail: A roughly 2:1 image displayed alongside your server information.
+ timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server.
+ trendable_by_default: Skip manual review of trending content. Individual items can still be removed from trends after the fact.
+ trends: Trends show which posts, hashtags and news stories are gaining traction on your server.
form_challenge:
current_password: You are entering a secure area
imports:
@@ -213,8 +230,28 @@ en:
warn: Hide with a warning
form_admin_settings:
backups_retention_period: User archive retention period
+ bootstrap_timeline_accounts: Always recommend these accounts to new users
+ closed_registrations_message: Custom message when sign-ups are not available
content_cache_retention_period: Content cache retention period
+ custom_css: Custom CSS
+ mascot: Custom mascot (legacy)
media_cache_retention_period: Media cache retention period
+ profile_directory: Enable profile directory
+ registrations_mode: Who can sign-up
+ require_invite_text: Require a reason to join
+ show_domain_blocks: Show domain blocks
+ show_domain_blocks_rationale: Show why domains were blocked
+ site_contact_email: Contact e-mail
+ site_contact_username: Contact username
+ site_extended_description: Extended description
+ site_short_description: Server description
+ site_terms: Privacy Policy
+ site_title: Server name
+ theme: Default theme
+ thumbnail: Server thumbnail
+ timeline_preview: Allow unauthenticated access to public timelines
+ trendable_by_default: Allow trends without prior review
+ trends: Enable trends
interactions:
must_be_follower: Block notifications from non-followers
must_be_following: Block notifications from people you don't follow
diff --git a/config/navigation.rb b/config/navigation.rb
index 706de0471..e901fb932 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -52,7 +52,7 @@ SimpleNavigation::Configuration.run do |navigation|
n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), nil, if: -> { current_user.can?(:view_dashboard, :manage_settings, :manage_rules, :manage_announcements, :manage_custom_emojis, :manage_webhooks, :manage_federation) } do |s|
s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_path, if: -> { current_user.can?(:view_dashboard) }
- s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
+ s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_path, if: -> { current_user.can?(:manage_settings) }, highlights_on: %r{/admin/settings}
s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}, if: -> { current_user.can?(:manage_rules) }
s.item :roles, safe_join([fa_icon('vcard fw'), t('admin.roles.title')]), admin_roles_path, highlights_on: %r{/admin/roles}, if: -> { current_user.can?(:manage_roles) }
s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
diff --git a/config/routes.rb b/config/routes.rb
index 1ed585f19..b44479e77 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -241,7 +241,18 @@ Rails.application.routes.draw do
end
end
- resource :settings, only: [:edit, :update]
+ get '/settings', to: redirect('/admin/settings/branding')
+ get '/settings/edit', to: redirect('/admin/settings/branding')
+
+ namespace :settings do
+ resource :branding, only: [:show, :update], controller: 'branding'
+ resource :registrations, only: [:show, :update], controller: 'registrations'
+ resource :content_retention, only: [:show, :update], controller: 'content_retention'
+ resource :about, only: [:show, :update], controller: 'about'
+ resource :appearance, only: [:show, :update], controller: 'appearance'
+ resource :discovery, only: [:show, :update], controller: 'discovery'
+ end
+
resources :site_uploads, only: [:destroy]
resources :invites, only: [:index, :create, :destroy] do
diff --git a/spec/controllers/admin/settings/branding_controller_spec.rb b/spec/controllers/admin/settings/branding_controller_spec.rb
new file mode 100644
index 000000000..ee1c441bc
--- /dev/null
+++ b/spec/controllers/admin/settings/branding_controller_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::Settings::BrandingController, type: :controller do
+ render_views
+
+ describe 'When signed in as an admin' do
+ before do
+ sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
+ end
+
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ describe 'PUT #update' do
+ before do
+ allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
+ end
+
+ around do |example|
+ before = Setting.site_short_description
+ Setting.site_short_description = nil
+ example.run
+ Setting.site_short_description = before
+ Setting.new_setting_key = nil
+ end
+
+ it 'cannot create a setting value for a non-admin key' do
+ expect(Setting.new_setting_key).to be_blank
+
+ patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
+
+ expect(response).to redirect_to(admin_settings_branding_path)
+ expect(Setting.new_setting_key).to be_nil
+ end
+
+ it 'creates a settings value that didnt exist before for eligible key' do
+ expect(Setting.site_short_description).to be_blank
+
+ patch :update, params: { form_admin_settings: { site_short_description: 'New key value' } }
+
+ expect(response).to redirect_to(admin_settings_branding_path)
+ expect(Setting.site_short_description).to eq 'New key value'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
deleted file mode 100644
index 46749f76c..000000000
--- a/spec/controllers/admin/settings_controller_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::SettingsController, type: :controller do
- render_views
-
- describe 'When signed in as an admin' do
- before do
- sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
- end
-
- describe 'GET #edit' do
- it 'returns http success' do
- get :edit
-
- expect(response).to have_http_status(200)
- end
- end
-
- describe 'PUT #update' do
- before do
- allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
- end
-
- describe 'for a record that doesnt exist' do
- around do |example|
- before = Setting.site_extended_description
- Setting.site_extended_description = nil
- example.run
- Setting.site_extended_description = before
- Setting.new_setting_key = nil
- end
-
- it 'cannot create a setting value for a non-admin key' do
- expect(Setting.new_setting_key).to be_blank
-
- patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
-
- expect(response).to redirect_to(edit_admin_settings_path)
- expect(Setting.new_setting_key).to be_nil
- end
-
- it 'creates a settings value that didnt exist before for eligible key' do
- expect(Setting.site_extended_description).to be_blank
-
- patch :update, params: { form_admin_settings: { site_extended_description: 'New key value' } }
-
- expect(response).to redirect_to(edit_admin_settings_path)
- expect(Setting.site_extended_description).to eq 'New key value'
- end
- end
-
- context do
- around do |example|
- site_title = Setting.site_title
- example.run
- Setting.site_title = site_title
- end
-
- it 'updates a settings value' do
- Setting.site_title = 'Original'
- patch :update, params: { form_admin_settings: { site_title: 'New title' } }
-
- expect(response).to redirect_to(edit_admin_settings_path)
- expect(Setting.site_title).to eq 'New title'
- end
- end
- end
- end
-end
--
cgit
From 6f01111863bfb15b3574c95c8d60d59ae1d79772 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Tue, 25 Oct 2022 21:43:33 +0200
Subject: Fix wrong size of avatars in admin UI (#19457)
---
app/helpers/home_helper.rb | 2 +-
app/javascript/styles/mastodon/widgets.scss | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
(limited to 'app/helpers')
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
index 4da68500a..f41104709 100644
--- a/app/helpers/home_helper.rb
+++ b/app/helpers/home_helper.rb
@@ -23,7 +23,7 @@ module HomeHelper
else
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
content_tag(:div, class: 'account__avatar-wrapper') do
- image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
+ image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar', width: 46, height: 46)
end +
content_tag(:span, class: 'display-name') do
content_tag(:bdi) do
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 260a97c6d..0e39dc87b 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -359,8 +359,9 @@
vertical-align: initial !important;
}
- &__interrelationships {
+ tbody td.accounts-table__interrelationships {
width: 21px;
+ padding-right: 16px;
}
.fa {
--
cgit
From 7926cb1bc7f502c10dc3a6ef3ca97ffc5a0b3a6a Mon Sep 17 00:00:00 2001
From: Matthias Bethke
Date: Sat, 29 Oct 2022 17:04:56 +0530
Subject: fix name of Lao language (#19520)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It said ພາສາ or pha-sa, which means just "language" in Lao. "ພາສາລາວ",
pha-sa lao, is the full name but the short "ລາວ" is commonly used.
---
app/helpers/languages_helper.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'app/helpers')
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index 4077e19bd..e5bae2c6b 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -97,7 +97,7 @@ module LanguagesHelper
lg: ['Ganda', 'Luganda'].freeze,
li: ['Limburgish', 'Limburgs'].freeze,
ln: ['Lingala', 'Lingála'].freeze,
- lo: ['Lao', 'ພາສາ'].freeze,
+ lo: ['Lao', 'ລາວ'].freeze,
lt: ['Lithuanian', 'lietuvių kalba'].freeze,
lu: ['Luba-Katanga', 'Tshiluba'].freeze,
lv: ['Latvian', 'latviešu valoda'].freeze,
--
cgit
From 1dca08b76f25d15365127ded37202d783a50e298 Mon Sep 17 00:00:00 2001
From: Claire
Date: Thu, 3 Nov 2022 16:06:42 +0100
Subject: Fix admin action logs page (#19649)
* Add tests
* Fix crash when trying to display orphaned action logs
* Add migration for older admin action logs
---
app/helpers/admin/action_logs_helper.rb | 20 ++-
.../20221101190723_backfill_admin_action_logs.rb | 150 +++++++++++++++++++++
db/schema.rb | 2 +-
lib/tasks/tests.rake | 51 +++++--
.../admin/action_logs_controller_spec.rb | 13 ++
5 files changed, 221 insertions(+), 15 deletions(-)
create mode 100644 db/post_migrate/20221101190723_backfill_admin_action_logs.rb
(limited to 'app/helpers')
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index fd1977ac5..215ecea0d 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -4,15 +4,19 @@ module Admin::ActionLogsHelper
def log_target(log)
case log.target_type
when 'Account'
- link_to log.human_identifier, admin_account_path(log.target_id)
+ link_to (log.human_identifier.presence || I18n.t('admin.action_logs.deleted_account')), admin_account_path(log.target_id)
when 'User'
- link_to log.human_identifier, admin_account_path(log.route_param)
+ if log.route_param.present?
+ link_to log.human_identifier, admin_account_path(log.route_param)
+ else
+ I18n.t('admin.action_logs.deleted_account')
+ end
when 'UserRole'
link_to log.human_identifier, admin_roles_path(log.target_id)
when 'Report'
- link_to "##{log.human_identifier}", admin_report_path(log.target_id)
+ link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id)
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
- link_to log.human_identifier, "https://#{log.human_identifier}"
+ link_to log.human_identifier, "https://#{log.human_identifier.presence}"
when 'Status'
link_to log.human_identifier, log.permalink
when 'AccountWarning'
@@ -22,9 +26,13 @@ module Admin::ActionLogsHelper
when 'IpBlock', 'Instance', 'CustomEmoji'
log.human_identifier
when 'CanonicalEmailBlock'
- content_tag(:samp, log.human_identifier[0...7], title: log.human_identifier)
+ content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier)
when 'Appeal'
- link_to log.human_identifier, disputes_strike_path(log.route_param)
+ if log.route_param.present?
+ link_to log.human_identifier, disputes_strike_path(log.route_param.presence)
+ else
+ I18n.t('admin.action_logs.deleted_account')
+ end
end
end
end
diff --git a/db/post_migrate/20221101190723_backfill_admin_action_logs.rb b/db/post_migrate/20221101190723_backfill_admin_action_logs.rb
new file mode 100644
index 000000000..9a64d1715
--- /dev/null
+++ b/db/post_migrate/20221101190723_backfill_admin_action_logs.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ class Account < ApplicationRecord
+ # Dummy class, to make migration possible across version changes
+ has_one :user, inverse_of: :account
+
+ def local?
+ domain.nil?
+ end
+
+ def acct
+ local? ? username : "#{username}@#{domain}"
+ end
+ end
+
+ class User < ApplicationRecord
+ # Dummy class, to make migration possible across version changes
+ belongs_to :account
+ end
+
+ class Status < ApplicationRecord
+ include RoutingHelper
+
+ # Dummy class, to make migration possible across version changes
+ belongs_to :account
+
+ def local?
+ attributes['local'] || attributes['uri'].nil?
+ end
+
+ def uri
+ local? ? activity_account_status_url(account, self) : attributes['uri']
+ end
+ end
+
+ class DomainBlock < ApplicationRecord; end
+ class DomainAllow < ApplicationRecord; end
+ class EmailDomainBlock < ApplicationRecord; end
+ class UnavailableDomain < ApplicationRecord; end
+
+ class AccountWarning < ApplicationRecord
+ # Dummy class, to make migration possible across version changes
+ belongs_to :account
+ end
+
+ class Announcement < ApplicationRecord; end
+ class IpBlock < ApplicationRecord; end
+ class CustomEmoji < ApplicationRecord; end
+ class CanonicalEmailBlock < ApplicationRecord; end
+
+ class Appeal < ApplicationRecord
+ # Dummy class, to make migration possible across version changes
+ belongs_to :account
+ end
+
+ class AdminActionLog < ApplicationRecord
+ # Dummy class, to make migration possible across version changes
+
+ # Cannot use usual polymorphic support because of namespacing issues
+ belongs_to :status, foreign_key: :target_id
+ belongs_to :account, foreign_key: :target_id
+ belongs_to :user, foreign_key: :user_id
+ belongs_to :domain_block, foreign_key: :target_id
+ belongs_to :domain_allow, foreign_key: :target_id
+ belongs_to :email_domain_block, foreign_key: :target_id
+ belongs_to :unavailable_domain, foreign_key: :target_id
+ belongs_to :account_warning, foreign_key: :target_id
+ belongs_to :announcement, foreign_key: :target_id
+ belongs_to :ip_block, foreign_key: :target_id
+ belongs_to :custom_emoji, foreign_key: :target_id
+ belongs_to :canonical_email_block, foreign_key: :target_id
+ belongs_to :appeal, foreign_key: :target_id
+ end
+
+ def up
+ safety_assured do
+ AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
+ next if log.account.nil?
+ log.update(human_identifier: log.account.acct)
+ end
+
+ AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
+ next if log.user.nil?
+ log.update(human_identifier: log.user.account.acct, route_param: log.user.account_id)
+ end
+
+ Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
+
+ AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
+ next if log.domain_block.nil?
+ log.update(human_identifier: log.domain_block.domain)
+ end
+
+ AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
+ next if log.domain_allow.nil?
+ log.update(human_identifier: log.domain_allow.domain)
+ end
+
+ AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
+ next if log.email_domain_block.nil?
+ log.update(human_identifier: log.email_domain_block.domain)
+ end
+
+ AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
+ next if log.unavailable_domain.nil?
+ log.update(human_identifier: log.unavailable_domain.domain)
+ end
+
+ AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
+ next if log.status.nil?
+ log.update(human_identifier: log.status.account.acct, permalink: log.status.uri)
+ end
+
+ AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
+ next if log.account_warning.nil?
+ log.update(human_identifier: log.account_warning.account.acct)
+ end
+
+ AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
+ next if log.announcement.nil?
+ log.update(human_identifier: log.announcement.text)
+ end
+
+ AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
+ next if log.ip_block.nil?
+ log.update(human_identifier: "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
+ end
+
+ AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
+ next if log.custom_emoji.nil?
+ log.update(human_identifier: log.custom_emoji.shortcode)
+ end
+
+ AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
+ next if log.canonical_email_block.nil?
+ log.update(human_identifier: log.canonical_email_block.canonical_email_hash)
+ end
+
+ AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
+ next if log.appeal.nil?
+ log.update(human_identifier: log.appeal.account.acct, route_param: log.appeal.account_warning_id)
+ end
+ end
+ end
+
+ def down; end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d7e40b133..12ec37c11 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_10_25_171544) do
+ActiveRecord::Schema.define(version: 2022_11_01_190723) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index 65bff6a8e..96d2f7112 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -53,6 +53,41 @@ namespace :tests do
VALUES
(1, 2, 'test', '{ "home", "public" }', true, true, now(), now()),
(2, 2, 'take', '{ "home" }', false, false, now(), now());
+
+ -- Orphaned admin action logs
+
+ INSERT INTO "admin_action_logs"
+ (account_id, action, target_type, target_id, created_at, updated_at)
+ VALUES
+ (1, 'destroy', 'Account', 1312, now(), now()),
+ (1, 'destroy', 'User', 1312, now(), now()),
+ (1, 'destroy', 'Report', 1312, now(), now()),
+ (1, 'destroy', 'DomainBlock', 1312, now(), now()),
+ (1, 'destroy', 'EmailDomainBlock', 1312, now(), now()),
+ (1, 'destroy', 'Status', 1312, now(), now()),
+ (1, 'destroy', 'CustomEmoji', 1312, now(), now());
+
+ -- Admin action logs with linked objects
+
+ INSERT INTO "domain_blocks"
+ (id, domain, created_at, updated_at)
+ VALUES
+ (1, 'example.org', now(), now());
+
+ INSERT INTO "email_domain_blocks"
+ (id, domain, created_at, updated_at)
+ VALUES
+ (1, 'example.org', now(), now());
+
+ INSERT INTO "admin_action_logs"
+ (account_id, action, target_type, target_id, created_at, updated_at)
+ VALUES
+ (1, 'destroy', 'Account', 1, now(), now()),
+ (1, 'destroy', 'User', 1, now(), now()),
+ (1, 'destroy', 'DomainBlock', 1312, now(), now()),
+ (1, 'destroy', 'EmailDomainBlock', 1312, now(), now()),
+ (1, 'destroy', 'Status', 1, now(), now()),
+ (1, 'destroy', 'CustomEmoji', 3, now(), now());
SQL
end
@@ -207,18 +242,18 @@ namespace :tests do
-- custom emoji
INSERT INTO "custom_emojis"
- (shortcode, created_at, updated_at)
+ (id, shortcode, created_at, updated_at)
VALUES
- ('test', now(), now()),
- ('Test', now(), now()),
- ('blobcat', now(), now());
+ (1, 'test', now(), now()),
+ (2, 'Test', now(), now()),
+ (3, 'blobcat', now(), now());
INSERT INTO "custom_emojis"
- (shortcode, domain, uri, created_at, updated_at)
+ (id, shortcode, domain, uri, created_at, updated_at)
VALUES
- ('blobcat', 'remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
- ('blobcat', 'Remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
- ('Blobcat', 'remote.org', 'https://remote.org/emoji/Blobcat', now(), now());
+ (4, 'blobcat', 'remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
+ (5, 'blobcat', 'Remote.org', 'https://remote.org/emoji/blobcat', now(), now()),
+ (6, 'Blobcat', 'remote.org', 'https://remote.org/emoji/Blobcat', now(), now());
-- favourites
diff --git a/spec/controllers/admin/action_logs_controller_spec.rb b/spec/controllers/admin/action_logs_controller_spec.rb
index c1957258f..7cd8cdf46 100644
--- a/spec/controllers/admin/action_logs_controller_spec.rb
+++ b/spec/controllers/admin/action_logs_controller_spec.rb
@@ -3,6 +3,19 @@
require 'rails_helper'
describe Admin::ActionLogsController, type: :controller do
+ render_views
+
+ # Action logs typically cause issues when their targets are not in the database
+ let!(:account) { Fabricate(:account) }
+
+ let!(:orphaned_logs) do
+ %w(
+ Account User UserRole Report DomainBlock DomainAllow
+ EmailDomainBlock UnavailableDomain Status AccountWarning
+ Announcement IpBlock Instance CustomEmoji CanonicalEmailBlock Appeal
+ ).map { |type| Admin::ActionLog.new(account: account, action: 'destroy', target_type: type, target_id: 1312).save! }
+ end
+
describe 'GET #index' do
it 'returns 200' do
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
--
cgit
From c4b92b1aee27a813e24395d43e265cc02a8fe9a3 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 5 Nov 2022 00:09:52 +0100
Subject: Fix n+1 query during status removal (#19753)
---
app/helpers/application_helper.rb | 2 +-
app/services/remove_status_service.rb | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
(limited to 'app/helpers')
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9cc34cab6..8706f5c2a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -203,7 +203,7 @@ module ApplicationHelper
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
- if user_signed_in?
+ if user_signed_in? && current_user.functional?
state_params[:settings] = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {})
state_params[:push_subscription] = current_account.user.web_push_subscription(current_session)
state_params[:current_account] = current_account
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 37d2dabae..45cfb75f4 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -57,13 +57,13 @@ class RemoveStatusService < BaseService
end
def remove_from_followers
- @account.followers_for_local_distribution.reorder(nil).find_each do |follower|
+ @account.followers_for_local_distribution.includes(:user).reorder(nil).find_each do |follower|
FeedManager.instance.unpush_from_home(follower, @status)
end
end
def remove_from_lists
- @account.lists_for_local_distribution.select(:id, :account_id).reorder(nil).find_each do |list|
+ @account.lists_for_local_distribution.select(:id, :account_id).includes(account: :user).reorder(nil).find_each do |list|
FeedManager.instance.unpush_from_list(list, @status)
end
end
--
cgit
From 312d616371096235f1f317041300b8220c447613 Mon Sep 17 00:00:00 2001
From: Claire
Date: Sat, 5 Nov 2022 18:28:13 +0100
Subject: Change sign-in banner to reflect disabled or moved account status
(#19773)
---
app/helpers/application_helper.rb | 5 ++
app/javascript/mastodon/containers/mastodon.js | 2 +
.../ui/components/disabled_account_banner.js | 92 ++++++++++++++++++++++
.../features/ui/components/navigation_panel.js | 5 +-
app/javascript/mastodon/initial_state.js | 4 +
app/javascript/styles/mastodon/components.scss | 14 ++++
app/presenters/initial_state_presenter.rb | 3 +-
app/serializers/initial_state_serializer.rb | 5 ++
8 files changed, 127 insertions(+), 3 deletions(-)
create mode 100644 app/javascript/mastodon/features/ui/components/disabled_account_banner.js
(limited to 'app/helpers')
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8706f5c2a..4c20f1e14 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -211,6 +211,11 @@ module ApplicationHelper
state_params[:admin] = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
end
+ if user_signed_in? && !current_user.functional?
+ state_params[:disabled_account] = current_account
+ state_params[:moved_to_account] = current_account.moved_to_account
+ end
+
if single_user_mode?
state_params[:owner] = Account.local.without_suspended.where('id > 0').first
end
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 730695c49..724719f74 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -28,6 +28,7 @@ store.dispatch(fetchCustomEmojis());
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
+ disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
@@ -42,6 +43,7 @@ export default class Mastodon extends React.PureComponent {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
+ disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
diff --git a/app/javascript/mastodon/features/ui/components/disabled_account_banner.js b/app/javascript/mastodon/features/ui/components/disabled_account_banner.js
new file mode 100644
index 000000000..c9845d917
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/disabled_account_banner.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import { disabledAccountId, movedToAccountId, domain } from 'mastodon/initial_state';
+import { openModal } from 'mastodon/actions/modal';
+import { logOut } from 'mastodon/utils/log_out';
+
+const messages = defineMessages({
+ logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
+ logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
+});
+
+const mapStateToProps = (state) => ({
+ disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']),
+ movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined,
+});
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+ onLogout () {
+ dispatch(openModal('CONFIRM', {
+ message: intl.formatMessage(messages.logoutMessage),
+ confirm: intl.formatMessage(messages.logoutConfirm),
+ closeWhenConfirm: false,
+ onConfirm: () => logOut(),
+ }));
+ },
+});
+
+export default @injectIntl
+@connect(mapStateToProps, mapDispatchToProps)
+class DisabledAccountBanner extends React.PureComponent {
+
+ static propTypes = {
+ disabledAcct: PropTypes.string.isRequired,
+ movedToAcct: PropTypes.string,
+ onLogout: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ handleLogOutClick = e => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ this.props.onLogout();
+
+ return false;
+ }
+
+ render () {
+ const { disabledAcct, movedToAcct } = this.props;
+
+ const disabledAccountLink = (
+
+ {disabledAcct}@{domain}
+
+ );
+
+ return (
+
+
+ {movedToAcct ? (
+ {movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@{domain}`},
+ }}
+ />
+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+ }
+
+};
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js
index 4e9e39e2f..9a9309be0 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.js
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js
@@ -5,6 +5,7 @@ import { Link } from 'react-router-dom';
import Logo from 'mastodon/components/logo';
import { timelinePreview, showTrends } from 'mastodon/initial_state';
import ColumnLink from './column_link';
+import DisabledAccountBanner from './disabled_account_banner';
import FollowRequestsColumnLink from './follow_requests_column_link';
import ListPanel from './list_panel';
import NotificationsCounterIcon from './notifications_counter_icon';
@@ -41,7 +42,7 @@ class NavigationPanel extends React.Component {
render () {
const { intl } = this.props;
- const { signedIn } = this.context.identity;
+ const { signedIn, disabledAccountId } = this.context.identity;
return (
@@ -74,7 +75,7 @@ class NavigationPanel extends React.Component {
{!signedIn && (
-
+ { disabledAccountId ? : }
)}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index bb05dafdf..62fd4ac72 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -54,6 +54,7 @@
* @property {boolean} crop_images
* @property {boolean=} delete_modal
* @property {boolean=} disable_swiping
+ * @property {string=} disabled_account_id
* @property {boolean} display_media
* @property {string} domain
* @property {boolean=} expand_spoilers
@@ -61,6 +62,7 @@
* @property {string} locale
* @property {string | null} mascot
* @property {string=} me
+ * @property {string=} moved_to_account_id
* @property {string=} owner
* @property {boolean} profile_directory
* @property {boolean} registrations_open
@@ -104,6 +106,7 @@ export const boostModal = getMeta('boost_modal');
export const cropImages = getMeta('crop_images');
export const deleteModal = getMeta('delete_modal');
export const disableSwiping = getMeta('disable_swiping');
+export const disabledAccountId = getMeta('disabled_account_id');
export const displayMedia = getMeta('display_media');
export const domain = getMeta('domain');
export const expandSpoilers = getMeta('expand_spoilers');
@@ -111,6 +114,7 @@ export const forceSingleColumn = !getMeta('advanced_layout');
export const limitedFederationMode = getMeta('limited_federation_mode');
export const mascot = getMeta('mascot');
export const me = getMeta('me');
+export const movedToAccountId = getMeta('moved_to_account_id');
export const owner = getMeta('owner');
export const profile_directory = getMeta('profile_directory');
export const reduceMotion = getMeta('reduce_motion');
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2edb10857..542a2ce1b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -742,6 +742,20 @@
p {
color: $darker-text-color;
margin-bottom: 20px;
+
+ a {
+ color: $secondary-text-color;
+ text-decoration: none;
+ unicode-bidi: isolate;
+
+ &:hover {
+ text-decoration: underline;
+
+ .fa {
+ color: lighten($dark-text-color, 7%);
+ }
+ }
+ }
}
.button {
diff --git a/app/presenters/initial_state_presenter.rb b/app/presenters/initial_state_presenter.rb
index ed0479211..b87cff51e 100644
--- a/app/presenters/initial_state_presenter.rb
+++ b/app/presenters/initial_state_presenter.rb
@@ -2,7 +2,8 @@
class InitialStatePresenter < ActiveModelSerializers::Model
attributes :settings, :push_subscription, :token,
- :current_account, :admin, :owner, :text, :visibility
+ :current_account, :admin, :owner, :text, :visibility,
+ :disabled_account, :moved_to_account
def role
current_account&.user_role
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 02e45a92e..89f468ab5 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -57,6 +57,9 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:crop_images] = Setting.crop_images
end
+ store[:disabled_account_id] = object.disabled_account.id.to_s if object.disabled_account
+ store[:moved_to_account_id] = object.moved_to_account.id.to_s if object.moved_to_account
+
if Rails.configuration.x.single_user_mode
store[:owner] = object.owner&.id&.to_s
end
@@ -85,6 +88,8 @@ class InitialStateSerializer < ActiveModel::Serializer
store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account
store[object.admin.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin
store[object.owner.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner
+ store[object.disabled_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.disabled_account, serializer: REST::AccountSerializer) if object.disabled_account
+ store[object.moved_to_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.moved_to_account, serializer: REST::AccountSerializer) if object.moved_to_account
store
end
--
cgit
From 104157bd012f5d7306f3bd98962b49732d7d0915 Mon Sep 17 00:00:00 2001
From: Vyr Cossont
Date: Wed, 9 Nov 2022 06:23:52 -0800
Subject: Add Balaibalan, Láadan, Lingua Franca Nova, Lojban, Toki Pona to
language list (#20168)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add Balaibalan, Láadan, Lojban, Toki Pona to language list
Fixes #8995.
* Correct translated names for Lojban and Toki Pona
* Correct translated name for Balaibalan
* Add Lingua Franca Nova aka Elefen
* Disable unhelpful Rubocop checks
* Re-enable Rubocop checks at end of file
---
app/helpers/languages_helper.rb | 8 ++++++++
1 file changed, 8 insertions(+)
(limited to 'app/helpers')
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index e5bae2c6b..322548747 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Metrics/ModuleLength, Style/WordArray
module LanguagesHelper
ISO_639_1 = {
@@ -189,8 +190,13 @@ module LanguagesHelper
ISO_639_3 = {
ast: ['Asturian', 'Asturianu'].freeze,
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
+ jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
+ ldn: ['Láadan', 'Láadan'].freeze,
+ lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
+ tok: ['Toki Pona', 'toki pona'].freeze,
+ zba: ['Balaibalan', 'باليبلن'].freeze,
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
}.freeze
@@ -259,3 +265,5 @@ module LanguagesHelper
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end
+
+# rubocop:enable Metrics/ModuleLength, Style/WordArray
--
cgit