diff --git a/app/assets/stylesheets/accounts.scss b/app/assets/stylesheets/accounts.scss
index 7f33f178dc1..5d0963307a8 100644
--- a/app/assets/stylesheets/accounts.scss
+++ b/app/assets/stylesheets/accounts.scss
@@ -324,3 +324,61 @@
padding-bottom: 25px;
cursor: default;
}
+
+.account-card {
+ padding: 14px 10px;
+ background: #fff;
+ border-radius: 4px;
+ text-align: left;
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+
+ .detailed-status__display-name {
+ display: block;
+ overflow: hidden;
+ margin-bottom: 15px;
+
+ & > div {
+ float: left;
+ margin-right: 10px;
+ width: 48px;
+ height: 48px;
+ }
+
+ .avatar {
+ display: block;
+ border-radius: 4px;
+ }
+
+ .display-name {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ cursor: default;
+
+ strong {
+ font-weight: 500;
+ color: #282c37;
+ }
+
+ span {
+ font-size: 14px;
+ color: #9baec8;
+ }
+ }
+
+ &:hover {
+ .display-name {
+ strong {
+ text-decoration: none;
+ }
+ }
+ }
+ }
+
+ .account__header__content {
+ font-size: 14px;
+ color: #282c37;
+ }
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d05ca37951a..e4c550b8185 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -214,11 +214,13 @@ body {
.footer {
text-align: center;
margin-top: 30px;
+ font-size: 12px;
+ color: darken(#d9e1e8, 25%);
.domain {
- font-size: 12px;
- font-weight: 400;
- font-family: 'Roboto Mono', monospace;
+ //font-size: 12px;
+ font-weight: 500;
+ //font-family: 'Roboto Mono', monospace;
a {
color: inherit;
@@ -227,13 +229,12 @@ body {
}
.powered-by {
- font-size: 12px;
font-weight: 400;
- color: darken(#d9e1e8, 25%);
a {
color: inherit;
text-decoration: underline;
+ font-weight: 500;
&:hover {
text-decoration: none;
diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss
index cf9b4fba6c5..e6d2e85a2f2 100644
--- a/app/assets/stylesheets/forms.scss
+++ b/app/assets/stylesheets/forms.scss
@@ -185,7 +185,7 @@ code {
}
}
-.oauth-prompt {
+.oauth-prompt, .follow-prompt {
margin-bottom: 30px;
text-align: center;
color: #9baec8;
diff --git a/app/controllers/authorize_follow_controller.rb b/app/controllers/authorize_follow_controller.rb
new file mode 100644
index 00000000000..a276250a47e
--- /dev/null
+++ b/app/controllers/authorize_follow_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class AuthorizeFollowController < ApplicationController
+ layout 'public'
+
+ before_action :authenticate_user!
+
+ def new
+ @account = FollowRemoteAccountService.new.call(params[:acct])
+ render :error if @account.nil?
+ end
+
+ def create
+ @account = FollowService.new.call(current_account, params[:acct]).try(:target_account)
+
+ if @account.nil?
+ render :error
+ else
+ redirect_to web_url("accounts/#{@account.id}")
+ end
+ rescue ActiveRecord::RecordNotFound, Mastodon::NotPermitted
+ render :error
+ end
+end
diff --git a/app/helpers/authorize_follow_helper.rb b/app/helpers/authorize_follow_helper.rb
new file mode 100644
index 00000000000..43659ccfa49
--- /dev/null
+++ b/app/helpers/authorize_follow_helper.rb
@@ -0,0 +1,2 @@
+module AuthorizeFollowHelper
+end
diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml
index dfd7a9f5e47..dfdb231611d 100644
--- a/app/views/accounts/_grid_card.html.haml
+++ b/app/views/accounts/_grid_card.html.haml
@@ -1,6 +1,6 @@
.account-grid-card
.account-grid-card__header
- .avatar= image_tag account.avatar.url( :original)
+ .avatar= image_tag account.avatar.url(:original)
.name
= link_to TagManager.instance.url_for(account) do
%span.display_name= display_name(account)
diff --git a/app/views/authorize_follow/error.html.haml b/app/views/authorize_follow/error.html.haml
new file mode 100644
index 00000000000..88d33b68d80
--- /dev/null
+++ b/app/views/authorize_follow/error.html.haml
@@ -0,0 +1,3 @@
+.form-container
+ .flash-message#error_explanation
+ = t('authorize_follow.error')
diff --git a/app/views/authorize_follow/new.html.haml b/app/views/authorize_follow/new.html.haml
new file mode 100644
index 00000000000..7368b834a29
--- /dev/null
+++ b/app/views/authorize_follow/new.html.haml
@@ -0,0 +1,21 @@
+- content_for :page_title do
+ = t('authorize_follow.title', acct: @account.acct)
+
+.form-container
+ .follow-prompt
+ %h2= t('authorize_follow.prompt_html', self: current_account.username)
+
+ .account-card
+ .detailed-status__display-name
+ %div
+ = image_tag @account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
+
+ %span.display-name
+ %strong= display_name(@account)
+ %span= "@#{@account.acct}"
+
+ .account__header__content= Formatter.instance.simplified_format(@account)
+
+ = form_tag authorize_follow_path, method: :post, class: 'simple_form' do
+ = hidden_field_tag :acct, @account.acct
+ = button_tag t('authorize_follow.follow'), type: :submit
diff --git a/app/views/oauth/authorizations/error.html.haml b/app/views/oauth/authorizations/error.html.haml
index ee72d974074..408ca2b86e0 100644
--- a/app/views/oauth/authorizations/error.html.haml
+++ b/app/views/oauth/authorizations/error.html.haml
@@ -1,2 +1,3 @@
-.flash-message#error_explanation
- = @pre_auth.error_response.body[:error_description]
+.form-container
+ .flash-message#error_explanation
+ = @pre_auth.error_response.body[:error_description]
diff --git a/app/views/oauth/authorizations/new.html.haml b/app/views/oauth/authorizations/new.html.haml
index f058e2cce65..1f951c272dc 100644
--- a/app/views/oauth/authorizations/new.html.haml
+++ b/app/views/oauth/authorizations/new.html.haml
@@ -1,25 +1,26 @@
- content_for :page_title do
= t('doorkeeper.authorizations.new.title')
-.oauth-prompt
- %h2= t('doorkeeper.authorizations.new.prompt', client_name: @pre_auth.client.name)
+.form-container
+ .oauth-prompt
+ %h2= t('doorkeeper.authorizations.new.prompt', client_name: @pre_auth.client.name)
- %p
- = t('doorkeeper.authorizations.new.able_to')
- = @pre_auth.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.map { |s| "#{s}"}.to_sentence.html_safe
+ %p
+ = t('doorkeeper.authorizations.new.able_to')
+ = @pre_auth.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.map { |s| "#{s}"}.to_sentence.html_safe
-= form_tag oauth_authorization_path, method: :post, class: 'simple_form' do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = button_tag t('doorkeeper.authorizations.buttons.authorize'), type: :submit
+ = form_tag oauth_authorization_path, method: :post, class: 'simple_form' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = button_tag t('doorkeeper.authorizations.buttons.authorize'), type: :submit
-= form_tag oauth_authorization_path, method: :delete, class: 'simple_form' do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = button_tag t('doorkeeper.authorizations.buttons.deny'), type: :submit, class: 'negative'
+ = form_tag oauth_authorization_path, method: :delete, class: 'simple_form' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = button_tag t('doorkeeper.authorizations.buttons.deny'), type: :submit, class: 'negative'
diff --git a/app/views/oauth/authorizations/show.html.haml b/app/views/oauth/authorizations/show.html.haml
index 897a15cee53..b56667f352e 100644
--- a/app/views/oauth/authorizations/show.html.haml
+++ b/app/views/oauth/authorizations/show.html.haml
@@ -1,2 +1,3 @@
-.flash-message
- %code= params[:code]
+.form-container
+ .flash-message
+ %code= params[:code]
diff --git a/app/views/xrd/webfinger.json.rabl b/app/views/xrd/webfinger.json.rabl
index 0de17ac195e..e637ed9d329 100644
--- a/app/views/xrd/webfinger.json.rabl
+++ b/app/views/xrd/webfinger.json.rabl
@@ -11,6 +11,7 @@ node(:links) do
{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: TagManager.instance.url_for(@account) },
{ rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(@account, format: 'atom') },
{ rel: 'salmon', href: api_salmon_url(@account.id) },
- { rel: 'magic-public-key', href: "data:application/magic-public-key,#{@magic_key}" }
+ { rel: 'magic-public-key', href: "data:application/magic-public-key,#{@magic_key}" },
+ { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}" },
]
end
diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/xrd/webfinger.xml.ruby
index ee5b5fc9d39..80ac71d2708 100644
--- a/app/views/xrd/webfinger.xml.ruby
+++ b/app/views/xrd/webfinger.xml.ruby
@@ -6,5 +6,6 @@ Nokogiri::XML::Builder.new do |xml|
xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(@account, format: 'atom'))
xml.Link(rel: 'salmon', href: api_salmon_url(@account.id))
xml.Link(rel: 'magic-public-key', href: "data:application/magic-public-key,#{@magic_key}")
+ xml.Link(rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}")
end
end.to_xml
diff --git a/config/application.rb b/config/application.rb
index 091f9c53506..79ace8521cb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -45,7 +45,7 @@ module Mastodon
config.browserify_rails.commandline_options = '--transform [ babelify --presets [ es2015 react ] ] --extension=".jsx"'
config.to_prepare do
- Doorkeeper::AuthorizationsController.layout 'auth'
+ Doorkeeper::AuthorizationsController.layout 'public'
end
config.action_dispatch.default_headers = {
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 76007886208..f57c7202627 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -26,6 +26,11 @@ en:
resend_confirmation: Resend confirmation instructions
reset_password: Reset password
set_new_password: Set new password
+ authorize_follow:
+ error: Unfortunately, there was an error looking up the remote account
+ follow: Follow
+ prompt_html: 'You (%{self}) have requested to follow:'
+ title: Follow %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}h"
diff --git a/config/routes.rb b/config/routes.rb
index 985d6583dec..1468d426b53 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -48,6 +48,10 @@ Rails.application.routes.draw do
resources :media, only: [:show]
resources :tags, only: [:show]
+ # Remote follow
+ get :authorize_follow, to: 'authorize_follow#new'
+ post :authorize_follow, to: 'authorize_follow#create'
+
namespace :admin do
resources :pubsubhubbub, only: [:index]
resources :domain_blocks, only: [:index, :create]
diff --git a/spec/controllers/authorize_follow_controller_spec.rb b/spec/controllers/authorize_follow_controller_spec.rb
new file mode 100644
index 00000000000..954efd53e7a
--- /dev/null
+++ b/spec/controllers/authorize_follow_controller_spec.rb
@@ -0,0 +1,6 @@
+require 'rails_helper'
+
+RSpec.describe AuthorizeFollowController, type: :controller do
+ describe 'GET #new'
+ describe 'POST #create'
+end
diff --git a/spec/helpers/authorize_follow_helper_spec.rb b/spec/helpers/authorize_follow_helper_spec.rb
new file mode 100644
index 00000000000..ba5b0a70b96
--- /dev/null
+++ b/spec/helpers/authorize_follow_helper_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AuthorizeFollowHelper, type: :helper do
+
+end