mirror of
https://github.com/mastodon/mastodon.git
synced 2024-12-23 16:52:53 +01:00
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_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());
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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} />
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
const getStatus = makeGetStatus();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, Number(props.params.statusId)),
|
status: getStatus(state, Number(props.params.statusId)),
|
||||||
ancestors: getStatusAncestors(state, Number(props.params.statusId)),
|
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
|
||||||
descendants: getStatusDescendants(state, Number(props.params.statusId)),
|
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
|
||||||
me: state.getIn(['timelines', 'me'])
|
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);
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
const getStatus = makeGetStatus();
|
||||||
|
|
||||||
|
const mapStateToProps = function (state, props) {
|
||||||
return {
|
return {
|
||||||
text: state.getIn(['compose', 'text']),
|
text: state.getIn(['compose', 'text']),
|
||||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||||
is_uploading: state.getIn(['compose', 'is_uploading']),
|
is_uploading: state.getIn(['compose', 'is_uploading']),
|
||||||
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
|
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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
return createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {
|
||||||
if (base === null) {
|
if (base === null) {
|
||||||
return 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) => {
|
||||||
|
|
Loading…
Reference in a new issue