From 3282448878dd2640ea47dc1a77a4ae958ba8923e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Dec 2016 18:48:33 +0100 Subject: [PATCH 01/27] Fix #86 - resolve layout breaking on zoom-out on accounts grid --- app/assets/stylesheets/accounts.scss | 2 -- app/controllers/application_controller.rb | 4 ++-- app/views/accounts/_header.html.haml | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/accounts.scss b/app/assets/stylesheets/accounts.scss index e1d5043db9d..7f33f178dc1 100644 --- a/app/assets/stylesheets/accounts.scss +++ b/app/assets/stylesheets/accounts.scss @@ -283,8 +283,6 @@ } .name { - width: 333-20-60-15px; - float: left; padding-top: 10px; a { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e2d879d5887..0a6b50a297f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -49,13 +49,13 @@ class ApplicationController < ActionController::Base def not_found respond_to do |format| - format.any { head 404 } + format.any { head 404 } end end def gone respond_to do |format| - format.any { head 410 } + format.any { head 410 } end end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 12c9b069d4f..01e63920589 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -20,12 +20,12 @@ .counter{ class: active_nav_class(account_url(@account)) } = link_to account_url(@account) do %span.counter-label= t('accounts.posts') - %span.counter-number= @account.statuses.count + %span.counter-number= number_with_delimiter @account.statuses.count .counter{ class: active_nav_class(following_account_url(@account)) } = link_to following_account_url(@account) do %span.counter-label= t('accounts.following') - %span.counter-number= @account.following.count + %span.counter-number= number_with_delimiter @account.following.count .counter{ class: active_nav_class(followers_account_url(@account)) } = link_to followers_account_url(@account) do %span.counter-label= t('accounts.followers') - %span.counter-number= @account.followers.count + %span.counter-number= number_with_delimiter @account.followers.count From 7376af90f79b1de0c4cdd294f3f4d1481eedf0d7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Dec 2016 19:13:56 +0100 Subject: [PATCH 02/27] Don't show statuses to blocked users --- .eslintrc | 34 +++++++++++++++++-- app/models/status.rb | 13 +++++-- app/services/process_interaction_service.rb | 2 +- .../api/v1/statuses_controller_spec.rb | 1 - 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 10bf7054628..f91385cec94 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,37 @@ "sourceType": "module", "ecmaFeatures": { - "jsx": true - }, + "arrowFunctions": true, + "jsx": true, + "destructuring": true, + "modules": true, + "spread": true + } }, + + "rules": { + "no-cond-assign": 2, + "no-console": 1, + "no-irregular-whitespace": 2, + "no-unreachable": 2, + "valid-typeof": 2, + "consistent-return": 2, + "dot-notation": 2, + "eqeqeq": 2, + "no-fallthrough": 2, + "no-unused-expressions": 2, + "strict": 0, + "no-catch-shadow": 2, + "indent": [1, 2], + "brace-style": 1, + "comma-spacing": [1, {"before": false, "after": true}], + "comma-style": [1, "last"], + "no-mixed-spaces-and-tabs": 1, + "no-nested-ternary": 1, + "no-trailing-spaces": 1, + "react/wrap-multilines": 2, + "react/self-closing-comp": 2, + "react/prop-types": 2, + "react/no-multi-comp": 0 + } } diff --git a/app/models/status.rb b/app/models/status.rb index dc7fc60d72b..1720d754aae 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -31,7 +31,6 @@ class Status < ApplicationRecord scope :remote, -> { where.not(uri: nil) } scope :local, -> { where(uri: nil) } - scope :permitted_for, ->(target_account, account) { account&.id == target_account.id || account&.following?(target_account) ? where('1=1') : where.not(visibility: :private) } cache_associated :account, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account @@ -72,7 +71,7 @@ class Status < ApplicationRecord end def permitted?(other_account = nil) - private_visibility? ? (account.id == other_account&.id || other_account&.following?(account)) : true + private_visibility? ? (account.id == other_account&.id || other_account&.following?(account)) : other_account.nil? || !account.blocking?(other_account) end def ancestors(account = nil) @@ -145,6 +144,16 @@ class Status < ApplicationRecord end end + def permitted_for(target_account, account) + if account&.id == target_account.id || account&.following?(target_account) + where('1 = 1') + elsif !account.nil? && target_account.blocking?(account) + where('1 = 0') + else + where.not(visibility: :private) + end + end + private def filter_timeline(query, account) diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index 3d3cccb6a9c..450b0c5cc41 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -30,7 +30,7 @@ class ProcessInteractionService < BaseService case verb(xml) when :follow - follow!(account, target_account) unless target_account.locked? + follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) when :unfollow unfollow!(account, target_account) when :favorite diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index ab918fe508f..d9c73f9529a 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -7,7 +7,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do let(:token) { double acceptable?: true, resource_owner_id: user.id } before do - stub_request(:post, "https://pubsubhubbub.superfeedr.com/").to_return(:status => 200, :body => "", :headers => {}) allow(controller).to receive(:doorkeeper_token) { token } end From 004382e4d09f90e5ca824996c4b20e99599bf98f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Dec 2016 19:30:45 +0100 Subject: [PATCH 03/27] Adding follow requests API --- .../api/v1/follow_requests_controller.rb | 29 +++++++++++ app/helpers/api/v1/follow_requests_helper.rb | 2 + app/models/follow_request.rb | 2 + app/views/api/v1/follow_requests/index.rabl | 2 + config/routes.rb | 7 +++ .../api/v1/follow_requests_controller_spec.rb | 52 +++++++++++++++++++ spec/helpers/api/oembed_helper_spec.rb | 12 +---- 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 app/controllers/api/v1/follow_requests_controller.rb create mode 100644 app/helpers/api/v1/follow_requests_helper.rb create mode 100644 app/views/api/v1/follow_requests/index.rabl create mode 100644 spec/controllers/api/v1/follow_requests_controller_spec.rb diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb new file mode 100644 index 00000000000..a30e97e715d --- /dev/null +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -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 diff --git a/app/helpers/api/v1/follow_requests_helper.rb b/app/helpers/api/v1/follow_requests_helper.rb new file mode 100644 index 00000000000..b36faf2a364 --- /dev/null +++ b/app/helpers/api/v1/follow_requests_helper.rb @@ -0,0 +1,2 @@ +module Api::V1::FollowRequestsHelper +end diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 132316fb40b..b46065d53c6 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FollowRequest < ApplicationRecord + include Paginable + belongs_to :account belongs_to :target_account, class_name: 'Account' diff --git a/app/views/api/v1/follow_requests/index.rabl b/app/views/api/v1/follow_requests/index.rabl new file mode 100644 index 00000000000..9f3b13a53d9 --- /dev/null +++ b/app/views/api/v1/follow_requests/index.rabl @@ -0,0 +1,2 @@ +collection @accounts +extends 'api/v1/accounts/show' diff --git a/config/routes.rb b/config/routes.rb index e8c8f619d27..2e9b2a87c4c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -104,6 +104,13 @@ Rails.application.routes.draw do resources :media, only: [:create] resources :apps, only: [:create] + resources :follow_requests, only: [:index] do + member do + post :authorize + post :reject + end + end + resources :notifications, only: [:index] resources :accounts, only: [:show] do diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb new file mode 100644 index 00000000000..a90d2d2902a --- /dev/null +++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb @@ -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 diff --git a/spec/helpers/api/oembed_helper_spec.rb b/spec/helpers/api/oembed_helper_spec.rb index 4f64cb84f3e..a671e2d65ca 100644 --- a/spec/helpers/api/oembed_helper_spec.rb +++ b/spec/helpers/api/oembed_helper_spec.rb @@ -1,15 +1,5 @@ require 'rails_helper' -# Specs in this file have access to a helper object that includes -# the Api::OembedHelper. For example: -# -# describe Api::OembedHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end RSpec.describe Api::OembedHelper, type: :helper do - pending "add some examples to (or delete) #{__FILE__}" + end From 3689c119f0ad8d523ab8deb3c2c8ed0a9c84db6e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Dec 2016 21:33:51 +0100 Subject: [PATCH 04/27] Replacing follow requests in the settings area with in-UI column --- .../components/actions/accounts.jsx | 153 ++++++++++++++++++ .../components/components/status_list.jsx | 6 +- .../components/containers/mastodon.jsx | 3 + .../components/account_authorize.jsx | 61 +++++++ .../account_authorize_container.jsx | 26 +++ .../features/follow_requests/index.jsx | 66 ++++++++ .../features/getting_started/index.jsx | 9 +- .../components/reducers/accounts.jsx | 4 +- .../components/reducers/user_lists.jsx | 13 +- app/controllers/follow_requests_controller.rb | 28 ---- app/helpers/follow_requests_helper.rb | 2 - app/views/follow_requests/index.html.haml | 16 -- app/views/settings/shared/_links.html.haml | 2 - config/locales/en.yml | 4 - config/routes.rb | 7 - .../follow_requests_controller_spec.rb | 16 -- spec/helpers/follow_requests_helper_spec.rb | 5 - 17 files changed, 334 insertions(+), 87 deletions(-) create mode 100644 app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx create mode 100644 app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx create mode 100644 app/assets/javascripts/components/features/follow_requests/index.jsx delete mode 100644 app/controllers/follow_requests_controller.rb delete mode 100644 app/helpers/follow_requests_helper.rb delete mode 100644 app/views/follow_requests/index.html.haml delete mode 100644 spec/controllers/follow_requests_controller_spec.rb delete mode 100644 spec/helpers/follow_requests_helper_spec.rb diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx index 759435afe4a..8d28b051f3a 100644 --- a/app/assets/javascripts/components/actions/accounts.jsx +++ b/app/assets/javascripts/components/actions/accounts.jsx @@ -51,6 +51,22 @@ export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; +export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; +export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; +export const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL'; + +export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; +export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; +export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; + +export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; +export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; +export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; + +export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; +export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; +export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; + export function setAccountSelf(account) { return { type: ACCOUNT_SET_SELF, @@ -509,3 +525,140 @@ export function fetchRelationshipsFail(error) { error }; }; + +export function fetchFollowRequests() { + return (dispatch, getState) => { + dispatch(fetchFollowRequestsRequest()); + + api(getState).get('/api/v1/follow_requests').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)) + }).catch(error => dispatch(fetchFollowRequestsFail(error))); + }; +}; + +export function fetchFollowRequestsRequest() { + return { + type: FOLLOW_REQUESTS_FETCH_REQUEST + }; +}; + +export function fetchFollowRequestsSuccess(accounts, next) { + return { + type: FOLLOW_REQUESTS_FETCH_SUCCESS, + accounts, + next + }; +}; + +export function fetchFollowRequestsFail(error) { + return { + type: FOLLOW_REQUESTS_FETCH_FAIL, + error + }; +}; + +export function expandFollowRequests() { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'follow_requests', 'next']); + + if (url === null) { + return; + } + + dispatch(expandFollowRequestsRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)) + }).catch(error => dispatch(expandFollowRequestsFail(error))); + }; +}; + +export function expandFollowRequestsRequest() { + return { + type: FOLLOW_REQUESTS_EXPAND_REQUEST + }; +}; + +export function expandFollowRequestsSuccess(accounts, next) { + return { + type: FOLLOW_REQUESTS_EXPAND_SUCCESS, + accounts, + next + }; +}; + +export function expandFollowRequestsFail(error) { + return { + type: FOLLOW_REQUESTS_EXPAND_FAIL, + error + }; +}; + +export function authorizeFollowRequest(id) { + return (dispatch, getState) => { + dispatch(authorizeFollowRequestRequest(id)); + + api(getState) + .post(`/api/v1/follow_requests/${id}/authorize`) + .then(response => dispatch(authorizeFollowRequestSuccess(id))) + .catch(error => dispatch(authorizeFollowRequestFail(id, error))); + }; +}; + +export function authorizeFollowRequestRequest(id) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, + id + }; +}; + +export function authorizeFollowRequestSuccess(id) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + id + }; +}; + +export function authorizeFollowRequestFail(id, error) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_FAIL, + id, + error + }; +}; + + +export function rejectFollowRequest(id) { + return (dispatch, getState) => { + dispatch(rejectFollowRequestRequest(id)); + + api(getState) + .post(`/api/v1/follow_requests/${id}/reject`) + .then(response => dispatch(rejectFollowRequestSuccess(id))) + .catch(error => dispatch(rejectFollowRequestFail(id, error))); + }; +}; + +export function rejectFollowRequestRequest(id) { + return { + type: FOLLOW_REQUEST_REJECT_REQUEST, + id + }; +}; + +export function rejectFollowRequestSuccess(id) { + return { + type: FOLLOW_REQUEST_REJECT_SUCCESS, + id + }; +}; + +export function rejectFollowRequestFail(id, error) { + return { + type: FOLLOW_REQUEST_REJECT_FAIL, + id, + error + }; +}; diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx index b48d944050e..e0a73435feb 100644 --- a/app/assets/javascripts/components/components/status_list.jsx +++ b/app/assets/javascripts/components/components/status_list.jsx @@ -27,11 +27,11 @@ const StatusList = React.createClass({ this._oldScrollPosition = scrollHeight - scrollTop; - if (scrollTop === scrollHeight - clientHeight) { + if (scrollTop === scrollHeight - clientHeight && this.props.onScrollToBottom) { this.props.onScrollToBottom(); - } else if (scrollTop < 100) { + } else if (scrollTop < 100 && this.props.onScrollToTop) { this.props.onScrollToTop(); - } else { + } else if (this.props.onScroll) { this.props.onScroll(); } }, diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx index 69fe2d07ffe..67045537651 100644 --- a/app/assets/javascripts/components/containers/mastodon.jsx +++ b/app/assets/javascripts/components/containers/mastodon.jsx @@ -34,6 +34,7 @@ import Reblogs from '../features/reblogs'; import Favourites from '../features/favourites'; import HashtagTimeline from '../features/hashtag_timeline'; import Notifications from '../features/notifications'; +import FollowRequests from '../features/follow_requests'; import { IntlProvider, addLocaleData } from 'react-intl'; import en from 'react-intl/locale-data/en'; import de from 'react-intl/locale-data/de'; @@ -125,6 +126,8 @@ const Mastodon = React.createClass({ + + diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx new file mode 100644 index 00000000000..c16488aea1b --- /dev/null +++ b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx @@ -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 ( +
+
+ +
+ +
+ +
+
+ +
+
+
+
+
+ ) +}; + +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); diff --git a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx b/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx new file mode 100644 index 00000000000..da1e5eaa14b --- /dev/null +++ b/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/follow_requests/index.jsx b/app/assets/javascripts/components/features/follow_requests/index.jsx new file mode 100644 index 00000000000..4613709993b --- /dev/null +++ b/app/assets/javascripts/components/features/follow_requests/index.jsx @@ -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 ( + + + + ); + } + + return ( + + +
+ {accountIds.map(id => + + )} +
+
+
+ ); + } +}); + +export default connect(mapStateToProps)(injectIntl(FollowRequests)); diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index bff75f86f98..7c249195442 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -7,7 +7,8 @@ import { connect } from 'react-redux'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Public timeline' }, - settings: { id: 'navigation_bar.settings', defaultMessage: 'Settings' } + settings: { id: 'navigation_bar.settings', defaultMessage: 'Settings' }, + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' } }); const mapStateToProps = state => ({ @@ -32,6 +33,7 @@ const GettingStarted = ({ intl, me }) => {
+
@@ -43,4 +45,9 @@ const GettingStarted = ({ intl, me }) => { ); }; +GettingStarted.propTypes = { + intl: React.PropTypes.object.isRequired, + me: React.PropTypes.number +}; + export default connect(mapStateToProps)(injectIntl(GettingStarted)); diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx index 982e63073bc..7f2f89d0a43 100644 --- a/app/assets/javascripts/components/reducers/accounts.jsx +++ b/app/assets/javascripts/components/reducers/accounts.jsx @@ -6,7 +6,8 @@ import { FOLLOWING_FETCH_SUCCESS, FOLLOWING_EXPAND_SUCCESS, ACCOUNT_TIMELINE_FETCH_SUCCESS, - ACCOUNT_TIMELINE_EXPAND_SUCCESS + ACCOUNT_TIMELINE_EXPAND_SUCCESS, + FOLLOW_REQUESTS_FETCH_SUCCESS } from '../actions/accounts'; import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; import { @@ -78,6 +79,7 @@ export default function accounts(state = initialState, action) { case FAVOURITES_FETCH_SUCCESS: case COMPOSE_SUGGESTIONS_READY: case SEARCH_SUGGESTIONS_READY: + case FOLLOW_REQUESTS_FETCH_SUCCESS: return normalizeAccounts(state, action.accounts); case NOTIFICATIONS_REFRESH_SUCCESS: case NOTIFICATIONS_EXPAND_SUCCESS: diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx index 3608e420970..36093663fc3 100644 --- a/app/assets/javascripts/components/reducers/user_lists.jsx +++ b/app/assets/javascripts/components/reducers/user_lists.jsx @@ -2,7 +2,10 @@ import { FOLLOWERS_FETCH_SUCCESS, FOLLOWERS_EXPAND_SUCCESS, FOLLOWING_FETCH_SUCCESS, - FOLLOWING_EXPAND_SUCCESS + FOLLOWING_EXPAND_SUCCESS, + FOLLOW_REQUESTS_FETCH_SUCCESS, + FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + FOLLOW_REQUEST_REJECT_SUCCESS } from '../actions/accounts'; import { REBLOGS_FETCH_SUCCESS, @@ -14,7 +17,8 @@ const initialState = Immutable.Map({ followers: Immutable.Map(), following: Immutable.Map(), reblogged_by: Immutable.Map(), - favourited_by: Immutable.Map() + favourited_by: Immutable.Map(), + follow_requests: Immutable.Map() }); const normalizeList = (state, type, id, accounts, next) => { @@ -44,6 +48,11 @@ export default function userLists(state = initialState, action) { return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id))); case FAVOURITES_FETCH_SUCCESS: return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id))); + case FOLLOW_REQUESTS_FETCH_SUCCESS: + return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); + case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: + case FOLLOW_REQUEST_REJECT_SUCCESS: + return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id)); default: return state; } diff --git a/app/controllers/follow_requests_controller.rb b/app/controllers/follow_requests_controller.rb deleted file mode 100644 index d4368f7733a..00000000000 --- a/app/controllers/follow_requests_controller.rb +++ /dev/null @@ -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 diff --git a/app/helpers/follow_requests_helper.rb b/app/helpers/follow_requests_helper.rb deleted file mode 100644 index cfd350e5306..00000000000 --- a/app/helpers/follow_requests_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module FollowRequestsHelper -end diff --git a/app/views/follow_requests/index.html.haml b/app/views/follow_requests/index.html.haml deleted file mode 100644 index 8c83488def3..00000000000 --- a/app/views/follow_requests/index.html.haml +++ /dev/null @@ -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" diff --git a/app/views/settings/shared/_links.html.haml b/app/views/settings/shared/_links.html.haml index b6a0b1fc127..44f097950bf 100644 --- a/app/views/settings/shared/_links.html.haml +++ b/app/views/settings/shared/_links.html.haml @@ -1,8 +1,6 @@ %ul.no-list - if controller_name != 'profiles' %li= link_to t('settings.edit_profile'), settings_profile_path - - if controller_name != 'follow_requests' - %li= link_to t('follow_requests.title'), follow_requests_path - if controller_name != 'preferences' %li= link_to t('settings.preferences'), settings_preferences_path - if controller_name != 'registrations' diff --git a/config/locales/en.yml b/config/locales/en.yml index 4cf958517fd..e5916dd766e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -40,10 +40,6 @@ en: x_minutes: "%{count}m" x_months: "%{count}mo" x_seconds: "%{count}s" - follow_requests: - authorize: Authorize - reject: Reject - title: Follow requests generic: changes_saved_msg: Changes successfully saved! powered_by: powered by %{link} diff --git a/config/routes.rb b/config/routes.rb index 2e9b2a87c4c..985d6583dec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -48,13 +48,6 @@ Rails.application.routes.draw do resources :media, only: [:show] resources :tags, only: [:show] - resources :follow_requests do - member do - post :authorize - post :reject - end - end - namespace :admin do resources :pubsubhubbub, only: [:index] resources :domain_blocks, only: [:index, :create] diff --git a/spec/controllers/follow_requests_controller_spec.rb b/spec/controllers/follow_requests_controller_spec.rb deleted file mode 100644 index 72f5fd9b9e4..00000000000 --- a/spec/controllers/follow_requests_controller_spec.rb +++ /dev/null @@ -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 diff --git a/spec/helpers/follow_requests_helper_spec.rb b/spec/helpers/follow_requests_helper_spec.rb deleted file mode 100644 index e031cf40237..00000000000 --- a/spec/helpers/follow_requests_helper_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe FollowRequestsHelper, type: :helper do - -end From 2146ac91a004bad2a6c4dc1d01599a85515928f5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 26 Dec 2016 21:52:03 +0100 Subject: [PATCH 05/27] Follow requests send e-mail notifications, but are excluded from notifications API Better initial state for unlisted/nsfw toggles --- .../compose/components/compose_form.jsx | 5 +++-- .../components/account_authorize.jsx | 2 +- .../api/v1/notifications_controller.rb | 2 +- app/mailers/notification_mailer.rb | 9 +++++++++ app/models/follow_request.rb | 2 ++ app/models/notification.rb | 18 ++++++++++++------ app/models/user.rb | 2 +- app/services/follow_service.rb | 7 ++++++- app/services/notify_service.rb | 5 +++++ .../follow_request.text.erb | 5 +++++ config/locales/en.yml | 3 +++ 11 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 app/views/notification_mailer/follow_request.text.erb diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index 760b0efd10e..012e39c9112 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -20,6 +20,7 @@ const messages = defineMessages({ const ComposeForm = React.createClass({ propTypes: { + intl: React.PropTypes.object.isRequired, text: React.PropTypes.string.isRequired, suggestion_token: React.PropTypes.string, suggestions: ImmutablePropTypes.list, @@ -129,7 +130,7 @@ const ComposeForm = React.createClass({ - + {({ opacity, height }) => - + {({ opacity, height }) =>