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
This commit is contained in:
		
							parent
							
								
									61db14bcbe
								
							
						
					
					
						commit
						f8f40f15da
					
				
					 13 changed files with 179 additions and 154 deletions
				
			
		| 
						 | 
				
			
			@ -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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
 | 
			
		||||
    return <StatusList statuses={statuses} me={me} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} onDelete={this.handleDelete} />
 | 
			
		||||
    if (!statusIds) {
 | 
			
		||||
      return <LoadingIndicator />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return <StatusList statusIds={statusIds} me={me} onScrollToBottom={this.handleScrollToBottom} />
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  }
 | 
			
		||||
export const makeGetStatus = () => {
 | 
			
		||||
  return createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {
 | 
			
		||||
    if (base === null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  return assembleStatus(base.get('id'), statuses, accounts);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue