about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-09-23 20:23:26 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-09-23 20:23:26 +0200
commit3f9708edc4cd799afb68000adc35036dd8b8faa5 (patch)
treeaf7e97a0095c562810577acf68933b461e126794
parentc6d893a71dede65dd88a0dfac81178c8e81e27d2 (diff)
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)
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx8
-rw-r--r--app/assets/javascripts/components/components/status_content.jsx27
-rw-r--r--app/assets/javascripts/components/features/account/components/action_bar.jsx48
-rw-r--r--app/assets/javascripts/components/features/account/components/header.jsx5
-rw-r--r--app/assets/javascripts/components/features/account/index.jsx18
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx25
-rw-r--r--app/controllers/api/accounts_controller.rb14
-rw-r--r--app/views/api/accounts/relationship.rabl5
-rw-r--r--app/views/api/accounts/relationships.rabl5
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
index ac9f5962f..b4183bbee 100644
--- 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
index 98d914514..285945fa8 100644
--- 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
new file mode 100644
index 000000000..a6eb01d61
--- /dev/null
+++ 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
index 20ed7512b..8a4a629ce 100644
--- 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
index 46d0e5954..5b09594cc 100644
--- 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
index 9180b17a2..3b8beafaa 100644
--- 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
index ccbbc93ff..490c28e75 100644
--- 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
new file mode 100644
index 000000000..3e5bf882c
--- /dev/null
+++ 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
index dc1c1bc65..16fdc40d9 100644
--- 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'