about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx39
-rw-r--r--app/assets/javascripts/components/features/compose/components/suggestions_box.jsx2
-rw-r--r--app/assets/javascripts/components/features/followers/components/account.jsx68
-rw-r--r--app/assets/javascripts/components/features/followers/containers/account_container.jsx12
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx13
-rw-r--r--app/assets/stylesheets/components.scss12
6 files changed, 116 insertions, 30 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index 803911c6c..224562199 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -40,6 +40,10 @@ export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST';
 export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS';
 export const FOLLOWING_FETCH_FAIL    = 'FOLLOWING_FETCH_FAIL';
 
+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 function setAccountSelf(account) {
   return {
     type: ACCOUNT_SET_SELF,
@@ -304,6 +308,7 @@ export function fetchFollowers(id) {
 
     api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => {
       dispatch(fetchFollowersSuccess(id, response.data));
+      dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => {
       dispatch(fetchFollowersFail(id, error));
     });
@@ -339,6 +344,7 @@ export function fetchFollowing(id) {
 
     api(getState).get(`/api/v1/accounts/${id}/following`).then(response => {
       dispatch(fetchFollowingSuccess(id, response.data));
+      dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => {
       dispatch(fetchFollowingFail(id, error));
     });
@@ -367,3 +373,36 @@ export function fetchFollowingFail(id, error) {
     error: error
   };
 };
+
+export function fetchRelationships(account_ids) {
+  return (dispatch, getState) => {
+    dispatch(fetchRelationshipsRequest(account_ids));
+
+    api(getState).get(`/api/v1/accounts/relationships?${account_ids.map(id => `id[]=${id}`).join('&')}`).then(response => {
+      dispatch(fetchRelationshipsSuccess(response.data));
+    }).catch(error => {
+      dispatch(fetchRelationshipsFail(error));
+    });
+  };
+};
+
+export function fetchRelationshipsRequest(ids) {
+  return {
+    type: RELATIONSHIPS_FETCH_REQUEST,
+    ids: ids
+  };
+};
+
+export function fetchRelationshipsSuccess(relationships) {
+  return {
+    type: RELATIONSHIPS_FETCH_SUCCESS,
+    relationships: relationships
+  };
+};
+
+export function fetchRelationshipsFail(error) {
+  return {
+    type: RELATIONSHIPS_FETCH_FAIL,
+    error: error
+  };
+};
diff --git a/app/assets/javascripts/components/features/compose/components/suggestions_box.jsx b/app/assets/javascripts/components/features/compose/components/suggestions_box.jsx
index aebe36230..c46b82534 100644
--- a/app/assets/javascripts/components/features/compose/components/suggestions_box.jsx
+++ b/app/assets/javascripts/components/features/compose/components/suggestions_box.jsx
@@ -109,7 +109,7 @@ const SuggestionsBox = React.createClass({
             <Link key={account.get('id')} style={itemStyle} to={`/accounts/${account.get('id')}`}>
               <div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
               <strong style={displayNameStyle}>{displayName}</strong>
-              <span style={acctStyle}>{account.get('acct')}</span>
+              <span style={acctStyle}>@{account.get('acct')}</span>
             </Link>
           )
         })}
diff --git a/app/assets/javascripts/components/features/followers/components/account.jsx b/app/assets/javascripts/components/features/followers/components/account.jsx
index 1aa3ce511..27f34c477 100644
--- a/app/assets/javascripts/components/features/followers/components/account.jsx
+++ b/app/assets/javascripts/components/features/followers/components/account.jsx
@@ -1,62 +1,82 @@
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Avatar             from '../../../components/avatar';
+import DisplayName        from '../../../components/display_name';
 import { Link }           from 'react-router';
+import IconButton         from '../../../components/icon_button';
 
 const outerStyle = {
-  padding: '10px'
+  padding: '10px',
+  borderBottom: '1px solid #363c4b'
 };
 
-const displayNameStyle = {
+const itemStyle = {
+  flex: '1 1 auto',
   display: 'block',
-  fontWeight: '500',
+  color: '#9baec8',
   overflow: 'hidden',
-  textOverflow: 'ellipsis',
-  color: '#fff'
+  textDecoration: 'none',
+  fontSize: '14px'
 };
 
-const acctStyle = {
-  display: 'block',
-  overflow: 'hidden',
-  textOverflow: 'ellipsis'
+const noteStyle = {
+  paddingTop: '5px',
+  fontSize: '12px',
+  color: '#616b86'
 };
 
-const itemStyle = {
-  display: 'block',
-  color: '#9baec8',
-  overflow: 'hidden',
-  textDecoration: 'none'
+const buttonsStyle = {
+  padding: '10px'
 };
 
 const Account = React.createClass({
 
   propTypes: {
     account: ImmutablePropTypes.map.isRequired,
-    me: React.PropTypes.number.isRequired
+    me: React.PropTypes.number.isRequired,
+    onFollow: React.PropTypes.func.isRequired,
+    withNote: React.PropTypes.bool
   },
 
   mixins: [PureRenderMixin],
 
+  handleFollow () {
+    this.props.onFollow(this.props.account);
+  },
+
   render () {
-    const { account } = this.props;
+    const { account, me } = this.props;
 
     if (!account) {
       return <div />;
     }
 
-    let displayName = account.get('display_name');
+    let note, buttons;
 
-    if (displayName.length === 0) {
-      displayName = account.get('username');
+    if (account.get('note').length > 0) {
+      note = <div style={noteStyle}>{account.get('note')}</div>;
+    }
+
+    if (account.get('id') !== me) {
+      buttons = (
+        <div style={buttonsStyle}>
+          <IconButton icon='user-plus' title='Follow' onClick={this.handleFollow} active={account.getIn(['relationship', 'following'])} />
+        </div>
+      );
     }
 
     return (
       <div style={outerStyle}>
-        <Link key={account.get('id')} style={itemStyle} to={`/accounts/${account.get('id')}`}>
-          <div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
-          <strong style={displayNameStyle}>{displayName}</strong>
-          <span style={acctStyle}>{account.get('acct')}</span>
-        </Link>
+        <div style={{ display: 'flex' }}>
+          <Link key={account.get('id')} style={itemStyle} className='account__display-name' to={`/accounts/${account.get('id')}`}>
+            <div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
+            <DisplayName account={account} />
+          </Link>
+
+          {buttons}
+        </div>
+
+        {note}
       </div>
     );
   }
diff --git a/app/assets/javascripts/components/features/followers/containers/account_container.jsx b/app/assets/javascripts/components/features/followers/containers/account_container.jsx
index ee6b6dcfd..988d60adb 100644
--- a/app/assets/javascripts/components/features/followers/containers/account_container.jsx
+++ b/app/assets/javascripts/components/features/followers/containers/account_container.jsx
@@ -1,6 +1,10 @@
 import { connect }            from 'react-redux';
 import { makeGetAccount }     from '../../../selectors';
 import Account                from '../components/account';
+import {
+  followAccount,
+  unfollowAccount
+}                             from '../../../actions/accounts';
 
 const makeMapStateToProps = () => {
   const getAccount = makeGetAccount();
@@ -14,7 +18,13 @@ const makeMapStateToProps = () => {
 };
 
 const mapDispatchToProps = (dispatch) => ({
-  //
+  onFollow (account) {
+    if (account.getIn(['relationship', 'following'])) {
+      dispatch(unfollowAccount(account.get('id')));
+    } else {
+      dispatch(followAccount(account.get('id')));
+    }
+  }
 });
 
 export default connect(makeMapStateToProps, mapDispatchToProps)(Account);
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index 59a1fbaa7..4bf97c18d 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -20,7 +20,8 @@ import {
   ACCOUNT_TIMELINE_FETCH_SUCCESS,
   ACCOUNT_TIMELINE_EXPAND_SUCCESS,
   FOLLOWERS_FETCH_SUCCESS,
-  FOLLOWING_FETCH_SUCCESS
+  FOLLOWING_FETCH_SUCCESS,
+  RELATIONSHIPS_FETCH_SUCCESS
 }                                from '../actions/accounts';
 import {
   STATUS_FETCH_SUCCESS,
@@ -184,6 +185,14 @@ function normalizeRelationship(state, relationship) {
   return state.setIn(['relationships', relationship.get('id')], relationship);
 };
 
+function normalizeRelationships(state, relationships) {
+  relationships.forEach(relationship => {
+    state = normalizeRelationship(state, relationship);
+  });
+
+  return state;
+};
+
 function setSelf(state, account) {
   state = normalizeAccount(state, account);
   return state.set('me', account.get('id'));
@@ -252,6 +261,8 @@ export default function timelines(state = initialState, action) {
     case FOLLOWERS_FETCH_SUCCESS:
     case FOLLOWING_FETCH_SUCCESS:
       return normalizeAccounts(state, Immutable.fromJS(action.accounts));
+    case RELATIONSHIPS_FETCH_SUCCESS:
+      return normalizeRelationships(state, Immutable.fromJS(action.relationships));
     default:
       return state;
   }
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 0fd026fee..2b1c1194d 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -117,17 +117,17 @@
   }
 }
 
-.status__display-name, .status__relative-time, .detailed-status__display-name, .detailed-status__datetime {
+.status__display-name, .status__relative-time, .detailed-status__display-name, .detailed-status__datetime, .account__display-name {
   text-decoration: none;
 }
 
-.status__display-name {
+.status__display-name, .account__display-name {
   strong {
     color: #fff;
   }
 }
 
-.status__display-name, .reply-indicator__display-name, .detailed-status__display-name {
+.status__display-name, .reply-indicator__display-name, .detailed-status__display-name, .account__display-name {
   &:hover {
     strong {
       text-decoration: underline;
@@ -135,6 +135,12 @@
   }
 }
 
+.account__display-name {
+  strong {
+    display: block;
+  }
+}
+
 .detailed-status__display-name {
   color: #d9e1e8;
   line-height: 24px;