Add remote interaction dialog for toots (#8202)

* Add remote interaction dialog for toots

* Change AuthorizeFollow into AuthorizeInteraction, support statuses

* Update brakeman.ignore

* Adjust how interaction buttons are display on public pages

* Fix tests
This commit is contained in:
Eugen Rochko
2018-08-18 03:03:12 +02:00
committed by GitHub
parent bf1bde5d6a
commit 78fa926ed5
27 changed files with 347 additions and 195 deletions

View File

@ -1,66 +0,0 @@
# frozen_string_literal: true
class AuthorizeFollowsController < ApplicationController
layout 'modal'
before_action :authenticate_user!
before_action :set_body_classes
def show
@account = located_account || render(:error)
end
def create
@account = follow_attempt.try(:target_account)
if @account.nil?
render :error
else
render :success
end
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
render :error
end
private
def follow_attempt
FollowService.new.call(current_account, acct_without_prefix)
end
def located_account
if acct_param_is_url?
account_from_remote_fetch
else
account_from_remote_follow
end
end
def account_from_remote_fetch
FetchRemoteAccountService.new.call(acct_without_prefix)
end
def account_from_remote_follow
ResolveAccountService.new.call(acct_without_prefix)
end
def acct_param_is_url?
parsed_uri.path && %w(http https).include?(parsed_uri.scheme)
end
def parsed_uri
Addressable::URI.parse(acct_without_prefix).normalize
end
def acct_without_prefix
acct_params.gsub(/\Aacct:/, '')
end
def acct_params
params.fetch(:acct, '')
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
class AuthorizeInteractionsController < ApplicationController
include Authorization
layout 'modal'
before_action :authenticate_user!
before_action :set_body_classes
before_action :set_resource
def show
if @resource.is_a?(Account)
render :show
elsif @resource.is_a?(Status)
redirect_to web_url("statuses/#{@resource.id}")
else
render :error
end
end
def create
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource)
render :success
else
render :error
end
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
render :error
end
private
def set_resource
@resource = located_resource || render(:error)
authorize(@resource, :show?) if @resource.is_a?(Status)
end
def located_resource
if uri_param_is_url?
ResolveURLService.new.call(uri_param)
else
account_from_remote_follow
end
end
def account_from_remote_follow
ResolveAccountService.new.call(uri_param)
end
def uri_param_is_url?
parsed_uri.path && %w(http https).include?(parsed_uri.scheme)
end
def parsed_uri
Addressable::URI.parse(uri_param).normalize
end
def uri_param
params[:uri] || params.fetch(:acct, '').gsub(/\Aacct:/, '')
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@ -8,7 +8,7 @@ class IntentsController < ApplicationController
if uri.scheme == 'web+mastodon'
case uri.host
when 'follow'
return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, ''))
return redirect_to authorize_interaction_path(uri: uri.query_values['uri'].gsub(/\Aacct:/, ''))
when 'share'
return redirect_to share_path(text: uri.query_values['text'])
end

View File

@ -42,5 +42,6 @@ class RemoteFollowController < ApplicationController
def set_body_classes
@body_classes = 'modal-layout'
@hide_header = true
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
class RemoteInteractionController < ApplicationController
include Authorization
layout 'modal'
before_action :set_status
before_action :set_body_classes
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] }
end
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404
raise ActiveRecord::RecordNotFound
end
def set_body_classes
@body_classes = 'modal-layout'
@hide_header = true
end
end

View File

@ -38,4 +38,14 @@ module HomeHelper
end
end
end
def obscured_counter(count)
if count <= 0
0
elsif count == 1
1
else
'1+'
end
end
end

View File

@ -67,13 +67,6 @@ function main() {
}, datetime, now, datetime.getFullYear());
});
[].forEach.call(document.querySelectorAll('.modal-button'), (content) => {
content.addEventListener('click', (e) => {
e.preventDefault();
window.open(e.target.href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
});
});
const reactComponents = document.querySelectorAll('[data-component]');
if (reactComponents.length > 0) {
import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container')
@ -119,6 +112,20 @@ function main() {
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 nameCounter = document.querySelector('.name-counter');
const name = document.querySelector('.card .display-name strong');

View File

@ -628,6 +628,7 @@
overflow: hidden;
white-space: pre-wrap;
padding-top: 2px;
color: $primary-text-color;
&:focus {
outline: 0;

View File

@ -3,6 +3,7 @@
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
text-align: left;
@media screen and (max-width: $no-gap-breakpoint) {
margin-bottom: 0;
@ -63,6 +64,10 @@
}
}
}
&--highlighted .entry {
background: lighten($ui-base-color, 8%);
}
}
.button.logo-button {

View File

@ -22,6 +22,10 @@ class RemoteFollow
addressable_template.expand(uri: account.local_username_and_domain).to_s
end
def interact_address_for(status)
addressable_template.expand(uri: ActivityPub::TagManager.instance.uri_for(status)).to_s
end
private
def populate_template

View File

@ -20,7 +20,7 @@ class WebfingerSerializer < ActiveModel::Serializer
{ rel: 'self', type: 'application/activity+json', href: account_url(object) },
{ rel: 'salmon', href: api_salmon_url(object.id) },
{ rel: 'magic-public-key', href: "data:application/magic-public-key,#{object.magic_key}" },
{ rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}" },
{ rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
]
end
end

View File

@ -1,17 +0,0 @@
- content_for :page_title do
= t('authorize_follow.title', acct: @account.acct)
.form-container
.follow-prompt
= render 'application/card', account: @account
- if current_account.following?(@account)
.flash-message
%strong
= t('authorize_follow.already_following')
= render 'post_follow_actions'
- else
= 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

View File

@ -1,4 +1,4 @@
.post-follow-actions
%div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block'
%div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block'
%div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
%div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@resource), class: 'button button--block'
%div= t('authorize_follow.post_follow.close')

View File

@ -0,0 +1,18 @@
- content_for :page_title do
= t('authorize_follow.title', acct: @resource.acct)
.form-container
.follow-prompt
= render 'application/card', account: @resource
- if current_account.following?(@resource)
.flash-message
%strong
= t('authorize_follow.already_following')
= render 'post_follow_actions'
- else
= form_tag authorize_interaction_path, method: :post, class: 'simple_form' do
= hidden_field_tag :action, :follow
= hidden_field_tag :acct, @resource.acct
= button_tag t('authorize_follow.follow'), type: :submit

View File

@ -1,13 +1,13 @@
- content_for :page_title do
= t('authorize_follow.title', acct: @account.acct)
= t('authorize_follow.title', acct: @resource.acct)
.form-container
.follow-prompt
- if @account.locked?
- if @resource.locked?
%h2= t('authorize_follow.follow_request')
- else
%h2= t('authorize_follow.following')
= render 'application/card', account: @account
= render 'application/card', account: @resource
= render 'post_follow_actions'

View File

@ -2,7 +2,7 @@
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do
- if user_signed_in?
- if user_signed_in? && !@hide_header
.account-header
.avatar= image_tag current_account.avatar.url(:original)
.name

View File

@ -0,0 +1,17 @@
.form-container
.follow-prompt
%h2= t('remote_interaction.prompt')
.public-layout
.activity-stream.activity-stream--highlighted
= render 'stream_entries/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
= f.input :acct, placeholder: t('remote_follow.acct'), input_html: { autocapitalize: 'none', autocorrect: 'off' }
.actions
= f.button :button, t('remote_interaction.proceed'), type: :submit
%p.hint.subtle-hint= t('remote_follow.no_account_html', sign_up_path: open_registrations? ? new_user_registration_path : 'https://joinmastodon.org/#getting-started')

View File

@ -39,6 +39,11 @@
- else
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener'
·
= link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
= fa_icon('reply')
%span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true
= " "
·
- if status.direct_visibility?
%span.detailed-status__link<
= fa_icon('envelope')
@ -46,13 +51,15 @@
%span.detailed-status__link<
= fa_icon('lock')
- else
%span.detailed-status__link<
= link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
= fa_icon('retweet')
%span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
%span.detailed-status__reblogs>= number_to_human status.reblogs_count, strip_insignificant_zeros: true
= " "
·
%span.detailed-status__link<
= link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do
= fa_icon('star')
%span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true
%span.detailed-status__favorites>= number_to_human status.favourites_count, strip_insignificant_zeros: true
= " "
- if user_signed_in?
·

View File

@ -29,14 +29,16 @@
= react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
.status__action-bar
.status__action-bar-button.static-icon-button<
.status__action-bar__counter
= link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
= fa_icon 'reply fw'
.status__action-bar__counter__label= obscured_counter status.replies_count
= link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
- if status.public_visibility? || status.unlisted_visibility?
= fa_icon 'retweet fw'
%span.detailed-status__reblogs= number_to_human status.reblogs_count, strip_insignificant_zeros: true
- elsif status.private_visibility?
= fa_icon 'lock fw'
- else
= fa_icon 'envelope fw'
.status__action-bar-button.static-icon-button<
= link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
= fa_icon 'star fw'
%span.detailed-status__favorites= number_to_human status.favourites_count, strip_insignificant_zeros: true

View File

@ -19,7 +19,7 @@
.grid
.column-0
.activity-stream.activity-stream-headless.h-entry
.activity-stream.h-entry
= render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
.column-1
= render 'application/sidebar'

View File

@ -37,7 +37,7 @@ doc << Ox::Element.new('XRD').tap do |xrd|
xrd << Ox::Element.new('Link').tap do |link|
link['rel'] = 'http://ostatus.org/schema/1.0/subscribe'
link['template'] = "#{authorize_follow_url}?acct={uri}"
link['template'] = "#{authorize_interaction_url}?acct={uri}"
end
end