commit: 3f9708edc4cd799afb68000adc35036dd8b8faa5
parent: c6d893a71dede65dd88a0dfac81178c8e81e27d2
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Fri, 23 Sep 2016 20:23:26 +0200
Change output of api/accounts/:id/follow and unfollow to return relationship
Track relationship in redux state. Display follow/unfollow and following-back
information on account view (unstyled)
Diffstat:
9 files changed, 122 insertions(+), 33 deletions(-)
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
@@ -124,10 +124,10 @@ export function followAccountRequest(id) {
};
};
-export function followAccountSuccess(account) {
+export function followAccountSuccess(relationship) {
return {
type: ACCOUNT_FOLLOW_SUCCESS,
- account: account
+ relationship: relationship
};
};
@@ -145,10 +145,10 @@ export function unfollowAccountRequest(id) {
};
};
-export function unfollowAccountSuccess(account) {
+export function unfollowAccountSuccess(relationship) {
return {
type: ACCOUNT_UNFOLLOW_SUCCESS,
- account: account
+ relationship: relationship
};
};
diff --git a/app/assets/javascripts/components/components/status_content.jsx b/app/assets/javascripts/components/components/status_content.jsx
@@ -14,15 +14,24 @@ const StatusContent = React.createClass({
mixins: [PureRenderMixin],
componentDidMount () {
- const node = ReactDOM.findDOMNode(this);
-
- this.props.status.get('mentions').forEach(mention => {
- const links = node.querySelector(`a[href="${mention.get('url')}"]`);
- links.addEventListener('click', this.onLinkClick.bind(this, mention));
- });
+ const node = ReactDOM.findDOMNode(this);
+ const links = node.querySelectorAll('a');
+
+ for (var i = 0; i < links.length; ++i) {
+ let link = links[i];
+ let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
+
+ if (mention) {
+ link.addEventListener('click', this.onMentionClick.bind(this, mention));
+ } else {
+ link.setAttribute('target', '_blank');
+ link.setAttribute('rel', 'noopener');
+ link.addEventListener('click', this.onNormalClick);
+ }
+ }
},
- onLinkClick (mention, e) {
+ onMentionClick (mention, e) {
if (e.button === 0) {
e.preventDefault();
this.context.router.push(`/accounts/${mention.get('id')}`);
@@ -31,6 +40,10 @@ const StatusContent = React.createClass({
e.stopPropagation();
},
+ onNormalClick (e) {
+ e.stopPropagation();
+ },
+
render () {
const content = { __html: this.props.status.get('content') };
return <div className='status__content' dangerouslySetInnerHTML={content} />;
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
@@ -0,0 +1,48 @@
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Button from '../../../components/button';
+
+const ActionBar = React.createClass({
+
+ propTypes: {
+ account: ImmutablePropTypes.map.isRequired,
+ me: React.PropTypes.number.isRequired,
+ onFollow: React.PropTypes.func.isRequired,
+ onUnfollow: React.PropTypes.func.isRequired
+ },
+
+ mixins: [PureRenderMixin],
+
+ render () {
+ const { account, me } = this.props;
+
+ let followBack = '';
+ let actionButton = '';
+
+ if (account.get('id') === me) {
+ actionButton = 'This is you!';
+ } else {
+ if (account.getIn(['relationship', 'following'])) {
+ actionButton = <Button text='Unfollow' onClick={this.props.onUnfollow} />
+ } else {
+ actionButton = <Button text='Follow' onClick={this.props.onFollow} />
+ }
+
+ if (account.getIn(['relationship', 'followed_by'])) {
+ followBack = 'follows you';
+ }
+ }
+
+ return (
+ <div>
+ {actionButton}
+ {account.get('followers_count')} followers
+ {account.get('following_count')} following
+ {followBack}
+ </div>
+ );
+ },
+
+});
+
+export default ActionBar;
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
@@ -1,13 +1,10 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import Button from '../../../components/button';
const Header = React.createClass({
propTypes: {
- account: ImmutablePropTypes.map.isRequired,
- onFollow: React.PropTypes.func.isRequired,
- onUnfollow: React.PropTypes.func.isRequired
+ account: ImmutablePropTypes.map.isRequired
},
mixins: [PureRenderMixin],
diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx
@@ -11,13 +11,13 @@ import {
import { replyCompose } from '../../actions/compose';
import { favourite, reblog } from '../../actions/interactions';
import Header from './components/header';
-import { selectStatus } from '../../reducers/timelines';
+import {
+ selectStatus,
+ selectAccount
+} from '../../reducers/timelines';
import StatusList from '../../components/status_list';
import Immutable from 'immutable';
-
-function selectAccount(state, id) {
- return state.getIn(['timelines', 'accounts', id], null);
-};
+import ActionBar from './components/action_bar';
function selectStatuses(state, accountId) {
return state.getIn(['timelines', 'accounts_timelines', accountId], Immutable.List()).map(id => selectStatus(state, id)).filterNot(status => status === null);
@@ -25,7 +25,8 @@ function selectStatuses(state, accountId) {
const mapStateToProps = (state, props) => ({
account: selectAccount(state, Number(props.params.accountId)),
- statuses: selectStatuses(state, Number(props.params.accountId))
+ statuses: selectStatuses(state, Number(props.params.accountId)),
+ me: state.getIn(['timelines', 'me'])
});
const Account = React.createClass({
@@ -76,7 +77,7 @@ const Account = React.createClass({
},
render () {
- const { account, statuses } = this.props;
+ const { account, statuses, me } = this.props;
if (account === null) {
return <div>Loading {this.props.params.accountId}...</div>;
@@ -84,7 +85,8 @@ const Account = React.createClass({
return (
<div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
- <Header account={account} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} />
+ <Header account={account} />
+ <ActionBar account={account} me={me} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} />
<StatusList statuses={statuses} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} />
</div>
);
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -39,7 +39,7 @@ export function selectStatus(state, id) {
return null;
}
- status = status.set('account', state.getIn(['timelines', 'accounts', status.get('account')]));
+ status = status.set('account', selectAccount(state, status.get('account')));
if (status.get('reblog') !== null) {
status = status.set('reblog', selectStatus(state, status.get('reblog')));
@@ -48,6 +48,16 @@ export function selectStatus(state, id) {
return status;
};
+export function selectAccount(state, id) {
+ let account = state.getIn(['timelines', 'accounts', id], null);
+
+ if (account === null) {
+ return null;
+ }
+
+ return account.set('relationship', state.getIn(['timelines', 'relationships', id]));
+};
+
function normalizeStatus(state, status) {
// Separate account
let account = status.get('account');
@@ -139,10 +149,18 @@ function deleteStatus(state, id) {
return state.deleteIn(['statuses', id]);
};
-function normalizeAccount(state, account) {
+function normalizeAccount(state, account, relationship) {
+ if (relationship) {
+ state = normalizeRelationship(state, relationship);
+ }
+
return state.setIn(['accounts', account.get('id')], account);
};
+function normalizeRelationship(state, relationship) {
+ return state.setIn(['relationships', relationship.get('id')], relationship);
+};
+
function setSelf(state, account) {
state = normalizeAccount(state, account);
return state.set('me', account.get('id'));
@@ -184,9 +202,10 @@ export default function timelines(state = initialState, action) {
return setSelf(state, Immutable.fromJS(action.account));
case ACCOUNT_FETCH_SUCCESS:
case FOLLOW_SUBMIT_SUCCESS:
+ return normalizeAccount(state, Immutable.fromJS(action.account), Immutable.fromJS(action.relationship));
case ACCOUNT_FOLLOW_SUCCESS:
case ACCOUNT_UNFOLLOW_SUCCESS:
- return normalizeAccount(state, Immutable.fromJS(action.account));
+ return normalizeRelationship(state, Immutable.fromJS(action.relationship));
case STATUS_FETCH_SUCCESS:
return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
diff --git a/app/controllers/api/accounts_controller.rb b/app/controllers/api/accounts_controller.rb
@@ -1,6 +1,6 @@
class Api::AccountsController < ApiController
- before_action :set_account
before_action :doorkeeper_authorize!
+ before_action :set_account
respond_to :json
def show
@@ -20,12 +20,14 @@ class Api::AccountsController < ApiController
def follow
@follow = FollowService.new.(current_user.account, @account.acct)
- render action: :show
+ set_relationship
+ render action: :relationship
end
def unfollow
@unfollow = UnfollowService.new.(current_user.account, @account)
- render action: :show
+ set_relationship
+ render action: :relationship
end
def relationships
@@ -41,4 +43,10 @@ class Api::AccountsController < ApiController
def set_account
@account = Account.find(params[:id])
end
+
+ def set_relationship
+ @following = Account.following_map([@account.id], current_user.account_id)
+ @followed_by = Account.followed_by_map([@account.id], current_user.account_id)
+ @blocking = {}
+ end
end
diff --git a/app/views/api/accounts/relationship.rabl b/app/views/api/accounts/relationship.rabl
@@ -0,0 +1,5 @@
+object @account
+attribute :id
+node(:following) { |account| @following[account.id] || false }
+node(:followed_by) { |account| @followed_by[account.id] || false }
+node(:blocking) { |account| @blocking[account.id] || false }
diff --git a/app/views/api/accounts/relationships.rabl b/app/views/api/accounts/relationships.rabl
@@ -1,5 +1,2 @@
collection @accounts
-attribute :id
-node(:following) { |account| @following[account.id] || false }
-node(:followed_by) { |account| @followed_by[account.id] || false }
-node(:blocking) { |account| @blocking[account.id] || false }
+extends 'api/accounts/relationship'