Render posts for the current channel (#153)
* Added selectChannel action * Fixed selected team not being cleared on logout * Fixed posts and postsByChannel not being initialized correctly * Added basic post rendering * Renamed selectors files to be plural * Removed empty file * Fixed unit tests after merge * Removing unnecessary code
This commit is contained in:
parent
8243eda444
commit
4ecd3758e7
28 changed files with 460 additions and 74 deletions
49
app/actions/views/channel.js
Normal file
49
app/actions/views/channel.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {fetchMyChannelsAndMembers, selectChannel} from 'service/actions/channels';
|
||||
import {getPosts} from 'service/actions/posts';
|
||||
import {Constants} from 'service/constants';
|
||||
|
||||
export function loadChannelsIfNecessary(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
const channels = getState().entities.channels.channels;
|
||||
|
||||
let hasChannelsForTeam = false;
|
||||
for (const channel of Object.values(channels)) {
|
||||
if (channel.team_id === teamId) {
|
||||
// If we have one channel, assume we have all of them
|
||||
hasChannelsForTeam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChannelsForTeam) {
|
||||
await fetchMyChannelsAndMembers(teamId)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadPostsIfNecessary(channel) {
|
||||
return async (dispatch, getState) => {
|
||||
const postsInChannel = getState().entities.posts.postsByChannel[channel.id];
|
||||
|
||||
if (!postsInChannel) {
|
||||
await getPosts(channel.team_id, channel.id)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function selectInitialChannel(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
const channels = getState().entities.channels.channels;
|
||||
|
||||
for (const channel of Object.values(channels)) {
|
||||
// TODO figure out how to handle when we can't find the town square
|
||||
if (channel.team_id === teamId && channel.name === Constants.DEFAULT_CHANNEL) {
|
||||
await selectChannel(channel.id)(dispatch, getState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -5,17 +5,19 @@ import React from 'react';
|
|||
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
export default class ChannelSidebar extends React.Component {
|
||||
class ChannelSidebar extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
selectChannel: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentTeam: React.PropTypes.object.isRequired,
|
||||
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
||||
onSelectChannel: React.PropTypes.func
|
||||
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
onSelectChannel = (channel) => {
|
||||
console.log('clicked channel ' + channel.name); // eslint-disable-line no-console
|
||||
|
||||
// this.props.onSelectChannel(channel);
|
||||
this.props.actions.selectChannel(channel.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -45,3 +47,22 @@ export default class ChannelSidebar extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {selectChannel} from 'service/actions/channels';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return ownProps;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
selectChannel
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelSidebar);
|
||||
|
|
|
|||
37
app/components/formatted_time.js
Normal file
37
app/components/formatted_time.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
class FormattedTime extends React.Component {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
value: React.PropTypes.any.isRequired,
|
||||
format: React.PropTypes.string,
|
||||
children: React.PropTypes.func
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
value,
|
||||
children,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
Reflect.deleteProperty(props, 'format');
|
||||
|
||||
const formattedTime = intl.formatTime(value, this.props);
|
||||
|
||||
if (typeof children === 'function') {
|
||||
return children(formattedTime);
|
||||
}
|
||||
|
||||
return <Text>{formattedTime}</Text>;
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(FormattedTime);
|
||||
|
||||
6
app/components/post/index.js
Normal file
6
app/components/post/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import Post from './post_container';
|
||||
|
||||
export default Post;
|
||||
45
app/components/post/post.js
Normal file
45
app/components/post/post.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import FormattedTime from 'app/components/formatted_time';
|
||||
|
||||
import {isSystemMessage} from 'service/utils/post_utils.js';
|
||||
|
||||
export default class Post extends React.Component {
|
||||
static propTypes = {
|
||||
post: React.PropTypes.object.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
theme: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
let displayName;
|
||||
if (isSystemMessage(this.props.post)) {
|
||||
displayName = 'System'; // TODO this should be localized
|
||||
} else if (this.props.user) {
|
||||
displayName = this.props.user.username;
|
||||
} else {
|
||||
displayName = (
|
||||
<FormattedText
|
||||
id='channel_loader.someone'
|
||||
defaultMessage='Someone'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>
|
||||
{'['}
|
||||
<FormattedTime value={this.props.post.create_at}/>
|
||||
{'] '}
|
||||
{displayName}
|
||||
{': ' + this.props.post.message}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
20
app/components/post/post_container.js
Normal file
20
app/components/post/post_container.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
import {getUser} from 'service/selectors/entities/users';
|
||||
|
||||
import Post from './post';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
user: getUser(state, ownProps.post.user_id),
|
||||
post: ownProps.post,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Post);
|
||||
|
||||
43
app/components/post_list.js
Normal file
43
app/components/post_list.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {ListView} from 'react-native';
|
||||
|
||||
import Post from 'app/components/post';
|
||||
|
||||
export default class PostList extends React.Component {
|
||||
static propTypes = {
|
||||
posts: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dataSource: new ListView.DataSource({
|
||||
rowHasChanged: (a, b) => a !== b
|
||||
}).cloneWithRows(this.props.posts)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
dataSource: this.state.dataSource.cloneWithRows(nextProps.posts)
|
||||
});
|
||||
}
|
||||
|
||||
renderPost(post) {
|
||||
return <Post post={post}/>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ListView
|
||||
enableEmptySections={true}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this.renderPost}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,21 +2,24 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
import Drawer from 'react-native-drawer';
|
||||
import ChannelSidebar from 'app/components/channel_sidebar';
|
||||
import Loading from 'app/components/loading';
|
||||
import RightSidebarMenu from 'app/components/right_sidebar_menu';
|
||||
import {StatusBar, Text, TouchableHighlight, View} from 'react-native';
|
||||
import Drawer from 'react-native-drawer';
|
||||
|
||||
import ChannelSidebar from 'app/components/channel_sidebar';
|
||||
import RightSidebarMenu from 'app/components/right_sidebar_menu';
|
||||
|
||||
import ChannelPostList from './components/channel_post_list';
|
||||
|
||||
export default class Channel extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
currentTeam: React.PropTypes.object,
|
||||
actions: React.PropTypes.shape({
|
||||
loadChannelsIfNecessary: React.PropTypes.func.isRequired,
|
||||
selectInitialChannel: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentTeam: React.PropTypes.object.isRequired,
|
||||
currentChannel: React.PropTypes.object,
|
||||
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
||||
channels: React.PropTypes.array,
|
||||
theme: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
|
@ -32,12 +35,16 @@ export default class Channel extends React.Component {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.actions.fetchMyChannelsAndMembers(this.props.currentTeam.id);
|
||||
this.props.actions.loadChannelsIfNecessary(this.props.currentTeam.id).then(() => {
|
||||
return this.props.actions.selectInitialChannel(this.props.currentTeam.id);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.currentTeam && nextProps.currentTeam && this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
this.props.actions.fetchMyChannelsAndMembers(nextProps.currentTeam.id);
|
||||
if (this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
this.props.actions.loadChannelsIfNecessary(nextProps.currentTeam.id).then(() => {
|
||||
this.props.actions.selectInitialChannel(nextProps.currentTeam.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,14 +66,16 @@ export default class Channel extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
currentChannel,
|
||||
currentTeam,
|
||||
currentChannel,
|
||||
channels,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentChannel) {
|
||||
return <Loading/>;
|
||||
if (!currentTeam) {
|
||||
return <Text>{'Waiting on team'}</Text>;
|
||||
} else if (!currentChannel) {
|
||||
return <Text>{'Waiting on channel'}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -109,10 +118,7 @@ export default class Channel extends React.Component {
|
|||
<Text style={{color: theme.sidebarHeaderTextColor}}>{'>'}</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
<View style={{flex: 1}}>
|
||||
<Text style={{color: theme.centerChannelColor}}>{currentTeam.id + ' - ' + currentTeam.name}</Text>
|
||||
<Text style={{color: theme.centerChannelColor}}>{currentChannel.id + ' - ' + currentChannel.name}</Text>
|
||||
</View>
|
||||
<ChannelPostList channel={currentChannel}/>
|
||||
</Drawer>
|
||||
</Drawer>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -4,38 +4,20 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {fetchMyChannelsAndMembers} from 'service/actions/channels';
|
||||
import {loadChannelsIfNecessary, selectInitialChannel} from 'app/actions/views/channel';
|
||||
|
||||
import {getChannelsOnCurrentTeam, getCurrentChannel} from 'service/selectors/entities/channels';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
import {getCurrentTeam} from 'service/selectors/entities/teams';
|
||||
|
||||
import Channel from './channel.js';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const currentTeamId = state.entities.teams.currentId;
|
||||
const currentTeam = state.entities.teams.teams[currentTeamId];
|
||||
let channels = state.entities.channels.channels;
|
||||
|
||||
let currentChannel;
|
||||
if (ownProps.currentChannelId) {
|
||||
currentChannel = state.entities.channels.channels[ownProps.currentChannelId];
|
||||
} else {
|
||||
// TODO figure out the town square id before this
|
||||
for (const channelId of Object.keys(channels)) {
|
||||
const channel = channels[channelId];
|
||||
|
||||
if (channel.name === 'town-square') {
|
||||
currentChannel = channel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channels = Object.keys(channels).map((channelId) => channels[channelId]);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeam,
|
||||
currentChannel,
|
||||
channels,
|
||||
currentTeam: getCurrentTeam(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
channels: getChannelsOnCurrentTeam(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
|
@ -43,7 +25,8 @@ function mapStateToProps(state, ownProps) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
fetchMyChannelsAndMembers
|
||||
loadChannelsIfNecessary,
|
||||
selectInitialChannel
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import PostList from 'app/components/post_list';
|
||||
|
||||
export default class ChannelPostList extends React.Component {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
loadPostsIfNecessary: React.PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channel: React.PropTypes.object.isRequired,
|
||||
posts: React.PropTypes.array
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.actions.loadPostsIfNecessary(this.props.channel);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.channel.id !== nextProps.channel.id) {
|
||||
this.props.actions.loadPostsIfNecessary(nextProps.channel);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.posts) {
|
||||
return <Text>{'waiting on posts'}</Text>;
|
||||
}
|
||||
|
||||
return <PostList posts={this.props.posts}/>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {loadPostsIfNecessary} from 'app/actions/views/channel';
|
||||
|
||||
import {getPostsInCurrentChannel} from 'service/selectors/entities/posts';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
posts: getPostsInCurrentChannel(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
loadPostsIfNecessary
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelPostList);
|
||||
6
app/scenes/channel/components/channel_post_list/index.js
Normal file
6
app/scenes/channel/components/channel_post_list/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ChannelPostListContainer from './channel_post_list_container';
|
||||
|
||||
export default ChannelPostListContainer;
|
||||
|
|
@ -38,8 +38,9 @@ export default class SelectTeam extends Component {
|
|||
}
|
||||
|
||||
onSelectTeam(team) {
|
||||
this.props.actions.selectTeam(team);
|
||||
this.props.actions.goToChannelView();
|
||||
this.props.actions.selectTeam(team).then(() => {
|
||||
this.props.actions.goToChannelView();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"react-redux": "4.4.5",
|
||||
"redux": "3.6.0",
|
||||
"redux-batched-actions": "0.1.3",
|
||||
"redux-thunk": "2.1.0"
|
||||
"redux-thunk": "2.1.0",
|
||||
"reselect": "2.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "7.0.0",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import Client from 'service/client';
|
|||
export function selectChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ChannelTypes.SELECTED_CHANNEL,
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: channelId
|
||||
}, getState);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export function getPosts(teamId, channelId, offset = 0, limit = Constants.POST_C
|
|||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch);
|
||||
dispatch({type: PostsTypes.GET_POSTS_FAILURE, error}, getState);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
|
|
@ -141,6 +141,8 @@ export function getPosts(teamId, channelId, offset = 0, limit = Constants.POST_C
|
|||
type: PostsTypes.GET_POSTS_SUCCESS
|
||||
}
|
||||
]), getState);
|
||||
|
||||
return posts;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ function handleLeaveTeamEvent(msg, dispatch, getState) {
|
|||
data: ''
|
||||
},
|
||||
{
|
||||
type: ChannelTypes.SELECTED_CHANNEL,
|
||||
type: ChannelTypes.SELECT_CHANNEL,
|
||||
data: ''
|
||||
}
|
||||
]), getState);
|
||||
|
|
@ -314,7 +314,7 @@ function handleChannelDeletedEvent(msg, dispatch, getState) {
|
|||
channelId = channel[0];
|
||||
}
|
||||
|
||||
dispatch({type: ChannelTypes.SELECTED_CHANNEL, data: channelId}, getState);
|
||||
dispatch({type: ChannelTypes.SELECT_CHANNEL, data: channelId}, getState);
|
||||
}
|
||||
|
||||
fetchMyChannelsAndMembers(teams.currentId)(dispatch, getState);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const ChannelTypes = keyMirror({
|
|||
REMOVE_CHANNEL_MEMBER_SUCCESS: null,
|
||||
REMOVE_CHANNEL_MEMBER_FAILURE: null,
|
||||
|
||||
SELECTED_CHANNEL: null,
|
||||
SELECT_CHANNEL: null,
|
||||
LEAVE_CHANNEL: null,
|
||||
RECEIVED_CHANNEL: null,
|
||||
RECEIVED_CHANNELS: null,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const Constants = {
|
|||
DM_CHANNEL: 'D',
|
||||
|
||||
POST_DELETED: 'DELETED',
|
||||
SYSTEM_MESSAGE_PREFIX: 'system_',
|
||||
|
||||
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {combineReducers} from 'redux';
|
|||
|
||||
function currentId(state = '', action) {
|
||||
switch (action.type) {
|
||||
case ChannelTypes.SELECTED_CHANNEL:
|
||||
case ChannelTypes.SELECT_CHANNEL:
|
||||
return action.data;
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -3,13 +3,6 @@
|
|||
|
||||
import {Constants, PostsTypes, UsersTypes} from 'service/constants';
|
||||
|
||||
function initialState() {
|
||||
return {
|
||||
posts: {},
|
||||
postsByChannel: {}
|
||||
};
|
||||
}
|
||||
|
||||
function handleReceivedPost(posts = {}, postsByChannel = {}, action) {
|
||||
const post = action.data;
|
||||
const channelId = post.channel_id;
|
||||
|
|
@ -130,21 +123,27 @@ function handleRemovePost(posts = {}, postsByChannel = {}, action) {
|
|||
return {posts: nextPosts, postsByChannel: nextPostsByChannel};
|
||||
}
|
||||
|
||||
function handlePosts(state = initialState(), action) {
|
||||
function handlePosts(posts = {}, postsByChannel = {}, action) {
|
||||
switch (action.type) {
|
||||
case PostsTypes.RECEIVED_POST:
|
||||
return handleReceivedPost(state.posts, state.postsByChannel, action);
|
||||
return handleReceivedPost(posts, postsByChannel, action);
|
||||
case PostsTypes.RECEIVED_POSTS:
|
||||
return handleReceivedPosts(state.posts, state.postsByChannel, action);
|
||||
return handleReceivedPosts(posts, postsByChannel, action);
|
||||
case PostsTypes.POST_DELETED:
|
||||
return handlePostDeleted(state.posts, state.postsByChannel, action);
|
||||
return handlePostDeleted(posts, postsByChannel, action);
|
||||
case PostsTypes.REMOVE_POST:
|
||||
return handleRemovePost(state.posts, state.postsByChannel, action);
|
||||
return handleRemovePost(posts, postsByChannel, action);
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return initialState();
|
||||
return {
|
||||
posts: {},
|
||||
postsByChannel: {}
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
return {
|
||||
posts,
|
||||
postsByChannel
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,8 +165,8 @@ function currentFocusedPostId(state = '', action) {
|
|||
}
|
||||
}
|
||||
|
||||
export default function(state = initialState(), action) {
|
||||
const {posts, postsByChannel} = handlePosts(state, action);
|
||||
export default function(state = {}, action) {
|
||||
const {posts, postsByChannel} = handlePosts(state.posts, state.postsByChannel, action);
|
||||
|
||||
const nextState = {
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ function currentId(state = '', action) {
|
|||
case TeamsTypes.SELECT_TEAM:
|
||||
return action.data;
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return '';
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
38
service/selectors/entities/channels.js
Normal file
38
service/selectors/entities/channels.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {getCurrentTeamId} from 'service/selectors/entities/teams';
|
||||
|
||||
function getAllChannels(state) {
|
||||
return state.entities.channels.channels;
|
||||
}
|
||||
|
||||
export function getCurrentChannelId(state) {
|
||||
return state.entities.channels.currentId;
|
||||
}
|
||||
|
||||
export const getCurrentChannel = createSelector(
|
||||
getAllChannels,
|
||||
getCurrentChannelId,
|
||||
(allChannels, currentChannelId) => {
|
||||
return allChannels[currentChannelId];
|
||||
}
|
||||
);
|
||||
|
||||
export const getChannelsOnCurrentTeam = createSelector(
|
||||
getAllChannels,
|
||||
getCurrentTeamId,
|
||||
(allChannels, currentTeamId) => {
|
||||
const channels = [];
|
||||
|
||||
for (const channel of Object.values(allChannels)) {
|
||||
if (channel.team_id === currentTeamId) {
|
||||
channels.push(channel);
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
);
|
||||
20
service/selectors/entities/posts.js
Normal file
20
service/selectors/entities/posts.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
function getPosts(state) {
|
||||
return state.entities.posts.posts;
|
||||
}
|
||||
|
||||
function getPostIdsInCurrentChannel(state) {
|
||||
return state.entities.posts.postsByChannel[state.entities.channels.currentId] || [];
|
||||
}
|
||||
|
||||
export const getPostsInCurrentChannel = createSelector(
|
||||
getPosts,
|
||||
getPostIdsInCurrentChannel,
|
||||
(posts, postIds) => {
|
||||
return postIds.map((id) => posts[id]);
|
||||
}
|
||||
);
|
||||
21
service/selectors/entities/teams.js
Normal file
21
service/selectors/entities/teams.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
export function getCurrentTeamId(state) {
|
||||
return state.entities.teams.currentId;
|
||||
}
|
||||
|
||||
export function getTeams(state) {
|
||||
return state.entities.teams.teams;
|
||||
}
|
||||
|
||||
export const getCurrentTeam = createSelector(
|
||||
getTeams,
|
||||
getCurrentTeamId,
|
||||
(teams, currentTeamId) => {
|
||||
return teams[currentTeamId];
|
||||
}
|
||||
);
|
||||
|
||||
6
service/selectors/entities/users.js
Normal file
6
service/selectors/entities/users.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
export function getUser(state, id) {
|
||||
return state.entities.users.profiles[id];
|
||||
}
|
||||
8
service/utils/post_utils.js
Normal file
8
service/utils/post_utils.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {Constants} from 'service/constants';
|
||||
|
||||
export function isSystemMessage(post) {
|
||||
return post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
|
||||
}
|
||||
|
|
@ -25,6 +25,15 @@ describe('Actions.Channels', () => {
|
|||
await TestHelper.basicClient.logout();
|
||||
});
|
||||
|
||||
it('selectChannel', async () => {
|
||||
const channelId = TestHelper.generateId();
|
||||
|
||||
await Actions.selectChannel(channelId)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
assert.equal(state.entities.channels.currentId, channelId);
|
||||
});
|
||||
|
||||
it('createChannel', async () => {
|
||||
const channel = {
|
||||
team_id: TestHelper.basicTeam.id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue