about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-10-24 17:11:02 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-10-24 18:08:23 +0200
commitf8f40f15dafca65dc07d5c5c19fb9a9dc3473dd6 (patch)
treeb9817a27143158a5c26f9b447a8a58dbb4b272d3
parent61db14bcbe424731c01cf782e8e147a9551c6125 (diff)
Move status components inside individual containers. We still need to select
all statuses/accounts to assemble, but at least lists don't have to be
re-rendered all the time now. Also add "mention" dropdown option
-rw-r--r--app/assets/javascripts/components/actions/compose.jsx8
-rw-r--r--app/assets/javascripts/components/components/status_action_bar.jsx9
-rw-r--r--app/assets/javascripts/components/components/status_list.jsx16
-rw-r--r--app/assets/javascripts/components/containers/status_container.jsx59
-rw-r--r--app/assets/javascripts/components/features/account/components/action_bar.jsx19
-rw-r--r--app/assets/javascripts/components/features/account/index.jsx7
-rw-r--r--app/assets/javascripts/components/features/account_timeline/index.jsx46
-rw-r--r--app/assets/javascripts/components/features/status/components/action_bar.jsx3
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx55
-rw-r--r--app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx22
-rw-r--r--app/assets/javascripts/components/features/ui/containers/status_list_container.jsx48
-rw-r--r--app/assets/javascripts/components/reducers/compose.jsx5
-rw-r--r--app/assets/javascripts/components/selectors/index.jsx36
13 files changed, 179 insertions, 154 deletions
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 402c59dc6..1bf95eec0 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -6,6 +6,7 @@ export const COMPOSE_SUBMIT_SUCCESS  = 'COMPOSE_SUBMIT_SUCCESS';
 export const COMPOSE_SUBMIT_FAIL     = 'COMPOSE_SUBMIT_FAIL';
 export const COMPOSE_REPLY           = 'COMPOSE_REPLY';
 export const COMPOSE_REPLY_CANCEL    = 'COMPOSE_REPLY_CANCEL';
+export const COMPOSE_MENTION         = 'COMPOSE_MENTION';
 export const COMPOSE_UPLOAD_REQUEST  = 'COMPOSE_UPLOAD_REQUEST';
 export const COMPOSE_UPLOAD_SUCCESS  = 'COMPOSE_UPLOAD_SUCCESS';
 export const COMPOSE_UPLOAD_FAIL     = 'COMPOSE_UPLOAD_FAIL';
@@ -32,6 +33,13 @@ export function cancelReplyCompose() {
   };
 };
 
+export function mentionCompose(account) {
+  return {
+    type: COMPOSE_MENTION,
+    account: account
+  };
+};
+
 export function submitCompose() {
   return function (dispatch, getState) {
     dispatch(submitComposeRequest());
diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx
index 945b722a6..23c8e4d5c 100644
--- a/app/assets/javascripts/components/components/status_action_bar.jsx
+++ b/app/assets/javascripts/components/components/status_action_bar.jsx
@@ -9,7 +9,8 @@ const StatusActionBar = React.createClass({
     onReply: React.PropTypes.func,
     onFavourite: React.PropTypes.func,
     onReblog: React.PropTypes.func,
-    onDelete: React.PropTypes.func
+    onDelete: React.PropTypes.func,
+    onMention: React.PropTypes.func
   },
 
   mixins: [PureRenderMixin],
@@ -30,12 +31,18 @@ const StatusActionBar = React.createClass({
     this.props.onDelete(this.props.status);
   },
 
+  handleMentionClick () {
+    this.props.onMention(this.props.status.get('account'));
+  },
+
   render () {
     const { status, me } = this.props;
     let menu = [];
 
     if (status.getIn(['account', 'id']) === me) {
       menu.push({ text: 'Delete', action: this.handleDeleteClick });
+    } else {
+      menu.push({ text: 'Mention', action: this.handleMentionClick });
     }
 
     return (
diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx
index 4977d84ce..b4463b69c 100644
--- a/app/assets/javascripts/components/components/status_list.jsx
+++ b/app/assets/javascripts/components/components/status_list.jsx
@@ -2,18 +2,14 @@ import Status              from './status';
 import ImmutablePropTypes  from 'react-immutable-proptypes';
 import PureRenderMixin     from 'react-addons-pure-render-mixin';
 import { ScrollContainer } from 'react-router-scroll';
+import StatusContainer     from '../containers/status_container';
 
 const StatusList = React.createClass({
 
   propTypes: {
-    statuses: ImmutablePropTypes.list.isRequired,
-    onReply: React.PropTypes.func,
-    onReblog: React.PropTypes.func,
-    onFavourite: React.PropTypes.func,
-    onDelete: React.PropTypes.func,
+    statusIds: ImmutablePropTypes.list.isRequired,
     onScrollToBottom: React.PropTypes.func,
-    trackScroll: React.PropTypes.bool,
-    me: React.PropTypes.number
+    trackScroll: React.PropTypes.bool
   },
 
   getDefaultProps () {
@@ -33,13 +29,13 @@ const StatusList = React.createClass({
   },
 
   render () {
-    const { statuses, onScrollToBottom, trackScroll, ...other } = this.props;
+    const { statusIds, onScrollToBottom, trackScroll } = this.props;
 
     const scrollableArea = (
       <div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}>
         <div>
-          {statuses.map((status) => {
-            return <Status key={status.get('id')} {...other} status={status} />;
+          {statusIds.map((statusId) => {
+            return <StatusContainer key={statusId} id={statusId} />;
           })}
         </div>
       </div>
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
new file mode 100644
index 000000000..b4d3740d9
--- /dev/null
+++ b/app/assets/javascripts/components/containers/status_container.jsx
@@ -0,0 +1,59 @@
+import { connect }       from 'react-redux';
+import Status            from '../components/status';
+import { makeGetStatus } from '../selectors';
+import {
+  replyCompose,
+  mentionCompose
+}                        from '../actions/compose';
+import {
+  reblog,
+  favourite,
+  unreblog,
+  unfavourite
+}                        from '../actions/interactions';
+import { deleteStatus }  from '../actions/statuses';
+
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
+
+  const mapStateToProps = (state, props) => ({
+    status: getStatus(state, props.id),
+    me: state.getIn(['timelines', 'me'])
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch) => ({
+
+  onReply (status) {
+    dispatch(replyCompose(status));
+  },
+
+  onReblog (status) {
+    if (status.get('reblogged')) {
+      dispatch(unreblog(status));
+    } else {
+      dispatch(reblog(status));
+    }
+  },
+
+  onFavourite (status) {
+    if (status.get('favourited')) {
+      dispatch(unfavourite(status));
+    } else {
+      dispatch(favourite(status));
+    }
+  },
+
+  onDelete (status) {
+    dispatch(deleteStatus(status.get('id')));
+  },
+
+  onMention (account) {
+    dispatch(mentionCompose(account));
+  }
+
+});
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(Status);
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
index 0f26b1e5a..195b143af 100644
--- a/app/assets/javascripts/components/features/account/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/account/components/action_bar.jsx
@@ -8,7 +8,8 @@ const ActionBar = React.createClass({
     account: ImmutablePropTypes.map.isRequired,
     me: React.PropTypes.number.isRequired,
     onFollow: React.PropTypes.func.isRequired,
-    onBlock: React.PropTypes.func.isRequired
+    onBlock: React.PropTypes.func.isRequired,
+    onMention: React.PropTypes.func.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -18,6 +19,8 @@ const ActionBar = React.createClass({
 
     let menu = [];
 
+    menu.push({ text: 'Mention', action: this.props.onMention });
+
     if (account.get('id') === me) {
       menu.push({ text: 'Edit profile', href: '/settings/profile' });
     } else if (account.getIn(['relationship', 'blocking'])) {
@@ -32,26 +35,26 @@ const ActionBar = React.createClass({
 
     return (
       <div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto', display: 'flex' }}>
+        <div style={{ padding: '10px', flex: '1 1 auto' }}>
+          <DropdownMenu items={menu} icon='bars' size={24} />
+        </div>
+
         <div style={{ flex: '1 1 auto', display: 'flex', lineHeight: '18px' }}>
-          <div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}>
+          <div style={{ overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}>
             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span>
             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span>
           </div>
 
-          <div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px 5px' }}>
+          <div style={{ overflow: 'hidden', width: '80px', borderLeft: '1px solid #363c4b', padding: '10px 5px' }}>
             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span>
             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span>
           </div>
 
-          <div style={{ overflow: 'hidden', width: '80px', padding: '10px 5px', borderRight: '1px solid #363c4b' }}>
+          <div style={{ overflow: 'hidden', width: '80px', padding: '10px 5px', borderLeft: '1px solid #363c4b' }}>
             <span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span>
             <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span>
           </div>
         </div>
-
-        <div style={{ padding: '10px', flex: '1 1 auto' }}>
-          <DropdownMenu items={menu} icon='bars' size={24} />
-        </div>
       </div>
     );
   },
diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx
index 83770eb74..76d69f751 100644
--- a/app/assets/javascripts/components/features/account/index.jsx
+++ b/app/assets/javascripts/components/features/account/index.jsx
@@ -10,6 +10,7 @@ import {
   fetchAccountTimeline,
   expandAccountTimeline
 }                            from '../../actions/accounts';
+import { mentionCompose }    from '../../actions/compose';
 import Header                from './components/header';
 import {
   getAccountTimeline,
@@ -62,6 +63,10 @@ const Account = React.createClass({
     }
   },
 
+  handleMention () {
+    this.props.dispatch(mentionCompose(this.props.account));
+  },
+
   render () {
     const { account, me } = this.props;
 
@@ -78,7 +83,7 @@ const Account = React.createClass({
         <ColumnBackButton />
         <Header account={account} me={me} />
 
-        <ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />
+        <ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} onMention={this.handleMention} />
 
         {this.props.children}
       </Column>
diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx
index 0b3d641d5..f79570361 100644
--- a/app/assets/javascripts/components/features/account_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/account_timeline/index.jsx
@@ -1,23 +1,15 @@
 import { connect }            from 'react-redux';
 import PureRenderMixin        from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes     from 'react-immutable-proptypes';
-import { getAccountTimeline } from '../../selectors';
 import {
   fetchAccountTimeline,
   expandAccountTimeline
 }                             from '../../actions/accounts';
-import { deleteStatus }       from '../../actions/statuses';
-import { replyCompose }       from '../../actions/compose';
-import {
-  favourite,
-  reblog,
-  unreblog,
-  unfavourite
-}                             from '../../actions/interactions';
 import StatusList             from '../../components/status_list';
+import LoadingIndicator       from '../../components/loading_indicator';
 
 const mapStateToProps = (state, props) => ({
-  statuses: getAccountTimeline(state, Number(props.params.accountId)),
+  statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId)]),
   me: state.getIn(['timelines', 'me'])
 });
 
@@ -26,7 +18,7 @@ const AccountTimeline = React.createClass({
   propTypes: {
     params: React.PropTypes.object.isRequired,
     dispatch: React.PropTypes.func.isRequired,
-    statuses: ImmutablePropTypes.list
+    statusIds: ImmutablePropTypes.list
   },
 
   mixins: [PureRenderMixin],
@@ -41,38 +33,18 @@ const AccountTimeline = React.createClass({
     }
   },
 
-  handleReply (status) {
-    this.props.dispatch(replyCompose(status));
-  },
-
-  handleReblog (status) {
-    if (status.get('reblogged')) {
-      this.props.dispatch(unreblog(status));
-    } else {
-      this.props.dispatch(reblog(status));
-    }
-  },
-
-  handleFavourite (status) {
-    if (status.get('favourited')) {
-      this.props.dispatch(unfavourite(status));
-    } else {
-      this.props.dispatch(favourite(status));
-    }
-  },
-
-  handleDelete (status) {
-    this.props.dispatch(deleteStatus(status.get('id')));
-  },
-
   handleScrollToBottom () {
     this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId)));
   },
 
   render () {
-    const { statuses, me } = this.props;
+    const { statusIds, me } = this.props;
+
+    if (!statusIds) {
+      return <LoadingIndicator />;
+    }
 
-    return <StatusList statuses={statuses} me={me} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} onDelete={this.handleDelete} />
+    return <StatusList statusIds={statusIds} me={me} onScrollToBottom={this.handleScrollToBottom} />
   }
 
 });
diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx
index 6d6aa87fc..d0cae4557 100644
--- a/app/assets/javascripts/components/features/status/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/status/components/action_bar.jsx
@@ -11,6 +11,7 @@ const ActionBar = React.createClass({
     onReblog: React.PropTypes.func.isRequired,
     onFavourite: React.PropTypes.func.isRequired,
     onDelete: React.PropTypes.func.isRequired,
+    onMention: React.PropTypes.func.isRequired,
     me: React.PropTypes.number.isRequired
   },
 
@@ -23,6 +24,8 @@ const ActionBar = React.createClass({
 
     if (me === status.getIn(['account', 'id'])) {
       menu.push({ text: 'Delete', action: () => this.props.onDelete(status) });
+    } else {
+      menu.push({ text: 'Mention', action: () => this.props.onMention(status.get('account')) });
     }
 
     return (
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index c51fb5d31..f4ca8ff92 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -9,22 +9,32 @@ import DetailedStatus        from './components/detailed_status';
 import ActionBar             from './components/action_bar';
 import Column                from '../ui/components/column';
 import { favourite, reblog } from '../../actions/interactions';
-import { replyCompose }      from '../../actions/compose';
+import {
+  replyCompose,
+  mentionCompose
+}                            from '../../actions/compose';
 import { deleteStatus }      from '../../actions/statuses';
 import {
-  getStatus,
+  makeGetStatus,
   getStatusAncestors,
   getStatusDescendants
 }                            from '../../selectors';
 import { ScrollContainer }   from 'react-router-scroll';
 import ColumnBackButton      from '../../components/column_back_button';
+import StatusContainer       from '../../containers/status_container';
 
-const mapStateToProps = (state, props) => ({
-  status: getStatus(state, Number(props.params.statusId)),
-  ancestors: getStatusAncestors(state, Number(props.params.statusId)),
-  descendants: getStatusDescendants(state, Number(props.params.statusId)),
-  me: state.getIn(['timelines', 'me'])
-});
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
+
+  const mapStateToProps = (state, props) => ({
+    status: getStatus(state, Number(props.params.statusId)),
+    ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
+    descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
+    me: state.getIn(['timelines', 'me'])
+  });
+
+  return mapStateToProps;
+};
 
 const Status = React.createClass({
 
@@ -32,8 +42,8 @@ const Status = React.createClass({
     params: React.PropTypes.object.isRequired,
     dispatch: React.PropTypes.func.isRequired,
     status: ImmutablePropTypes.map,
-    ancestors: ImmutablePropTypes.orderedSet.isRequired,
-    descendants: ImmutablePropTypes.orderedSet.isRequired
+    ancestorsIds: ImmutablePropTypes.orderedSet,
+    descendantsIds: ImmutablePropTypes.orderedSet
   },
 
   mixins: [PureRenderMixin],
@@ -64,12 +74,17 @@ const Status = React.createClass({
     this.props.dispatch(deleteStatus(status.get('id')));
   },
 
+  handleMentionClick (account) {
+    this.props.dispatch(mentionCompose(account));
+  },
+
   renderChildren (list) {
-    return list.map(s => <EmbeddedStatus status={s} me={this.props.me} key={s.get('id')} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />);
+    return list.map(id => <StatusContainer key={id} id={id} />);
   },
 
   render () {
-    const { status, ancestors, descendants, me } = this.props;
+    let ancestors, descendants;
+    const { status, ancestorsIds, descendantsIds, me } = this.props;
 
     if (status === null) {
       return (
@@ -81,18 +96,26 @@ const Status = React.createClass({
 
     const account = status.get('account');
 
+    if (ancestorsIds) {
+      ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
+    }
+
+    if (descendantsIds) {
+      descendants = <div>{this.renderChildren(descendantsIds)}</div>;
+    }
+
     return (
       <Column>
         <ColumnBackButton />
 
         <ScrollContainer scrollKey='thread'>
           <div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
-            <div>{this.renderChildren(ancestors)}</div>
+            {ancestors}
 
             <DetailedStatus status={status} me={me} />
-            <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
+            <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} />
 
-            <div>{this.renderChildren(descendants)}</div>
+            {descendants}
           </div>
         </ScrollContainer>
       </Column>
@@ -101,4 +124,4 @@ const Status = React.createClass({
 
 });
 
-export default connect(mapStateToProps)(Status);
+export default connect(makeMapStateToProps)(Status);
diff --git a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
index 747eb9691..163d6fa20 100644
--- a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
@@ -1,15 +1,21 @@
 import { connect }                                          from 'react-redux';
 import ComposeForm                                          from '../components/compose_form';
 import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose';
-import { getStatus }                                        from '../../../selectors';
+import { makeGetStatus }                                    from '../../../selectors';
 
-const mapStateToProps = function (state, props) {
-  return {
-    text: state.getIn(['compose', 'text']),
-    is_submitting: state.getIn(['compose', 'is_submitting']),
-    is_uploading: state.getIn(['compose', 'is_uploading']),
-    in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
+const makeMapStateToProps = () => {
+  const getStatus = makeGetStatus();
+
+  const mapStateToProps = function (state, props) {
+    return {
+      text: state.getIn(['compose', 'text']),
+      is_submitting: state.getIn(['compose', 'is_submitting']),
+      is_uploading: state.getIn(['compose', 'is_uploading']),
+      in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
+    };
   };
+
+  return mapStateToProps;
 };
 
 const mapDispatchToProps = function (dispatch) {
@@ -28,4 +34,4 @@ const mapDispatchToProps = function (dispatch) {
   }
 };
 
-export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
+export default connect(makeMapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
index 045cc59d1..213435a06 100644
--- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
@@ -1,57 +1,17 @@
 import { connect }           from 'react-redux';
 import StatusList            from '../../../components/status_list';
-import { replyCompose }      from '../../../actions/compose';
-import {
-  reblog,
-  favourite,
-  unreblog,
-  unfavourite
-}                            from '../../../actions/interactions';
 import { expandTimeline }    from '../../../actions/timelines';
-import { makeGetTimeline }   from '../../../selectors';
-import { deleteStatus }      from '../../../actions/statuses';
 
-const makeMapStateToProps = () => {
-  const getTimeline = makeGetTimeline();
-
-  const mapStateToProps = (state, props) => ({
-    statuses: getTimeline(state, props.type),
-    me: state.getIn(['timelines', 'me'])
-  });
-
-  return mapStateToProps;
-};
+const mapStateToProps = (state, props) => ({
+  statusIds: state.getIn(['timelines', props.type])
+});
 
 const mapDispatchToProps = function (dispatch, props) {
   return {
-    onReply (status) {
-      dispatch(replyCompose(status));
-    },
-
-    onFavourite (status) {
-      if (status.get('favourited')) {
-        dispatch(unfavourite(status));
-      } else {
-        dispatch(favourite(status));
-      }
-    },
-
-    onReblog (status) {
-      if (status.get('reblogged')) {
-        dispatch(unreblog(status));
-      } else {
-        dispatch(reblog(status));
-      }
-    },
-
     onScrollToBottom () {
       dispatch(expandTimeline(props.type));
-    },
-
-    onDelete (status) {
-      dispatch(deleteStatus(status.get('id')));
     }
   };
 };
 
-export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
+export default connect(mapStateToProps, mapDispatchToProps)(StatusList);
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index 9c9ce566e..f2fd3ad80 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -2,6 +2,7 @@ import {
   COMPOSE_CHANGE,
   COMPOSE_REPLY,
   COMPOSE_REPLY_CANCEL,
+  COMPOSE_MENTION,
   COMPOSE_SUBMIT_REQUEST,
   COMPOSE_SUBMIT_SUCCESS,
   COMPOSE_SUBMIT_FAIL,
@@ -32,7 +33,7 @@ function statusToTextMentions(state, status) {
   if (status.getIn(['account', 'id']) !== me) {
     set = set.add(`@${status.getIn(['account', 'acct'])} `);
   }
-  
+
   return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
 };
 
@@ -92,6 +93,8 @@ export default function compose(state = initialState, action) {
       return removeMedia(state, action.media_id);
     case COMPOSE_UPLOAD_PROGRESS:
       return state.set('progress', Math.round((action.loaded / action.total) * 100));
+    case COMPOSE_MENTION:
+      return state.update('text', text => `${text}@${action.account.get('acct')} `);
     case TIMELINE_DELETE:
       if (action.id === state.get('in_reply_to')) {
         return state.set('in_reply_to', null);
diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx
index 91f900f90..b571e43d5 100644
--- a/app/assets/javascripts/components/selectors/index.jsx
+++ b/app/assets/javascripts/components/selectors/index.jsx
@@ -17,15 +17,15 @@ export const getAccount = createSelector([getAccountBase, getAccountRelationship
 
 const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null);
 
-export const getStatus = createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {
-  if (base === null) {
-    return null;
-  }
-
-  return assembleStatus(base.get('id'), statuses, accounts);
-});
+export const makeGetStatus = () => {
+  return createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {
+    if (base === null) {
+      return null;
+    }
 
-const getAccountTimelineIds = (state, id) => state.getIn(['timelines', 'accounts_timelines', id], Immutable.List());
+    return assembleStatus(base.get('id'), statuses, accounts);
+  });
+};
 
 const assembleStatus = (id, statuses, accounts) => {
   let status = statuses.get(id, null);
@@ -48,26 +48,6 @@ const assembleStatus = (id, statuses, accounts) => {
   return status.set('reblog', reblog).set('account', accounts.get(status.get('account')));
 };
 
-const assembleStatusList = (ids, statuses, accounts) => {
-  return ids.map(statusId => assembleStatus(statusId, statuses, accounts)).filterNot(status => status === null);
-};
-
-export const getAccountTimeline = createSelector([getAccountTimelineIds, getStatuses, getAccounts], assembleStatusList);
-
-const getTimelineIds = (state, timelineType) => state.getIn(['timelines', timelineType]);
-
-export const makeGetTimeline = () => {
-  return createSelector([getTimelineIds, getStatuses, getAccounts], assembleStatusList);
-};
-
-const getStatusAncestorsIds = (state, id) => state.getIn(['timelines', 'ancestors', id], Immutable.OrderedSet());
-
-export const getStatusAncestors = createSelector([getStatusAncestorsIds, getStatuses, getAccounts], assembleStatusList);
-
-const getStatusDescendantsIds = (state, id) => state.getIn(['timelines', 'descendants', id], Immutable.OrderedSet());
-
-export const getStatusDescendants = createSelector([getStatusDescendantsIds, getStatuses, getAccounts], assembleStatusList);
-
 const getNotificationsBase = state => state.get('notifications');
 
 export const getNotifications = createSelector([getNotificationsBase], (base) => {