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