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:
Eugen Rochko 2016-10-24 17:11:02 +02:00
parent 61db14bcbe
commit f8f40f15da
13 changed files with 179 additions and 154 deletions

View File

@ -6,6 +6,7 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY = 'COMPOSE_REPLY';
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; 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_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; 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() { export function submitCompose() {
return function (dispatch, getState) { return function (dispatch, getState) {
dispatch(submitComposeRequest()); dispatch(submitComposeRequest());

View File

@ -9,7 +9,8 @@ const StatusActionBar = React.createClass({
onReply: React.PropTypes.func, onReply: React.PropTypes.func,
onFavourite: React.PropTypes.func, onFavourite: React.PropTypes.func,
onReblog: React.PropTypes.func, onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func onDelete: React.PropTypes.func,
onMention: React.PropTypes.func
}, },
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
@ -30,12 +31,18 @@ const StatusActionBar = React.createClass({
this.props.onDelete(this.props.status); this.props.onDelete(this.props.status);
}, },
handleMentionClick () {
this.props.onMention(this.props.status.get('account'));
},
render () { render () {
const { status, me } = this.props; const { status, me } = this.props;
let menu = []; let menu = [];
if (status.getIn(['account', 'id']) === me) { if (status.getIn(['account', 'id']) === me) {
menu.push({ text: 'Delete', action: this.handleDeleteClick }); menu.push({ text: 'Delete', action: this.handleDeleteClick });
} else {
menu.push({ text: 'Mention', action: this.handleMentionClick });
} }
return ( return (

View File

@ -2,18 +2,14 @@ import Status from './status';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import { ScrollContainer } from 'react-router-scroll'; import { ScrollContainer } from 'react-router-scroll';
import StatusContainer from '../containers/status_container';
const StatusList = React.createClass({ const StatusList = React.createClass({
propTypes: { propTypes: {
statuses: ImmutablePropTypes.list.isRequired, statusIds: ImmutablePropTypes.list.isRequired,
onReply: React.PropTypes.func,
onReblog: React.PropTypes.func,
onFavourite: React.PropTypes.func,
onDelete: React.PropTypes.func,
onScrollToBottom: React.PropTypes.func, onScrollToBottom: React.PropTypes.func,
trackScroll: React.PropTypes.bool, trackScroll: React.PropTypes.bool
me: React.PropTypes.number
}, },
getDefaultProps () { getDefaultProps () {
@ -33,13 +29,13 @@ const StatusList = React.createClass({
}, },
render () { render () {
const { statuses, onScrollToBottom, trackScroll, ...other } = this.props; const { statusIds, onScrollToBottom, trackScroll } = this.props;
const scrollableArea = ( const scrollableArea = (
<div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}> <div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}>
<div> <div>
{statuses.map((status) => { {statusIds.map((statusId) => {
return <Status key={status.get('id')} {...other} status={status} />; return <StatusContainer key={statusId} id={statusId} />;
})} })}
</div> </div>
</div> </div>

View File

@ -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);

View File

@ -8,7 +8,8 @@ const ActionBar = React.createClass({
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
me: React.PropTypes.number.isRequired, me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired, onFollow: React.PropTypes.func.isRequired,
onBlock: React.PropTypes.func.isRequired onBlock: React.PropTypes.func.isRequired,
onMention: React.PropTypes.func.isRequired
}, },
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
@ -18,6 +19,8 @@ const ActionBar = React.createClass({
let menu = []; let menu = [];
menu.push({ text: 'Mention', action: this.props.onMention });
if (account.get('id') === me) { if (account.get('id') === me) {
menu.push({ text: 'Edit profile', href: '/settings/profile' }); menu.push({ text: 'Edit profile', href: '/settings/profile' });
} else if (account.getIn(['relationship', 'blocking'])) { } else if (account.getIn(['relationship', 'blocking'])) {
@ -32,26 +35,26 @@ const ActionBar = React.createClass({
return ( return (
<div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto', display: 'flex' }}> <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={{ 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', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span> <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span>
</div> </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', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span> <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span>
</div> </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', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span> <span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span>
</div> </div>
</div> </div>
<div style={{ padding: '10px', flex: '1 1 auto' }}>
<DropdownMenu items={menu} icon='bars' size={24} />
</div>
</div> </div>
); );
}, },

View File

@ -10,6 +10,7 @@ import {
fetchAccountTimeline, fetchAccountTimeline,
expandAccountTimeline expandAccountTimeline
} from '../../actions/accounts'; } from '../../actions/accounts';
import { mentionCompose } from '../../actions/compose';
import Header from './components/header'; import Header from './components/header';
import { import {
getAccountTimeline, getAccountTimeline,
@ -62,6 +63,10 @@ const Account = React.createClass({
} }
}, },
handleMention () {
this.props.dispatch(mentionCompose(this.props.account));
},
render () { render () {
const { account, me } = this.props; const { account, me } = this.props;
@ -78,7 +83,7 @@ const Account = React.createClass({
<ColumnBackButton /> <ColumnBackButton />
<Header account={account} me={me} /> <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} {this.props.children}
</Column> </Column>

View File

@ -1,23 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { getAccountTimeline } from '../../selectors';
import { import {
fetchAccountTimeline, fetchAccountTimeline,
expandAccountTimeline expandAccountTimeline
} from '../../actions/accounts'; } 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 StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
const mapStateToProps = (state, props) => ({ 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']) me: state.getIn(['timelines', 'me'])
}); });
@ -26,7 +18,7 @@ const AccountTimeline = React.createClass({
propTypes: { propTypes: {
params: React.PropTypes.object.isRequired, params: React.PropTypes.object.isRequired,
dispatch: React.PropTypes.func.isRequired, dispatch: React.PropTypes.func.isRequired,
statuses: ImmutablePropTypes.list statusIds: ImmutablePropTypes.list
}, },
mixins: [PureRenderMixin], 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 () { handleScrollToBottom () {
this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId))); this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId)));
}, },
render () { 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} />
} }
}); });

View File

@ -11,6 +11,7 @@ const ActionBar = React.createClass({
onReblog: React.PropTypes.func.isRequired, onReblog: React.PropTypes.func.isRequired,
onFavourite: React.PropTypes.func.isRequired, onFavourite: React.PropTypes.func.isRequired,
onDelete: React.PropTypes.func.isRequired, onDelete: React.PropTypes.func.isRequired,
onMention: React.PropTypes.func.isRequired,
me: React.PropTypes.number.isRequired me: React.PropTypes.number.isRequired
}, },
@ -23,6 +24,8 @@ const ActionBar = React.createClass({
if (me === status.getIn(['account', 'id'])) { if (me === status.getIn(['account', 'id'])) {
menu.push({ text: 'Delete', action: () => this.props.onDelete(status) }); menu.push({ text: 'Delete', action: () => this.props.onDelete(status) });
} else {
menu.push({ text: 'Mention', action: () => this.props.onMention(status.get('account')) });
} }
return ( return (

View File

@ -9,22 +9,32 @@ import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar'; import ActionBar from './components/action_bar';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
import { favourite, reblog } from '../../actions/interactions'; import { favourite, reblog } from '../../actions/interactions';
import { replyCompose } from '../../actions/compose'; import {
replyCompose,
mentionCompose
} from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses'; import { deleteStatus } from '../../actions/statuses';
import { import {
getStatus, makeGetStatus,
getStatusAncestors, getStatusAncestors,
getStatusDescendants getStatusDescendants
} from '../../selectors'; } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll'; import { ScrollContainer } from 'react-router-scroll';
import ColumnBackButton from '../../components/column_back_button'; import ColumnBackButton from '../../components/column_back_button';
import StatusContainer from '../../containers/status_container';
const mapStateToProps = (state, props) => ({ const makeMapStateToProps = () => {
status: getStatus(state, Number(props.params.statusId)), const getStatus = makeGetStatus();
ancestors: getStatusAncestors(state, Number(props.params.statusId)),
descendants: getStatusDescendants(state, Number(props.params.statusId)), const mapStateToProps = (state, props) => ({
me: state.getIn(['timelines', 'me']) 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({ const Status = React.createClass({
@ -32,8 +42,8 @@ const Status = React.createClass({
params: React.PropTypes.object.isRequired, params: React.PropTypes.object.isRequired,
dispatch: React.PropTypes.func.isRequired, dispatch: React.PropTypes.func.isRequired,
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
ancestors: ImmutablePropTypes.orderedSet.isRequired, ancestorsIds: ImmutablePropTypes.orderedSet,
descendants: ImmutablePropTypes.orderedSet.isRequired descendantsIds: ImmutablePropTypes.orderedSet
}, },
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
@ -64,12 +74,17 @@ const Status = React.createClass({
this.props.dispatch(deleteStatus(status.get('id'))); this.props.dispatch(deleteStatus(status.get('id')));
}, },
handleMentionClick (account) {
this.props.dispatch(mentionCompose(account));
},
renderChildren (list) { 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 () { render () {
const { status, ancestors, descendants, me } = this.props; let ancestors, descendants;
const { status, ancestorsIds, descendantsIds, me } = this.props;
if (status === null) { if (status === null) {
return ( return (
@ -81,18 +96,26 @@ const Status = React.createClass({
const account = status.get('account'); const account = status.get('account');
if (ancestorsIds) {
ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
}
if (descendantsIds) {
descendants = <div>{this.renderChildren(descendantsIds)}</div>;
}
return ( return (
<Column> <Column>
<ColumnBackButton /> <ColumnBackButton />
<ScrollContainer scrollKey='thread'> <ScrollContainer scrollKey='thread'>
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'> <div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
<div>{this.renderChildren(ancestors)}</div> {ancestors}
<DetailedStatus status={status} me={me} /> <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> </div>
</ScrollContainer> </ScrollContainer>
</Column> </Column>
@ -101,4 +124,4 @@ const Status = React.createClass({
}); });
export default connect(mapStateToProps)(Status); export default connect(makeMapStateToProps)(Status);

View File

@ -1,15 +1,21 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ComposeForm from '../components/compose_form'; import ComposeForm from '../components/compose_form';
import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose'; import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose';
import { getStatus } from '../../../selectors'; import { makeGetStatus } from '../../../selectors';
const mapStateToProps = function (state, props) { const makeMapStateToProps = () => {
return { const getStatus = makeGetStatus();
text: state.getIn(['compose', 'text']),
is_submitting: state.getIn(['compose', 'is_submitting']), const mapStateToProps = function (state, props) {
is_uploading: state.getIn(['compose', 'is_uploading']), return {
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])) 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) { const mapDispatchToProps = function (dispatch) {
@ -28,4 +34,4 @@ const mapDispatchToProps = function (dispatch) {
} }
}; };
export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); export default connect(makeMapStateToProps, mapDispatchToProps)(ComposeForm);

View File

@ -1,57 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import StatusList from '../../../components/status_list'; 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 { expandTimeline } from '../../../actions/timelines';
import { makeGetTimeline } from '../../../selectors';
import { deleteStatus } from '../../../actions/statuses';
const makeMapStateToProps = () => { const mapStateToProps = (state, props) => ({
const getTimeline = makeGetTimeline(); statusIds: state.getIn(['timelines', props.type])
});
const mapStateToProps = (state, props) => ({
statuses: getTimeline(state, props.type),
me: state.getIn(['timelines', 'me'])
});
return mapStateToProps;
};
const mapDispatchToProps = function (dispatch, props) { const mapDispatchToProps = function (dispatch, props) {
return { 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 () { onScrollToBottom () {
dispatch(expandTimeline(props.type)); dispatch(expandTimeline(props.type));
},
onDelete (status) {
dispatch(deleteStatus(status.get('id')));
} }
}; };
}; };
export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList); export default connect(mapStateToProps, mapDispatchToProps)(StatusList);

View File

@ -2,6 +2,7 @@ import {
COMPOSE_CHANGE, COMPOSE_CHANGE,
COMPOSE_REPLY, COMPOSE_REPLY,
COMPOSE_REPLY_CANCEL, COMPOSE_REPLY_CANCEL,
COMPOSE_MENTION,
COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_REQUEST,
COMPOSE_SUBMIT_SUCCESS, COMPOSE_SUBMIT_SUCCESS,
COMPOSE_SUBMIT_FAIL, COMPOSE_SUBMIT_FAIL,
@ -32,7 +33,7 @@ function statusToTextMentions(state, status) {
if (status.getIn(['account', 'id']) !== me) { if (status.getIn(['account', 'id']) !== me) {
set = set.add(`@${status.getIn(['account', 'acct'])} `); 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(''); 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); return removeMedia(state, action.media_id);
case COMPOSE_UPLOAD_PROGRESS: case COMPOSE_UPLOAD_PROGRESS:
return state.set('progress', Math.round((action.loaded / action.total) * 100)); 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: case TIMELINE_DELETE:
if (action.id === state.get('in_reply_to')) { if (action.id === state.get('in_reply_to')) {
return state.set('in_reply_to', null); return state.set('in_reply_to', null);

View File

@ -17,15 +17,15 @@ export const getAccount = createSelector([getAccountBase, getAccountRelationship
const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null); const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null);
export const getStatus = createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => { export const makeGetStatus = () => {
if (base === null) { return createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {
return null; if (base === null) {
} return null;
}
return assembleStatus(base.get('id'), statuses, accounts); return assembleStatus(base.get('id'), statuses, accounts);
}); });
};
const getAccountTimelineIds = (state, id) => state.getIn(['timelines', 'accounts_timelines', id], Immutable.List());
const assembleStatus = (id, statuses, accounts) => { const assembleStatus = (id, statuses, accounts) => {
let status = statuses.get(id, null); 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'))); 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'); const getNotificationsBase = state => state.get('notifications');
export const getNotifications = createSelector([getNotificationsBase], (base) => { export const getNotifications = createSelector([getNotificationsBase], (base) => {