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 `<ColumnLoading />` 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 UIth-downstream
parent
fd61882f1a
commit
0fdfbe555e
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,7 @@
|
|||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('about.title')
|
= t('about.title')
|
||||||
|
|
||||||
|
- content_for :header_tags do
|
||||||
|
= render partial: 'shared/og'
|
||||||
|
|
||||||
= render partial: 'shared/web_app'
|
= render partial: 'shared/web_app'
|
||||||
|
@ -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)
|
|
@ -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)
|
|
@ -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}
|
|
@ -1,20 +1,6 @@
|
|||||||
- content_for :page_title do
|
|
||||||
= t('accounts.people_who_follow', name: display_name(@account))
|
|
||||||
|
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
%meta{ name: 'robots', content: 'noindex' }/
|
%meta{ name: 'robots', content: 'noindex' }/
|
||||||
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
= paginate @follows
|
= render 'shared/web_app'
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
- content_for :page_title do
|
|
||||||
= t('accounts.people_followed_by', name: display_name(@account))
|
|
||||||
|
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
%meta{ name: 'robots', content: 'noindex' }/
|
%meta{ name: 'robots', content: 'noindex' }/
|
||||||
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
|
||||||
|
|
||||||
= render 'accounts/header', account: @account
|
= render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
= paginate @follows
|
= render 'shared/web_app'
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
|
- unless request.path == '/'
|
||||||
|
%meta{ name: 'robots', content: 'noindex' }/
|
||||||
|
|
||||||
= render partial: 'shared/og'
|
= render partial: 'shared/og'
|
||||||
|
|
||||||
= render 'shared/web_app'
|
= render 'shared/web_app'
|
||||||
|
@ -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'
|
|
@ -1,4 +1,7 @@
|
|||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('privacy_policy.title')
|
= t('privacy_policy.title')
|
||||||
|
|
||||||
|
- content_for :header_tags do
|
||||||
|
= render partial: 'shared/og'
|
||||||
|
|
||||||
= render 'shared/web_app'
|
= render 'shared/web_app'
|
||||||
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
|||||||
|
- content_for :header_tags do
|
||||||
|
%meta{ name: 'robots', content: 'noindex' }/
|
||||||
|
= render partial: 'shared/og'
|
||||||
|
|
||||||
|
= render partial: 'shared/web_app'
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,31 +1,83 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe 'Routes under accounts/' do
|
describe 'Routes under accounts/' do
|
||||||
describe 'the route for accounts who are followers of an account' do
|
context 'with local username' do
|
||||||
it 'routes to the followers action with the right username' do
|
let(:username) { 'alice' }
|
||||||
expect(get('/users/name/followers')).
|
|
||||||
to route_to('follower_accounts#index', account_username: 'name')
|
it 'routes /@:username' do
|
||||||
|
expect(get("/@#{username}")).to route_to('accounts#show', username: username)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe 'the route for accounts who are followed by an account' do
|
it 'routes /@:username.json' do
|
||||||
it 'routes to the following action with the right username' do
|
expect(get("/@#{username}.json")).to route_to('accounts#show', username: username, format: 'json')
|
||||||
expect(get('/users/name/following')).
|
end
|
||||||
to route_to('following_accounts#index', account_username: 'name')
|
|
||||||
|
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
|
||||||
end
|
|
||||||
|
|
||||||
describe 'the route for following an account' do
|
it 'routes /@:username/tagged/:tag' do
|
||||||
it 'routes to the follow create action with the right username' do
|
expect(get("/@#{username}/tagged/foo")).to route_to('accounts#show', username: username, tag: 'foo')
|
||||||
expect(post('/users/name/follow')).
|
|
||||||
to route_to('account_follow#create', account_username: 'name')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'the route for unfollowing an account' do
|
context 'with remote username' do
|
||||||
it 'routes to the unfollow create action with the right username' do
|
let(:username) { 'alice@example.com' }
|
||||||
expect(post('/users/name/unfollow')).
|
|
||||||
to route_to('account_unfollow#create', account_username: 'name')
|
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
|
end
|
||||||
end
|
end
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue