commit
de154dbd5d
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 244 KiB |
@ -0,0 +1,61 @@ |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import Permalink from '../../../components/permalink'; |
||||
import Avatar from '../../../components/avatar'; |
||||
import DisplayName from '../../../components/display_name'; |
||||
import emojify from '../../../emoji'; |
||||
import IconButton from '../../../components/icon_button'; |
||||
import { defineMessages, injectIntl } from 'react-intl'; |
||||
|
||||
const messages = defineMessages({ |
||||
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, |
||||
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' } |
||||
}); |
||||
|
||||
const outerStyle = { |
||||
padding: '14px 10px' |
||||
}; |
||||
|
||||
const panelStyle = { |
||||
background: '#2f3441', |
||||
display: 'flex', |
||||
flexDirection: 'row', |
||||
borderTop: '1px solid #363c4b', |
||||
borderBottom: '1px solid #363c4b', |
||||
padding: '10px 0' |
||||
}; |
||||
|
||||
const btnStyle = { |
||||
flex: '1 1 auto', |
||||
textAlign: 'center' |
||||
}; |
||||
|
||||
const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => { |
||||
const content = { __html: emojify(account.get('note')) }; |
||||
|
||||
return ( |
||||
<div> |
||||
<div style={outerStyle}> |
||||
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}> |
||||
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={48} /></div> |
||||
<DisplayName account={account} /> |
||||
</Permalink> |
||||
|
||||
<div style={{ color: '#616b86', fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} /> |
||||
</div> |
||||
|
||||
<div style={panelStyle}> |
||||
<div style={btnStyle}><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div> |
||||
<div style={btnStyle}><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div> |
||||
</div> |
||||
</div> |
||||
) |
||||
}; |
||||
|
||||
AccountAuthorize.propTypes = { |
||||
account: ImmutablePropTypes.map.isRequired, |
||||
onAuthorize: React.PropTypes.func.isRequired, |
||||
onReject: React.PropTypes.func.isRequired, |
||||
intl: React.PropTypes.object.isRequired |
||||
}; |
||||
|
||||
export default injectIntl(AccountAuthorize); |
@ -0,0 +1,26 @@ |
||||
import { connect } from 'react-redux'; |
||||
import { makeGetAccount } from '../../../selectors'; |
||||
import AccountAuthorize from '../components/account_authorize'; |
||||
import { authorizeFollowRequest, rejectFollowRequest } from '../../../actions/accounts'; |
||||
|
||||
const makeMapStateToProps = () => { |
||||
const getAccount = makeGetAccount(); |
||||
|
||||
const mapStateToProps = (state, props) => ({ |
||||
account: getAccount(state, props.id) |
||||
}); |
||||
|
||||
return mapStateToProps; |
||||
}; |
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({ |
||||
onAuthorize (account) { |
||||
dispatch(authorizeFollowRequest(id)); |
||||
}, |
||||
|
||||
onReject (account) { |
||||
dispatch(rejectFollowRequest(id)); |
||||
} |
||||
}); |
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(AccountAuthorize); |
@ -0,0 +1,66 @@ |
||||
import { connect } from 'react-redux'; |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import LoadingIndicator from '../../components/loading_indicator'; |
||||
import { ScrollContainer } from 'react-router-scroll'; |
||||
import Column from '../ui/components/column'; |
||||
import AccountAuthorizeContainer from './containers/account_authorize_container'; |
||||
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; |
||||
import { defineMessages, injectIntl } from 'react-intl'; |
||||
|
||||
const messages = defineMessages({ |
||||
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' } |
||||
}); |
||||
|
||||
const mapStateToProps = state => ({ |
||||
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']) |
||||
}); |
||||
|
||||
const FollowRequests = React.createClass({ |
||||
propTypes: { |
||||
params: React.PropTypes.object.isRequired, |
||||
dispatch: React.PropTypes.func.isRequired, |
||||
accountIds: ImmutablePropTypes.list, |
||||
intl: React.PropTypes.object.isRequired |
||||
}, |
||||
|
||||
mixins: [PureRenderMixin], |
||||
|
||||
componentWillMount () { |
||||
this.props.dispatch(fetchFollowRequests()); |
||||
}, |
||||
|
||||
handleScroll (e) { |
||||
const { scrollTop, scrollHeight, clientHeight } = e.target; |
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) { |
||||
this.props.dispatch(expandFollowRequests()); |
||||
} |
||||
}, |
||||
|
||||
render () { |
||||
const { intl, accountIds } = this.props; |
||||
|
||||
if (!accountIds) { |
||||
return ( |
||||
<Column> |
||||
<LoadingIndicator /> |
||||
</Column> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<Column icon='users' heading={intl.formatMessage(messages.heading)}> |
||||
<ScrollContainer scrollKey='follow_requests'> |
||||
<div className='scrollable' onScroll={this.handleScroll}> |
||||
{accountIds.map(id => |
||||
<AccountAuthorizeContainer key={id} id={id} /> |
||||
)} |
||||
</div> |
||||
</ScrollContainer> |
||||
</Column> |
||||
); |
||||
} |
||||
}); |
||||
|
||||
export default connect(mapStateToProps)(injectIntl(FollowRequests)); |
@ -0,0 +1,150 @@ |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import Toggle from 'react-toggle'; |
||||
import { Motion, spring } from 'react-motion'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
const outerStyle = { |
||||
background: '#373b4a', |
||||
padding: '15px' |
||||
}; |
||||
|
||||
const iconStyle = { |
||||
fontSize: '16px', |
||||
padding: '15px', |
||||
position: 'absolute', |
||||
right: '0', |
||||
top: '-48px', |
||||
cursor: 'pointer' |
||||
}; |
||||
|
||||
const labelStyle = { |
||||
display: 'block', |
||||
lineHeight: '24px', |
||||
verticalAlign: 'middle' |
||||
}; |
||||
|
||||
const labelSpanStyle = { |
||||
display: 'inline-block', |
||||
verticalAlign: 'middle', |
||||
marginBottom: '14px', |
||||
marginLeft: '8px', |
||||
color: '#9baec8' |
||||
}; |
||||
|
||||
const sectionStyle = { |
||||
cursor: 'default', |
||||
display: 'block', |
||||
fontWeight: '500', |
||||
color: '#9baec8', |
||||
marginBottom: '10px' |
||||
}; |
||||
|
||||
const rowStyle = { |
||||
|
||||
}; |
||||
|
||||
const ColumnSettings = React.createClass({ |
||||
|
||||
propTypes: { |
||||
settings: ImmutablePropTypes.map.isRequired, |
||||
onChange: React.PropTypes.func.isRequired |
||||
}, |
||||
|
||||
getInitialState () { |
||||
return { |
||||
collapsed: true |
||||
}; |
||||
}, |
||||
|
||||
mixins: [PureRenderMixin], |
||||
|
||||
handleToggleCollapsed () { |
||||
this.setState({ collapsed: !this.state.collapsed }); |
||||
}, |
||||
|
||||
handleChange (key, e) { |
||||
this.props.onChange(key, e.target.checked); |
||||
}, |
||||
|
||||
render () { |
||||
const { settings } = this.props; |
||||
const { collapsed } = this.state; |
||||
|
||||
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />; |
||||
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; |
||||
|
||||
return ( |
||||
<div style={{ position: 'relative' }}> |
||||
<div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className='fa fa-sliders' /></div> |
||||
|
||||
<Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : 458) }}> |
||||
{({ opacity, height }) => |
||||
<div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}> |
||||
<div style={outerStyle}> |
||||
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span> |
||||
|
||||
<div style={rowStyle}> |
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['alerts', 'follow'])} onChange={this.handleChange.bind(this, ['alerts', 'follow'])} /> |
||||
<span style={labelSpanStyle}>{alertStr}</span> |
||||
</label> |
||||
|
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['shows', 'follow'])} onChange={this.handleChange.bind(this, ['shows', 'follow'])} /> |
||||
<span style={labelSpanStyle}>{showStr}</span> |
||||
</label> |
||||
</div> |
||||
|
||||
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> |
||||
|
||||
<div style={rowStyle}> |
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['alerts', 'favourite'])} onChange={this.handleChange.bind(this, ['alerts', 'favourite'])} /> |
||||
<span style={labelSpanStyle}>{alertStr}</span> |
||||
</label> |
||||
|
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['shows', 'favourite'])} onChange={this.handleChange.bind(this, ['shows', 'favourite'])} /> |
||||
<span style={labelSpanStyle}>{showStr}</span> |
||||
</label> |
||||
</div> |
||||
|
||||
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> |
||||
|
||||
<div style={rowStyle}> |
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['alerts', 'mention'])} onChange={this.handleChange.bind(this, ['alerts', 'mention'])} /> |
||||
<span style={labelSpanStyle}>{alertStr}</span> |
||||
</label> |
||||
|
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['shows', 'mention'])} onChange={this.handleChange.bind(this, ['shows', 'mention'])} /> |
||||
<span style={labelSpanStyle}>{showStr}</span> |
||||
</label> |
||||
</div> |
||||
|
||||
<span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> |
||||
|
||||
<div style={rowStyle}> |
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['alerts', 'reblog'])} onChange={this.handleChange.bind(this, ['alerts', 'reblog'])} /> |
||||
<span style={labelSpanStyle}>{alertStr}</span> |
||||
</label> |
||||
|
||||
<label style={labelStyle}> |
||||
<Toggle checked={settings.getIn(['shows', 'reblog'])} onChange={this.handleChange.bind(this, ['shows', 'reblog'])} /> |
||||
<span style={labelSpanStyle}>{showStr}</span> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
} |
||||
</Motion> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default ColumnSettings; |
@ -0,0 +1,17 @@ |
||||
import { connect } from 'react-redux'; |
||||
import ColumnSettings from '../components/column_settings'; |
||||
import { changeNotificationsSetting } from '../../../actions/notifications'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
settings: state.getIn(['notifications', 'settings']) |
||||
}); |
||||
|
||||
const mapDispatchToProps = dispatch => ({ |
||||
|
||||
onChange (key, checked) { |
||||
dispatch(changeNotificationsSetting(key, checked)); |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); |
@ -0,0 +1,46 @@ |
||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
const outerStyle = { |
||||
position: 'absolute', |
||||
right: '0', |
||||
top: '-48px', |
||||
padding: '15px', |
||||
fontSize: '16px', |
||||
background: '#2f3441', |
||||
flex: '0 0 auto', |
||||
cursor: 'pointer', |
||||
color: '#2b90d9' |
||||
}; |
||||
|
||||
const iconStyle = { |
||||
display: 'inline-block', |
||||
marginRight: '5px' |
||||
}; |
||||
|
||||
const ColumnBackButton = React.createClass({ |
||||
|
||||
contextTypes: { |
||||
router: React.PropTypes.object |
||||
}, |
||||
|
||||
mixins: [PureRenderMixin], |
||||
|
||||
handleClick () { |
||||
this.context.router.push('/'); |
||||
}, |
||||
|
||||
render () { |
||||
return ( |
||||
<div style={{ position: 'relative' }}> |
||||
<div style={outerStyle} onClick={this.handleClick} className='column-back-button'> |
||||
<i className='fa fa-fw fa-chevron-left' style={iconStyle} /> |
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' /> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
}); |
||||
|
||||
export default ColumnBackButton; |
@ -0,0 +1,21 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::BlocksController < ApiController |
||||
before_action -> { doorkeeper_authorize! :follow } |
||||
before_action :require_user! |
||||
|
||||
respond_to :json |
||||
|
||||
def index |
||||
results = Block.where(account: current_account).paginate_by_max_id(DEFAULT_ACCOUNTS_LIMIT, params[:max_id], params[:since_id]) |
||||
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h |
||||
@accounts = results.map { |f| accounts[f.target_account_id] } |
||||
|
||||
set_account_counters_maps(@accounts) |
||||
|
||||
next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT |
||||
prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty? |
||||
|
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
end |
@ -0,0 +1,21 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::FavouritesController < ApiController |
||||
before_action -> { doorkeeper_authorize! :read } |
||||
before_action :require_user! |
||||
|
||||
respond_to :json |
||||
|
||||
def index |
||||
results = Favourite.where(account: current_account).paginate_by_max_id(DEFAULT_STATUSES_LIMIT, params[:max_id], params[:since_id]) |
||||
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status) |
||||
|
||||
set_maps(@statuses) |
||||
set_counters_maps(@statuses) |
||||
|
||||
next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT |
||||
prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty? |
||||
|
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
end |
@ -0,0 +1,29 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::FollowRequestsController < ApiController |
||||
before_action -> { doorkeeper_authorize! :follow } |
||||
before_action :require_user! |
||||
|
||||
def index |
||||
results = FollowRequest.where(target_account: current_account).paginate_by_max_id(DEFAULT_ACCOUNTS_LIMIT, params[:max_id], params[:since_id]) |
||||
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h |
||||
@accounts = results.map { |f| accounts[f.account_id] } |
||||
|
||||
set_account_counters_maps(@accounts) |
||||
|
||||
next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT |
||||
prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty? |
||||
|
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
|
||||
def authorize |
||||
FollowRequest.find_by!(account_id: params[:id], target_account: current_account).authorize! |
||||
render_empty |
||||
end |
||||
|
||||
def reject |
||||
FollowRequest.find_by!(account_id: params[:id], target_account: current_account).reject! |
||||
render_empty |
||||
end |
||||
end |
@ -0,0 +1,45 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class AuthorizeFollowController < ApplicationController |
||||
layout 'public' |
||||
|
||||
before_action :authenticate_user! |
||||
|
||||
def new |
||||
uri = Addressable::URI.parse(acct_param) |
||||
|
||||
if uri.path && %w(http https).include?(uri.scheme) |
||||
set_account_from_url |
||||
else |
||||
set_account_from_acct |
||||
end |
||||
|
||||
render :error if @account.nil? |
||||
end |
||||
|
||||
def create |
||||
@account = FollowService.new.call(current_account, acct_param).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 |
||||
|
||||
private |
||||
|
||||
def set_account_from_url |
||||
@account = FetchRemoteAccountService.new.call(acct_param) |
||||
end |
||||
|
||||
def set_account_from_acct |
||||
@account = FollowRemoteAccountService.new.call(acct_param) |
||||
end |
||||
|
||||
def acct_param |
||||
params[:acct].gsub(/\Aacct:/, '') |
||||
end |
||||
end |
@ -1,28 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class FollowRequestsController < ApplicationController |
||||
layout 'auth' |
||||
|
||||
before_action :authenticate_user! |
||||
before_action :set_follow_request, except: :index |
||||
|
||||
def index |
||||
@follow_requests = FollowRequest.where(target_account: current_account) |
||||
end |
||||
|
||||
def authorize |
||||
@follow_request.authorize! |
||||
redirect_to follow_requests_path |
||||
end |
||||
|
||||
def reject |
||||
@follow_request.reject! |
||||
redirect_to follow_requests_path |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_follow_request |
||||
@follow_request = FollowRequest.find(params[:id]) |
||||
end |
||||
end |
@ -0,0 +1,47 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class RemoteFollowController < ApplicationController |
||||
layout 'public' |
||||
|
||||
before_action :set_account |
||||
before_action :check_account_suspension |
||||
|
||||
def new |
||||
@remote_follow = RemoteFollow.new |
||||
end |
||||
|
||||
def create |
||||
@remote_follow = RemoteFollow.new(resource_params) |
||||
|
||||
if @remote_follow.valid? |
||||
resource = Goldfinger.finger("acct:#{@remote_follow.acct}") |
||||
redirect_url_link = resource&.link('http://ostatus.org/schema/1.0/subscribe') |
||||
|
||||
if redirect_url_link.nil? || redirect_url_link.template.nil? |
||||
@remote_follow.errors.add(:acct, I18n.t('remote_follow.missing_resource')) |
||||
render(:new) && return |
||||
end |
||||
|
||||
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s |
||||
else |
||||
render :new |
||||
end |
||||
rescue Goldfinger::Error |
||||
@remote_follow.errors.add(:acct, I18n.t('remote_follow.missing_resource')) |
||||
render :new |
||||
end |
||||
|
||||
private |
||||
|
||||
def resource_params |
||||
params.require(:remote_follow).permit(:acct) |
||||
end |
||||
|
||||
def set_account |
||||
@account = Account.find_local!(params[:account_username]) |
||||
end |
||||
|
||||
def check_account_suspension |
||||
head 410 if @account.suspended? |
||||
end |
||||
end |
@ -1,2 +0,0 @@ |
||||
module Api::OembedHelper |
||||
end |
@ -0,0 +1,4 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module AuthorizeFollowHelper |
||||
end |
@ -1,2 +0,0 @@ |
||||
module FollowRequestsHelper |
||||
end |
@ -0,0 +1,13 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class RemoteFollow |
||||
include ActiveModel::Validations |
||||
|
||||
attr_accessor :acct |
||||
|
||||
validates :acct, presence: true |
||||
|
||||
def initialize(attrs = {}) |
||||
@acct = attrs[:acct] |
||||
end |
||||
end |
@ -0,0 +1,2 @@ |
||||
collection @accounts |
||||
extends 'api/v1/accounts/show' |
@ -0,0 +1,2 @@ |
||||
collection @statuses |
||||
extends 'api/v1/statuses/show' |
@ -0,0 +1,2 @@ |
||||
collection @accounts |
||||
extends 'api/v1/accounts/show' |
@ -0,0 +1,11 @@ |
||||
.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}" |
||||
|
||||
- unless account.note.blank? |
||||
.account__header__content= Formatter.instance.simplified_format(account) |
@ -0,0 +1,3 @@ |
||||
.form-container |
||||
.flash-message#error_explanation |
||||
= t('authorize_follow.error') |
@ -0,0 +1,12 @@ |
||||
- 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) |
||||
|
||||
= render partial: 'card', locals: { account: @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 |
@ -1,16 +0,0 @@ |
||||
- content_for :page_title do |
||||
= t('follow_requests.title') |
||||
|
||||
- if @follow_requests.empty? |
||||
%p.nothing-here= t('accounts.nothing_here') |
||||
- else |
||||
%table.table |
||||
%tbody |
||||
- @follow_requests.each do |follow_request| |
||||
%tr |
||||
%td= link_to follow_request.account.acct, web_path("accounts/#{follow_request.account.id}") |
||||
%td{ style: 'text-align: right' } |
||||
= table_link_to 'check-circle', t('follow_requests.authorize'), authorize_follow_request_path(follow_request), method: :post |
||||
= table_link_to 'times-circle', t('follow_requests.reject'), reject_follow_request_path(follow_request), method: :post |
||||
|
||||
.form-footer= render "settings/shared/links" |
@ -0,0 +1,5 @@ |
||||
<%= display_name(@me) %>, |
||||
|
||||
<%= t('notification_mailer.follow_request.body', name: @account.acct) %> |
||||
|
||||
<%= web_url("follow_requests") %> |
@ -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] |
||||
|
@ -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| "<strong>#{s}</strong>"}.to_sentence.html_safe |
||||
%p |
||||
= t('doorkeeper.authorizations.new.able_to') |
||||
= @pre_auth.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.map { |s| "<strong>#{s}</strong>"}.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' |
||||
|
@ -1,2 +1,3 @@ |
||||
.flash-message |
||||
%code= params[:code] |
||||
.form-container |
||||
.flash-message |
||||
%code= params[:code] |
||||
|
@ -0,0 +1,13 @@ |
||||
.form-container |
||||
.follow-prompt |
||||
%h2= t('remote_follow.prompt') |
||||
|
||||
= render partial: 'authorize_follow/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') |
||||
|
||||
.actions |
||||
= f.button :button, t('remote_follow.proceed'), type: :submit |
@ -0,0 +1,19 @@ |
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe Api::V1::BlocksController, type: :controller do |
||||
render_views |
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } |
||||
let(:token) { double acceptable?: true, resource_owner_id: user.id } |
||||
|
||||
before do |
||||
allow(controller).to receive(:doorkeeper_token) { token } |
||||
end |
||||
|
||||
describe 'GET #index' do |
||||
it 'returns http success' do |
||||
get :index |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,19 @@ |
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe Api::V1::FavouritesController, type: :controller do |
||||
render_views |
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } |
||||
let(:token) { double acceptable?: true, resource_owner_id: user.id } |
||||
|
||||
before do |
||||
allow(controller).to receive(:doorkeeper_token) { token } |
||||
end |
||||
|
||||
describe 'GET #index' do |
||||
it 'returns http success' do |
||||
get :index |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,52 @@ |
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe Api::V1::FollowRequestsController, type: :controller do |
||||
render_views |
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice', locked: true)) } |
||||
let(:token) { double acceptable?: true, resource_owner_id: user.id } |
||||
let(:follower) { Fabricate(:account, username: 'bob') } |
||||
|
||||
before do |
||||
FollowService.new.call(follower, user.account.acct) |
||||
allow(controller).to receive(:doorkeeper_token) { token } |
||||
end |
||||
|
||||
describe 'GET #index' do |
||||
before do |
||||
get :index |
||||
end |
||||
|
||||
it 'returns http success' do |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
end |
||||
|
||||
describe 'POST #authorize' do |
||||
before do |
||||
post :authorize, params: { id: follower.id } |
||||
end |
||||
|
||||
it 'returns http success' do |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
|
||||
it 'allows follower to follow' do |
||||
expect(follower.following?(user.account)).to be true |
||||
end |
||||
end |
||||
|
||||
describe 'POST #reject' do |
||||
before do |
||||
post :reject, params: { id: follower.id } |
||||
end |
||||
|
||||
it 'returns http success' do |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
|
||||
it 'removes follow request' do |
||||
expect(FollowRequest.where(target_account: user.account, account: follower).count).to eq 0 |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,6 @@ |
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe AuthorizeFollowController, type: :controller do |
||||
describe 'GET #new' |
||||
describe 'POST #create' |
||||
end |
@ -1,16 +0,0 @@ |
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe FollowRequestsController, type: :controller do |
||||
render_views |
||||
|
||||
before do |
||||
sign_in Fabricate(:user), scope: :user |
||||
end |
||||
|
||||
describe 'GET #index' do |
||||
it 'returns http success' do |
||||
get :index |
||||
expect(response).to have_http_status(:success) |
||||
end |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue