summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/admin_console/admin_navbar_dropdown.jsx4
-rw-r--r--webapp/components/admin_console/admin_team_members_dropdown.jsx (renamed from webapp/components/admin_console/user_item.jsx)126
-rw-r--r--webapp/components/admin_console/team_users.jsx255
-rw-r--r--webapp/components/analytics/system_analytics.jsx37
-rw-r--r--webapp/components/channel_header.jsx33
-rw-r--r--webapp/components/channel_invite_button.jsx10
-rw-r--r--webapp/components/channel_invite_modal.jsx166
-rw-r--r--webapp/components/channel_members_modal.jsx144
-rw-r--r--webapp/components/channel_notifications_modal.jsx10
-rw-r--r--webapp/components/channel_switch_modal.jsx32
-rw-r--r--webapp/components/edit_post_modal.jsx25
-rw-r--r--webapp/components/emoji/components/emoji_list.jsx29
-rw-r--r--webapp/components/emoji/components/emoji_list_item.jsx11
-rw-r--r--webapp/components/integrations/components/installed_command.jsx8
-rw-r--r--webapp/components/integrations/components/installed_commands.jsx26
-rw-r--r--webapp/components/integrations/components/installed_incoming_webhook.jsx5
-rw-r--r--webapp/components/integrations/components/installed_incoming_webhooks.jsx28
-rw-r--r--webapp/components/integrations/components/installed_outgoing_webhook.jsx6
-rw-r--r--webapp/components/integrations/components/installed_outgoing_webhooks.jsx26
-rw-r--r--webapp/components/logged_in.jsx11
-rw-r--r--webapp/components/member_list_team.jsx118
-rw-r--r--webapp/components/more_direct_channels.jsx201
-rw-r--r--webapp/components/navbar.jsx8
-rw-r--r--webapp/components/needs_team.jsx4
-rw-r--r--webapp/components/notify_counts.jsx2
-rw-r--r--webapp/components/popover_list_members.jsx52
-rw-r--r--webapp/components/post_view/components/pending_post_options.jsx11
-rw-r--r--webapp/components/post_view/components/post_list.jsx10
-rw-r--r--webapp/components/post_view/post_focus_view_controller.jsx12
-rw-r--r--webapp/components/post_view/post_view_controller.jsx21
-rw-r--r--webapp/components/rhs_root_post.jsx6
-rw-r--r--webapp/components/rhs_thread.jsx8
-rw-r--r--webapp/components/searchable_user_list.jsx226
-rw-r--r--webapp/components/select_team/select_team.jsx2
-rw-r--r--webapp/components/sidebar.jsx11
-rw-r--r--webapp/components/sidebar_header_dropdown.jsx4
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx134
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx76
-rw-r--r--webapp/components/suggestion/suggestion_list.jsx2
-rw-r--r--webapp/components/suggestion/switch_channel_provider.jsx8
-rw-r--r--webapp/components/team_members_dropdown.jsx56
-rw-r--r--webapp/components/textbox.jsx2
-rw-r--r--webapp/components/user_list.jsx24
-rw-r--r--webapp/components/user_list_row.jsx39
44 files changed, 1218 insertions, 811 deletions
diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx
index 7b958cbb0..f20451b4b 100644
--- a/webapp/components/admin_console/admin_navbar_dropdown.jsx
+++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx
@@ -22,7 +22,7 @@ export default class AdminNavbarDropdown extends React.Component {
this.state = {
teams: TeamStore.getAll(),
- teamMembers: TeamStore.getTeamMembers()
+ teamMembers: TeamStore.getMyTeamMembers()
};
}
@@ -45,7 +45,7 @@ export default class AdminNavbarDropdown extends React.Component {
onTeamChange() {
this.setState({
teams: TeamStore.getAll(),
- teamMembers: TeamStore.getTeamMembers()
+ teamMembers: TeamStore.getMyTeamMembers()
});
}
diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/admin_team_members_dropdown.jsx
index ac548afe0..85daa86ba 100644
--- a/webapp/components/admin_console/user_item.jsx
+++ b/webapp/components/admin_console/admin_team_members_dropdown.jsx
@@ -8,11 +8,11 @@ import UserStore from 'stores/user_store.jsx';
import ConfirmModal from '../confirm_modal.jsx';
import TeamStore from 'stores/team_store.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {FormattedMessage} from 'react-intl';
import React from 'react';
-export default class UserItem extends React.Component {
+export default class AdminTeamMembersDropdown extends React.Component {
constructor(props) {
super(props);
@@ -50,7 +50,7 @@ export default class UserItem extends React.Component {
}
);
Client.updateTeamMemberRoles(
- this.props.team.id,
+ this.props.teamMember.team_id,
this.props.user.id,
'team_user',
() => {
@@ -74,7 +74,7 @@ export default class UserItem extends React.Component {
handleRemoveFromTeam() {
Client.removeUserFromTeam(
- this.props.team.id,
+ this.props.teamMember.team_id,
this.props.user.id,
() => {
this.props.refreshProfiles();
@@ -111,7 +111,7 @@ export default class UserItem extends React.Component {
doMakeTeamAdmin() {
Client.updateTeamMemberRoles(
- this.props.team.id,
+ this.props.teamMember.team_id,
this.props.user.id,
'team_user team_admin',
() => {
@@ -241,7 +241,6 @@ export default class UserItem extends React.Component {
}
const me = UserStore.getCurrentUser();
- const email = user.email;
let showMakeMember = Utils.isAdmin(teamMember.roles) || Utils.isSystemAdmin(user.roles);
let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
let showMakeSystemAdmin = !Utils.isSystemAdmin(user.roles);
@@ -406,39 +405,8 @@ export default class UserItem extends React.Component {
);
}
- let mfaActiveText;
- if (mfaEnabled) {
- if (user.mfa_active) {
- mfaActiveText = (
- <FormattedHTMLMessage
- id='admin.user_item.mfaYes'
- defaultMessage=', <strong>MFA</strong>: Yes'
- />
- );
- } else {
- mfaActiveText = (
- <FormattedHTMLMessage
- id='admin.user_item.mfaNo'
- defaultMessage=', <strong>MFA</strong>: No'
- />
- );
- }
- }
-
- let authServiceText;
let passwordReset;
if (user.auth_service) {
- const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
- authServiceText = (
- <FormattedHTMLMessage
- id='admin.user_item.authServiceNotEmail'
- defaultMessage=', <strong>Sign-in Method:</strong> {service}'
- values={{
- service
- }}
- />
- );
-
passwordReset = (
<li role='presentation'>
<a
@@ -454,13 +422,6 @@ export default class UserItem extends React.Component {
</li>
);
} else {
- authServiceText = (
- <FormattedHTMLMessage
- id='admin.user_item.authServiceEmail'
- defaultMessage=', <strong>Sign-in Method:</strong> Email'
- />
- );
-
passwordReset = (
<li role='presentation'>
<a
@@ -531,63 +492,38 @@ export default class UserItem extends React.Component {
}
return (
- <div className='more-modal__row'>
- <img
- className='more-modal__image pull-left'
- src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
- height='36'
- width='36'
- />
- <div className='more-modal__details'>
- <div className='more-modal__name'>{displayedName}</div>
- <div className='more-modal__description'>
- <FormattedHTMLMessage
- id='admin.user_item.emailTitle'
- defaultMessage='<strong>Email:</strong> {email}'
- values={{
- email
- }}
- />
- {authServiceText}
- {mfaActiveText}
- </div>
- {serverError}
- </div>
- <div className='more-modal__actions'>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'/>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- >
- {removeFromTeam}
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- {makeSystemAdmin}
- {mfaReset}
- {passwordReset}
- </ul>
- </div>
- </div>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'/>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ >
+ {removeFromTeam}
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ {makeSystemAdmin}
+ {mfaReset}
+ {passwordReset}
+ </ul>
{makeDemoteModal}
+ {serverError}
</div>
);
}
}
-UserItem.propTypes = {
- team: React.PropTypes.object.isRequired,
+AdminTeamMembersDropdown.propTypes = {
user: React.PropTypes.object.isRequired,
teamMember: React.PropTypes.object.isRequired,
refreshProfiles: React.PropTypes.func.isRequired,
diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx
index 56b76c195..8fa73b084 100644
--- a/webapp/components/admin_console/team_users.jsx
+++ b/webapp/components/admin_console/team_users.jsx
@@ -1,16 +1,25 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import AdminStore from 'stores/admin_store.jsx';
-import Client from 'client/web_client.jsx';
-import FormError from 'components/form_error.jsx';
-import LoadingScreen from '../loading_screen.jsx';
-import UserItem from './user_item.jsx';
+import SearchableUserList from 'components/searchable_user_list.jsx';
+import AdminTeamMembersDropdown from './admin_team_members_dropdown.jsx';
import ResetPasswordModal from './reset_password_modal.jsx';
+import FormError from 'components/form_error.jsx';
+
+import AdminStore from 'stores/admin_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
+import {getTeamStats} from 'utils/async_client.jsx';
-import {FormattedMessage} from 'react-intl';
+import Constants from 'utils/constants.jsx';
+import * as Utils from 'utils/utils.jsx';
import React from 'react';
+import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+
+const USERS_PER_PAGE = 50;
export default class UserList extends React.Component {
static get propTypes() {
@@ -23,34 +32,49 @@ export default class UserList extends React.Component {
super(props);
this.onAllTeamsChange = this.onAllTeamsChange.bind(this);
+ this.onStatsChange = this.onStatsChange.bind(this);
+ this.onUsersChange = this.onUsersChange.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
- this.getTeamProfiles = this.getTeamProfiles.bind(this);
- this.getCurrentTeamProfiles = this.getCurrentTeamProfiles.bind(this);
this.doPasswordReset = this.doPasswordReset.bind(this);
this.doPasswordResetDismiss = this.doPasswordResetDismiss.bind(this);
this.doPasswordResetSubmit = this.doPasswordResetSubmit.bind(this);
- this.getTeamMemberForUser = this.getTeamMemberForUser.bind(this);
+ this.nextPage = this.nextPage.bind(this);
+ this.search = this.search.bind(this);
+ this.loadComplete = this.loadComplete.bind(this);
+
+ const stats = TeamStore.getStats(this.props.params.team);
this.state = {
team: AdminStore.getTeam(this.props.params.team),
- users: null,
- teamMembers: null,
+ users: [],
+ teamMembers: TeamStore.getMembersInTeam(this.props.params.team),
+ total: stats.member_count,
serverError: null,
showPasswordModal: false,
+ loading: true,
user: null
};
}
componentDidMount() {
- this.getCurrentTeamProfiles();
-
AdminStore.addAllTeamsChangeListener(this.onAllTeamsChange);
+ UserStore.addInTeamChangeListener(this.onUsersChange);
+ TeamStore.addChangeListener(this.onTeamChange);
+ TeamStore.addStatsChangeListener(this.onStatsChange);
+
+ loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, this.props.params.team, this.loadComplete);
+ getTeamStats(this.props.params.team);
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.team !== this.props.params.team) {
+ const stats = TeamStore.getStats(nextProps.params.team);
this.setState({
- team: AdminStore.getTeam(nextProps.params.team)
+ team: AdminStore.getTeam(nextProps.params.team),
+ users: [],
+ teamMembers: TeamStore.getMembersInTeam(nextProps.params.team),
+ total: stats.member_count
});
this.getTeamProfiles(nextProps.params.team);
@@ -59,6 +83,13 @@ export default class UserList extends React.Component {
componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.onAllTeamsChange);
+ UserStore.removeInTeamChangeListener(this.onUsersChange);
+ TeamStore.removeChangeListener(this.onTeamChange);
+ TeamStore.removeStatsChangeListener(this.onStatsChange);
+ }
+
+ loadComplete() {
+ this.setState({loading: false});
}
onAllTeamsChange() {
@@ -67,59 +98,21 @@ export default class UserList extends React.Component {
});
}
- getCurrentTeamProfiles() {
- this.getTeamProfiles(this.props.params.team);
+ onStatsChange() {
+ const stats = TeamStore.getStats(this.props.params.team);
+ this.setState({total: stats.member_count});
}
- getTeamProfiles(teamId) {
- Client.getTeamMembers(
- teamId,
- (data) => {
- this.setState({
- teamMembers: data
- });
- },
- (err) => {
- this.setState({
- teamMembers: null,
- serverError: err.message
- });
- }
- );
-
- Client.getProfilesForTeam(
- teamId,
- (users) => {
- var memberList = [];
- for (var id in users) {
- if (users.hasOwnProperty(id)) {
- memberList.push(users[id]);
- }
- }
-
- memberList.sort((a, b) => {
- if (a.username < b.username) {
- return -1;
- }
+ onUsersChange() {
+ this.setState({users: UserStore.getProfileListInTeam(this.props.params.team)});
+ }
- if (a.username > b.username) {
- return 1;
- }
+ onTeamChange() {
+ this.setState({teamMembers: TeamStore.getMembersInTeam(this.props.params.team)});
+ }
- return 0;
- });
-
- this.setState({
- users: memberList
- });
- },
- (err) => {
- this.setState({
- users: null,
- serverError: err.message
- });
- }
- );
+ nextPage(page) {
+ loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.props.params.team);
}
doPasswordReset(user) {
@@ -144,20 +137,21 @@ export default class UserList extends React.Component {
});
}
- getTeamMemberForUser(userId) {
- if (this.state.teamMembers) {
- for (const index in this.state.teamMembers) {
- if (this.state.teamMembers.hasOwnProperty(index)) {
- var teamMember = this.state.teamMembers[index];
-
- if (teamMember.user_id === userId) {
- return teamMember;
- }
- }
- }
+ search(term) {
+ if (term === '') {
+ this.setState({search: false, users: UserStore.getProfileListInTeam(this.props.params.team)});
+ return;
}
- return null;
+ searchUsers(
+ term,
+ this.props.params.team,
+ {},
+ (users) => {
+ this.setState({loading: true, search: true, users});
+ loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);
+ }
+ );
}
render() {
@@ -165,41 +159,71 @@ export default class UserList extends React.Component {
return null;
}
- if (this.state.users == null || this.state.teamMembers == null) {
- return (
- <div className='wrapper--fixed'>
- <h3>
- <FormattedMessage
- id='admin.userList.title'
- defaultMessage='Users for {team}'
- values={{
- team: this.state.team.name
- }}
- />
- </h3>
- <FormError error={this.state.serverError}/>
- <LoadingScreen/>
- </div>
- );
- }
+ const teamMembers = this.state.teamMembers;
+ const users = this.state.users;
+ const actionUserProps = {};
+ const extraInfo = {};
+ const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
+
+ let usersToDisplay;
+ if (this.state.loading) {
+ usersToDisplay = null;
+ } else {
+ usersToDisplay = [];
+
+ for (let i = 0; i < users.length; i++) {
+ const user = users[i];
+
+ if (teamMembers[user.id]) {
+ usersToDisplay.push(user);
+ actionUserProps[user.id] = {
+ teamMember: teamMembers[user.id]
+ };
+
+ const info = [];
+
+ if (user.auth_service) {
+ const service = (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) ? user.auth_service.toUpperCase() : Utils.toTitleCase(user.auth_service);
+ info.push(
+ <FormattedHTMLMessage
+ id='admin.user_item.authServiceNotEmail'
+ defaultMessage='<strong>Sign-in Method:</strong> {service}'
+ values={{
+ service
+ }}
+ />
+ );
+ } else {
+ info.push(
+ <FormattedHTMLMessage
+ id='admin.user_item.authServiceEmail'
+ defaultMessage='<strong>Sign-in Method:</strong> Email'
+ />
+ );
+ }
- var memberList = this.state.users.map((user) => {
- var teamMember = this.getTeamMemberForUser(user.id);
+ if (mfaEnabled) {
+ if (user.mfa_active) {
+ info.push(
+ <FormattedHTMLMessage
+ id='admin.user_item.mfaYes'
+ defaultMessage='<strong>MFA</strong>: Yes'
+ />
+ );
+ } else {
+ info.push(
+ <FormattedHTMLMessage
+ id='admin.user_item.mfaNo'
+ defaultMessage='<strong>MFA</strong>: No'
+ />
+ );
+ }
+ }
- if (!teamMember || teamMember.delete_at > 0) {
- return null;
+ extraInfo[user.id] = info;
+ }
}
-
- return (
- <UserItem
- team={this.state.team}
- key={'user_' + user.id}
- user={user}
- teamMember={teamMember}
- refreshProfiles={this.getCurrentTeamProfiles}
- doPasswordReset={this.doPasswordReset}
- />);
- });
+ }
return (
<div className='wrapper--fixed'>
@@ -209,7 +233,7 @@ export default class UserList extends React.Component {
defaultMessage='Users for {team} ({count})'
values={{
team: this.state.team.name,
- count: this.state.users.length
+ count: this.state.total
}}
/>
</h3>
@@ -219,7 +243,20 @@ export default class UserList extends React.Component {
role='form'
>
<div className='more-modal__list member-list-holder'>
- {memberList}
+ <SearchableUserList
+ users={usersToDisplay}
+ usersPerPage={USERS_PER_PAGE}
+ total={this.state.total}
+ extraInfo={extraInfo}
+ nextPage={this.nextPage}
+ search={this.search}
+ actions={[AdminTeamMembersDropdown]}
+ actionProps={{
+ refreshProfiles: this.getCurrentTeamProfiles,
+ doPasswordReset: this.doPasswordReset
+ }}
+ actionUserProps={actionUserProps}
+ />
</div>
</form>
<ResetPasswordModal
diff --git a/webapp/components/analytics/system_analytics.jsx b/webapp/components/analytics/system_analytics.jsx
index 5bd8b1d28..2b4b5b48f 100644
--- a/webapp/components/analytics/system_analytics.jsx
+++ b/webapp/components/analytics/system_analytics.jsx
@@ -82,6 +82,7 @@ class SystemAnalytics extends React.Component {
const stats = this.state.stats;
let advancedCounts;
+ let advancedStats;
let advancedGraphs;
let banner;
if (global.window.mm_license.IsLicensed === 'true') {
@@ -130,6 +131,41 @@ class SystemAnalytics extends React.Component {
</div>
);
+ advancedStats = (
+ <div className='row'>
+ <StatisticCount
+ title={
+ <FormattedMessage
+ id='analytics.system.totalWebsockets'
+ defaultMessage='Websocket Conns'
+ />
+ }
+ icon='fa-user'
+ count={stats[StatTypes.TOTAL_WEBSOCKET_CONNECTIONS]}
+ />
+ <StatisticCount
+ title={
+ <FormattedMessage
+ id='analytics.system.totalMasterDbConnections'
+ defaultMessage='Master DB Conns'
+ />
+ }
+ icon='fa-terminal'
+ count={stats[StatTypes.TOTAL_MASTER_DB_CONNECTIONS]}
+ />
+ <StatisticCount
+ title={
+ <FormattedMessage
+ id='analytics.system.totalReadDbConnections'
+ defaultMessage='Replica DB Conns'
+ />
+ }
+ icon='fa-terminal'
+ count={stats[StatTypes.TOTAL_READ_DB_CONNECTIONS]}
+ />
+ </div>
+ );
+
const channelTypeData = formatChannelDoughtnutData(stats[StatTypes.TOTAL_PUBLIC_CHANNELS], stats[StatTypes.TOTAL_PRIVATE_GROUPS], this.props.intl);
const postTypeData = formatPostDoughtnutData(stats[StatTypes.TOTAL_FILE_POSTS], stats[StatTypes.TOTAL_HASHTAG_POSTS], stats[StatTypes.TOTAL_POSTS], this.props.intl);
@@ -246,6 +282,7 @@ class SystemAnalytics extends React.Component {
/>
</div>
{advancedCounts}
+ {advancedStats}
{advancedGraphs}
<div className='row'>
<LineChart
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index bd57271ed..1a8625cd2 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -63,13 +63,15 @@ export default class ChannelHeader extends React.Component {
}
getStateFromStores() {
- const extraInfo = ChannelStore.getExtraInfo(this.props.channelId);
+ const stats = ChannelStore.getStats(this.props.channelId);
+
+ const users = UserStore.getProfileListInChannel(this.props.channelId);
return {
channel: ChannelStore.get(this.props.channelId),
- memberChannel: ChannelStore.getMember(this.props.channelId),
- users: extraInfo.members,
- userCount: extraInfo.member_count,
+ memberChannel: ChannelStore.getMyMember(this.props.channelId),
+ users,
+ userCount: stats.member_count,
currentUser: UserStore.getCurrentUser(),
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
isBusy: WebrtcStore.isBusy()
@@ -89,10 +91,10 @@ export default class ChannelHeader extends React.Component {
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
- ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.addStatsChangeListener(this.onListenerChange);
SearchStore.addSearchChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
+ UserStore.addInChannelChangeListener(this.onListenerChange);
UserStore.addStatusesChangeListener(this.onListenerChange);
WebrtcStore.addChangedListener(this.onListenerChange);
WebrtcStore.addBusyListener(this.onBusy);
@@ -102,10 +104,10 @@ export default class ChannelHeader extends React.Component {
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
- ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.removeStatsChangeListener(this.onListenerChange);
SearchStore.removeSearchChangeListener(this.onListenerChange);
PreferenceStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeInChannelChangeListener(this.onListenerChange);
UserStore.removeStatusesChangeListener(this.onListenerChange);
WebrtcStore.removeChangedListener(this.onListenerChange);
WebrtcStore.removeBusyListener(this.onBusy);
@@ -117,10 +119,7 @@ export default class ChannelHeader extends React.Component {
}
onListenerChange() {
- const newState = this.getStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ this.setState(this.getStateFromStores());
}
handleLeave() {
@@ -265,7 +264,6 @@ export default class ChannelHeader extends React.Component {
</Popover>
);
let channelTitle = channel.display_name;
- const currentId = this.state.currentUser.id;
const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
const isDirect = (this.state.channel.type === 'D');
@@ -273,13 +271,8 @@ export default class ChannelHeader extends React.Component {
if (isDirect) {
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- let contact;
- if (this.state.users.length > 1) {
- if (this.state.users[0].id === currentId) {
- contact = this.state.users[1];
- } else {
- contact = this.state.users[0];
- }
+ const contact = this.state.users[0];
+ if (contact) {
channelTitle = Utils.displayUsername(contact.id);
}
diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx
index 59eda8e41..290c2bea4 100644
--- a/webapp/components/channel_invite_button.jsx
+++ b/webapp/components/channel_invite_button.jsx
@@ -1,13 +1,12 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import SpinnerButton from 'components/spinner_button.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
-import Client from 'client/web_client.jsx';
+import {addUserToChannel} from 'actions/channel_actions.jsx';
+import React from 'react';
import {FormattedMessage} from 'react-intl';
-import SpinnerButton from 'components/spinner_button.jsx';
export default class ChannelInviteButton extends React.Component {
static get propTypes() {
@@ -37,7 +36,7 @@ export default class ChannelInviteButton extends React.Component {
addingUser: true
});
- Client.addChannelMember(
+ addUserToChannel(
this.props.channel.id,
this.props.user.id,
() => {
@@ -46,7 +45,6 @@ export default class ChannelInviteButton extends React.Component {
});
this.props.onInviteError(null);
- AsyncClient.getChannelExtraInfo();
},
(err) => {
this.setState({
diff --git a/webapp/components/channel_invite_modal.jsx b/webapp/components/channel_invite_modal.jsx
index c7c1906a5..99a4b9313 100644
--- a/webapp/components/channel_invite_modal.jsx
+++ b/webapp/components/channel_invite_modal.jsx
@@ -1,124 +1,85 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
import ChannelInviteButton from './channel_invite_button.jsx';
-import FilteredUserList from './filtered_user_list.jsx';
+import SearchableUserList from './searchable_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+
+import {searchUsers} from 'actions/user_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import {FormattedMessage} from 'react-intl';
-
+import React from 'react';
import {Modal} from 'react-bootstrap';
+import {FormattedMessage} from 'react-intl';
-import React from 'react';
+const USERS_PER_PAGE = 50;
export default class ChannelInviteModal extends React.Component {
constructor(props) {
super(props);
- this.onListenerChange = this.onListenerChange.bind(this);
- this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onChange = this.onChange.bind(this);
this.handleInviteError = this.handleInviteError.bind(this);
+ this.nextPage = this.nextPage.bind(this);
+ this.search = this.search.bind(this);
- this.state = this.getStateFromStores();
- }
- shouldComponentUpdate(nextProps, nextState) {
- if (!this.props.show && !nextProps.show) {
- return false;
- }
-
- if (!Utils.areObjectsEqual(this.props, nextProps)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(this.state, nextState)) {
- return true;
- }
-
- return false;
- }
- getStateFromStores() {
- const users = UserStore.getActiveOnlyProfiles();
-
- if ($.isEmptyObject(users)) {
- return {
- loading: true
- };
- }
-
- // make sure we have all members of this channel before rendering
- const extraInfo = ChannelStore.getCurrentExtraInfo();
- if (extraInfo.member_count !== extraInfo.members.length) {
- AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
-
- return {
- loading: true
- };
- }
-
- const currentUser = UserStore.getCurrentUser();
- if (!currentUser) {
- return {
- loading: true
- };
- }
-
- const currentMember = ChannelStore.getCurrentMember();
- if (!currentMember) {
- return {
- loading: true
- };
- }
+ this.term = '';
- const memberIds = extraInfo.members.map((user) => user.id);
+ const channelStats = ChannelStore.getStats(props.channel.id);
+ const teamStats = TeamStore.getCurrentStats();
- var nonmembers = [];
- for (var id in users) {
- if (memberIds.indexOf(id) === -1) {
- nonmembers.push(users[id]);
- }
- }
-
- nonmembers.sort((a, b) => {
- return a.username.localeCompare(b.username);
- });
-
- return {
- nonmembers,
- loading: false,
- currentUser,
- currentMember
+ this.state = {
+ users: [],
+ total: teamStats.member_count - channelStats.member_count,
+ search: false
};
}
+
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
- ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.addChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
- this.onListenerChange();
+ TeamStore.addStatsChangeListener(this.onChange);
+ ChannelStore.addStatsChangeListener(this.onChange);
+ UserStore.addNotInChannelChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
+
+ this.onChange();
+ AsyncClient.getProfilesNotInChannel(this.props.channel.id, 0);
+ AsyncClient.getTeamStats(TeamStore.getCurrentId());
} else if (this.props.show && !nextProps.show) {
- ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
+ TeamStore.removeStatsChangeListener(this.onChange);
+ ChannelStore.removeStatsChangeListener(this.onChange);
+ UserStore.removeNotInChannelChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
}
}
+
componentWillUnmount() {
- ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
+ ChannelStore.removeStatsChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ UserStore.removeNotInChannelChangeListener(this.onChange);
}
- onListenerChange() {
- var newState = this.getStateFromStores();
- if (!Utils.areObjectsEqual(this.state, newState)) {
- this.setState(newState);
+
+ onChange() {
+ if (this.state.search) {
+ this.search(this.term);
+ return;
}
+
+ const channelStats = ChannelStore.getStats(this.props.channel.id);
+ const teamStats = TeamStore.getCurrentStats();
+
+ this.setState({
+ users: UserStore.getProfileListNotInChannel(this.props.channel.id),
+ total: teamStats.member_count - channelStats.member_count
+ });
}
+
handleInviteError(err) {
if (err) {
this.setState({
@@ -130,6 +91,29 @@ export default class ChannelInviteModal extends React.Component {
});
}
}
+
+ nextPage(page) {
+ AsyncClient.getProfilesNotInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ }
+
+ search(term) {
+ this.term = term;
+
+ if (term === '') {
+ this.setState({users: UserStore.getProfileListNotInChannel(), search: false});
+ return;
+ }
+
+ searchUsers(
+ term,
+ TeamStore.getCurrentId(),
+ {not_in_channel: this.props.channel.id},
+ (users) => {
+ this.setState({search: true, users});
+ }
+ );
+ }
+
render() {
var inviteError = null;
if (this.state.inviteError) {
@@ -145,9 +129,13 @@ export default class ChannelInviteModal extends React.Component {
maxHeight = Utils.windowHeight() - 300;
}
content = (
- <FilteredUserList
+ <SearchableUserList
style={{maxHeight}}
- users={this.state.nonmembers}
+ users={this.state.users}
+ usersPerPage={USERS_PER_PAGE}
+ total={this.state.total}
+ nextPage={this.nextPage}
+ search={this.search}
actions={[ChannelInviteButton]}
actionProps={{
channel: this.props.channel,
diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx
index d20c00623..511209b42 100644
--- a/webapp/components/channel_members_modal.jsx
+++ b/webapp/components/channel_members_modal.jsx
@@ -1,122 +1,89 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import FilteredUserList from './filtered_user_list.jsx';
+import SearchableUserList from './searchable_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+
+import {searchUsers} from 'actions/user_actions.jsx';
+import {removeUserFromChannel} from 'actions/channel_actions.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import {FormattedMessage} from 'react-intl';
-
+import React from 'react';
import {Modal} from 'react-bootstrap';
+import {FormattedMessage} from 'react-intl';
-import React from 'react';
+const USERS_PER_PAGE = 50;
export default class ChannelMembersModal extends React.Component {
constructor(props) {
super(props);
- this.getStateFromStores = this.getStateFromStores.bind(this);
this.onChange = this.onChange.bind(this);
this.handleRemove = this.handleRemove.bind(this);
-
this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
+ this.search = this.search.bind(this);
+ this.nextPage = this.nextPage.bind(this);
- // the rest of the state gets populated when the modal is shown
- this.state = {
- showInviteModal: false
- };
- }
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(this.props, nextProps)) {
- return true;
- }
+ this.term = '';
- if (!Utils.areObjectsEqual(this.state, nextState)) {
- return true;
- }
-
- return false;
- }
- getStateFromStores() {
- const extraInfo = ChannelStore.getCurrentExtraInfo();
- const profiles = UserStore.getActiveOnlyProfiles();
-
- if (extraInfo.member_count !== extraInfo.members.length) {
- AsyncClient.getChannelExtraInfo(this.props.channel.id, -1);
-
- return {
- loading: true
- };
- }
-
- const memberList = extraInfo.members.map((member) => {
- return profiles[member.id];
- });
-
- function compareByUsername(a, b) {
- if (a.username < b.username) {
- return -1;
- } else if (a.username > b.username) {
- return 1;
- }
+ const stats = ChannelStore.getStats(props.channel.id);
- return 0;
- }
-
- memberList.sort(compareByUsername);
-
- return {
- memberList,
- loading: false
+ this.state = {
+ users: [],
+ total: stats.member_count,
+ showInviteModal: false,
+ search: false
};
}
+
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
- ChannelStore.addExtraInfoChangeListener(this.onChange);
- ChannelStore.addChangeListener(this.onChange);
+ ChannelStore.addStatsChangeListener(this.onChange);
+ UserStore.addInChannelChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
this.onChange();
+ AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
} else if (this.props.show && !nextProps.show) {
- ChannelStore.removeExtraInfoChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
+ ChannelStore.removeStatsChangeListener(this.onChange);
+ UserStore.removeInChannelChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
}
}
+
onChange() {
- const newState = this.getStateFromStores();
- if (!Utils.areObjectsEqual(this.state, newState)) {
- this.setState(newState);
+ if (this.state.search) {
+ this.search(this.term);
+ return;
}
+
+ const stats = ChannelStore.getStats(this.props.channel.id);
+ this.setState({
+ users: UserStore.getProfileListInChannel(this.props.channel.id),
+ total: stats.member_count
+ });
}
+
handleRemove(user) {
const userId = user.id;
- Client.removeChannelMember(
- ChannelStore.getCurrentId(),
+ removeUserFromChannel(
+ this.props.channel.id,
userId,
- () => {
- const memberList = this.state.memberList.slice();
- for (let i = 0; i < memberList.length; i++) {
- if (userId === memberList[i].id) {
- memberList.splice(i, 1);
- break;
- }
- }
-
- this.setState({memberList});
- AsyncClient.getChannelExtraInfo();
- },
+ null,
(err) => {
this.setState({inviteError: err.message});
}
);
}
+
createRemoveMemberButton({user}) {
if (user.id === UserStore.getCurrentId()) {
return null;
@@ -135,6 +102,29 @@ export default class ChannelMembersModal extends React.Component {
</button>
);
}
+
+ nextPage(page) {
+ AsyncClient.getProfilesInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ }
+
+ search(term) {
+ this.term = term;
+
+ if (term === '') {
+ this.setState({users: UserStore.getProfileListInChannel(this.props.channel.id), search: false});
+ return;
+ }
+
+ searchUsers(
+ term,
+ TeamStore.getCurrentId(),
+ {in_channel: this.props.channel.id},
+ (users) => {
+ this.setState({search: true, users});
+ }
+ );
+ }
+
render() {
let content;
if (this.state.loading) {
@@ -151,9 +141,13 @@ export default class ChannelMembersModal extends React.Component {
}
content = (
- <FilteredUserList
+ <SearchableUserList
style={{maxHeight}}
- users={this.state.memberList}
+ users={this.state.users}
+ usersPerPage={USERS_PER_PAGE}
+ total={this.state.total}
+ nextPage={this.nextPage}
+ search={this.search}
actions={removeButton}
/>
);
diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx
index 35a2e4087..91563a096 100644
--- a/webapp/components/channel_notifications_modal.jsx
+++ b/webapp/components/channel_notifications_modal.jsx
@@ -65,9 +65,9 @@ export default class ChannelNotificationsModal extends React.Component {
Client.updateChannelNotifyProps(data,
() => {
// YUCK
- var member = ChannelStore.getMember(channelId);
+ var member = ChannelStore.getMyMember(channelId);
member.notify_props.desktop = notifyLevel;
- ChannelStore.setChannelMember(member);
+ ChannelStore.storeMyChannelMember(member);
this.updateSection('');
},
(err) => {
@@ -256,13 +256,13 @@ export default class ChannelNotificationsModal extends React.Component {
mark_unread: markUnreadLevel
};
- //TODO: This should be fixed, moved to event_helpers
+ //TODO: This should be fixed, moved to actions
Client.updateChannelNotifyProps(data,
() => {
// Yuck...
- var member = ChannelStore.getMember(channelId);
+ var member = ChannelStore.getMyMember(channelId);
member.notify_props.mark_unread = markUnreadLevel;
- ChannelStore.setChannelMember(member);
+ ChannelStore.storeMyChannelMember(member);
this.updateSection('');
},
(err) => {
diff --git a/webapp/components/channel_switch_modal.jsx b/webapp/components/channel_switch_modal.jsx
index ec257bab5..7d15a9c45 100644
--- a/webapp/components/channel_switch_modal.jsx
+++ b/webapp/components/channel_switch_modal.jsx
@@ -8,12 +8,13 @@ import SwitchChannelProvider from './suggestion/switch_channel_provider.jsx';
import {FormattedMessage} from 'react-intl';
import {Modal} from 'react-bootstrap';
+import {goToChannel, openDirectChannelToUser} from 'actions/channel_actions.jsx';
+
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
import React from 'react';
import $ from 'jquery';
@@ -27,30 +28,14 @@ export default class SwitchChannelModal extends React.Component {
this.onExited = this.onExited.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
- this.handleDmUserChange = this.handleDmUserChange.bind(this);
this.suggestionProviders = [new SwitchChannelProvider()];
this.state = {
- dmUsers: UserStore.getDirectProfiles(),
text: '',
error: ''
};
}
- componentDidMount() {
- UserStore.addDmListChangeListener(this.handleDmUserChange);
- }
-
- componentWillUnmount() {
- UserStore.removeDmListChangeListener(this.handleDmUserChange);
- }
-
- handleDmUserChange() {
- this.setState({
- dmUsers: UserStore.getDirectProfiles()
- });
- }
-
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
const textbox = this.refs.search.getTextbox();
@@ -97,18 +82,13 @@ export default class SwitchChannelModal extends React.Component {
const name = this.state.text.trim();
let channel = null;
+ // TODO: Replace this hack with something reasonable
if (name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) > 0) {
const dmUsername = name.substr(0, name.indexOf(Utils.localizeMessage('channel_switch_modal.dm', '(Direct Message)')) - 1);
- let user = null;
- for (const id in this.state.dmUsers) {
- if (this.state.dmUsers[id].username === dmUsername) {
- user = this.state.dmUsers[id];
- break;
- }
- }
+ const user = UserStore.getProfileByUsername(dmUsername);
if (user) {
- Utils.openDirectChannelToUser(
+ openDirectChannelToUser(
user,
(ch) => {
channel = ch;
@@ -123,7 +103,7 @@ export default class SwitchChannelModal extends React.Component {
}
if (channel !== null) {
- ChannelActions.goToChannel(channel);
+ goToChannel(channel);
this.onHide();
} else if (this.state.text !== '') {
this.setState({
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index 44050bb12..263fd31c2 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -1,26 +1,27 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import Client from 'client/web_client.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
import Textbox from './textbox.jsx';
+
import BrowserStore from 'stores/browser_store.jsx';
import PostStore from 'stores/post_store.jsx';
import MessageHistoryStore from 'stores/message_history_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-import {FormattedMessage} from 'react-intl';
+import * as GlobalActions from 'actions/global_actions.jsx';
+import {loadPosts} from 'actions/post_actions.jsx';
-var KeyCodes = Constants.KeyCodes;
+import Client from 'client/web_client.jsx';
+import * as UserAgent from 'utils/user_agent.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
+import * as Utils from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
+const KeyCodes = Constants.KeyCodes;
+import $ from 'jquery';
import React from 'react';
+import ReactDOM from 'react-dom';
+import {FormattedMessage} from 'react-intl';
export default class EditPostModal extends React.Component {
constructor(props) {
@@ -77,7 +78,7 @@ export default class EditPostModal extends React.Component {
Client.updatePost(
updatedPost,
() => {
- AsyncClient.getPosts(updatedPost.channel_id);
+ loadPosts(updatedPost.channel_id);
window.scrollTo(0, 0);
},
(err) => {
diff --git a/webapp/components/emoji/components/emoji_list.jsx b/webapp/components/emoji/components/emoji_list.jsx
index 340fc6afc..76c509f12 100644
--- a/webapp/components/emoji/components/emoji_list.jsx
+++ b/webapp/components/emoji/components/emoji_list.jsx
@@ -1,16 +1,20 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import EmojiListItem from './emoji_list_item.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import EmojiStore from 'stores/emoji_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {loadEmoji} from 'actions/emoji_actions.jsx';
+
+import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import {FormattedMessage} from 'react-intl';
-import EmojiListItem from './emoji_list_item.jsx';
+import React from 'react';
import {Link} from 'react-router';
-import LoadingScreen from 'components/loading_screen.jsx';
+import {FormattedMessage} from 'react-intl';
export default class EmojiList extends React.Component {
static get propTypes() {
@@ -24,28 +28,30 @@ export default class EmojiList extends React.Component {
super(props);
this.handleEmojiChange = this.handleEmojiChange.bind(this);
-
+ this.handleUserChange = this.handleUserChange.bind(this);
this.deleteEmoji = this.deleteEmoji.bind(this);
-
this.updateFilter = this.updateFilter.bind(this);
this.state = {
emojis: EmojiStore.getCustomEmojiMap(),
loading: !EmojiStore.hasReceivedCustomEmojis(),
- filter: ''
+ filter: '',
+ users: UserStore.getProfiles()
};
}
componentDidMount() {
EmojiStore.addChangeListener(this.handleEmojiChange);
+ UserStore.addChangeListener(this.handleUserChange);
if (window.mm_config.EnableCustomEmoji === 'true') {
- AsyncClient.listEmoji();
+ loadEmoji();
}
}
componentWillUnmount() {
EmojiStore.removeChangeListener(this.handleEmojiChange);
+ UserStore.removeChangeListener(this.handleUserChange);
}
handleEmojiChange() {
@@ -55,6 +61,10 @@ export default class EmojiList extends React.Component {
});
}
+ handleUserChange() {
+ this.setState({users: UserStore.getProfiles()});
+ }
+
updateFilter(e) {
this.setState({
filter: e.target.value
@@ -98,6 +108,7 @@ export default class EmojiList extends React.Component {
emoji={emoji}
onDelete={onDelete}
filter={filter}
+ creator={this.state.users[emoji.creator_id] || {}}
/>
);
}
diff --git a/webapp/components/emoji/components/emoji_list_item.jsx b/webapp/components/emoji/components/emoji_list_item.jsx
index 0428f0286..dc27f3691 100644
--- a/webapp/components/emoji/components/emoji_list_item.jsx
+++ b/webapp/components/emoji/components/emoji_list_item.jsx
@@ -4,7 +4,7 @@
import React from 'react';
import EmojiStore from 'stores/emoji_store.jsx';
-import UserStore from 'stores/user_store.jsx';
+
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
@@ -14,7 +14,8 @@ export default class EmojiListItem extends React.Component {
return {
emoji: React.PropTypes.object.isRequired,
onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
+ filter: React.PropTypes.string,
+ creator: React.PropTypes.object.isRequired
};
}
@@ -22,10 +23,6 @@ export default class EmojiListItem extends React.Component {
super(props);
this.handleDelete = this.handleDelete.bind(this);
-
- this.state = {
- creator: UserStore.getProfile(this.props.emoji.creator_id)
- };
}
handleDelete(e) {
@@ -57,7 +54,7 @@ export default class EmojiListItem extends React.Component {
render() {
const emoji = this.props.emoji;
- const creator = this.state.creator;
+ const creator = this.props.creator;
const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
if (!this.matchesFilter(emoji, creator, filter)) {
diff --git a/webapp/components/integrations/components/installed_command.jsx b/webapp/components/integrations/components/installed_command.jsx
index 658126f19..f149a21ac 100644
--- a/webapp/components/integrations/components/installed_command.jsx
+++ b/webapp/components/integrations/components/installed_command.jsx
@@ -2,9 +2,6 @@
// See License.txt for license information.
import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
import {FormattedMessage} from 'react-intl';
export default class InstalledCommand extends React.Component {
@@ -13,7 +10,8 @@ export default class InstalledCommand extends React.Component {
command: React.PropTypes.object.isRequired,
onRegenToken: React.PropTypes.func.isRequired,
onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
+ filter: React.PropTypes.string,
+ creator: React.PropTypes.object.isRequired
};
}
@@ -113,7 +111,7 @@ export default class InstalledCommand extends React.Component {
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
- creator: Utils.displayUsername(command.creator_id),
+ creator: this.props.creator.username,
createAt: command.create_at
}}
/>
diff --git a/webapp/components/integrations/components/installed_commands.jsx b/webapp/components/integrations/components/installed_commands.jsx
index f6429c33e..1c5ef9000 100644
--- a/webapp/components/integrations/components/installed_commands.jsx
+++ b/webapp/components/integrations/components/installed_commands.jsx
@@ -1,16 +1,20 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import InstalledCommand from './installed_command.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {loadTeamCommands} from 'actions/integration_actions.jsx';
+
+import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import React from 'react';
import {FormattedMessage} from 'react-intl';
-import InstalledCommand from './installed_command.jsx';
export default class InstalledCommands extends React.Component {
static get propTypes() {
@@ -23,7 +27,7 @@ export default class InstalledCommands extends React.Component {
super(props);
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
+ this.handleUserChange = this.handleUserChange.bind(this);
this.regenCommandToken = this.regenCommandToken.bind(this);
this.deleteCommand = this.deleteCommand.bind(this);
@@ -31,20 +35,23 @@ export default class InstalledCommands extends React.Component {
this.state = {
commands: IntegrationStore.getCommands(teamId),
- loading: !IntegrationStore.hasReceivedCommands(teamId)
+ loading: !IntegrationStore.hasReceivedCommands(teamId),
+ users: UserStore.getProfiles()
};
}
componentDidMount() {
IntegrationStore.addChangeListener(this.handleIntegrationChange);
+ UserStore.addChangeListener(this.handleUserChange);
if (window.mm_config.EnableCommands === 'true') {
- AsyncClient.listTeamCommands();
+ loadTeamCommands();
}
}
componentWillUnmount() {
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ UserStore.removeChangeListener(this.handleUserChange);
}
handleIntegrationChange() {
@@ -56,6 +63,10 @@ export default class InstalledCommands extends React.Component {
});
}
+ handleUserChange() {
+ this.setState({users: UserStore.getProfiles()});
+ }
+
regenCommandToken(command) {
AsyncClient.regenCommandToken(command.id);
}
@@ -72,6 +83,7 @@ export default class InstalledCommands extends React.Component {
command={command}
onRegenToken={this.regenCommandToken}
onDelete={this.deleteCommand}
+ creator={this.state.users[command.creator_id] || {}}
/>
);
});
diff --git a/webapp/components/integrations/components/installed_incoming_webhook.jsx b/webapp/components/integrations/components/installed_incoming_webhook.jsx
index 2b514d5ec..86274c3d6 100644
--- a/webapp/components/integrations/components/installed_incoming_webhook.jsx
+++ b/webapp/components/integrations/components/installed_incoming_webhook.jsx
@@ -13,7 +13,8 @@ export default class InstalledIncomingWebhook extends React.Component {
return {
incomingWebhook: React.PropTypes.object.isRequired,
onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
+ filter: React.PropTypes.string,
+ creator: React.PropTypes.object.isRequired
};
}
@@ -108,7 +109,7 @@ export default class InstalledIncomingWebhook extends React.Component {
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
- creator: Utils.displayUsername(incomingWebhook.user_id),
+ creator: this.props.creator.username,
createAt: incomingWebhook.create_at
}}
/>
diff --git a/webapp/components/integrations/components/installed_incoming_webhooks.jsx b/webapp/components/integrations/components/installed_incoming_webhooks.jsx
index b14d1e3e8..243195b8b 100644
--- a/webapp/components/integrations/components/installed_incoming_webhooks.jsx
+++ b/webapp/components/integrations/components/installed_incoming_webhooks.jsx
@@ -1,16 +1,20 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {loadIncomingHooks} from 'actions/integration_actions.jsx';
+
+import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import React from 'react';
import {FormattedMessage} from 'react-intl';
-import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
export default class InstalledIncomingWebhooks extends React.Component {
static get propTypes() {
@@ -23,27 +27,30 @@ export default class InstalledIncomingWebhooks extends React.Component {
super(props);
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
+ this.handleUserChange = this.handleUserChange.bind(this);
this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
const teamId = TeamStore.getCurrentId();
this.state = {
incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
+ loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId),
+ users: UserStore.getProfiles()
};
}
componentDidMount() {
IntegrationStore.addChangeListener(this.handleIntegrationChange);
+ UserStore.addChangeListener(this.handleUserChange);
if (window.mm_config.EnableIncomingWebhooks === 'true') {
- AsyncClient.listIncomingHooks();
+ loadIncomingHooks();
}
}
componentWillUnmount() {
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ UserStore.removeChangeListener(this.handleUserChange);
}
handleIntegrationChange() {
@@ -55,6 +62,12 @@ export default class InstalledIncomingWebhooks extends React.Component {
});
}
+ handleUserChange() {
+ this.setState({
+ users: UserStore.getProfiles()
+ });
+ }
+
deleteIncomingWebhook(incomingWebhook) {
AsyncClient.deleteIncomingHook(incomingWebhook.id);
}
@@ -66,6 +79,7 @@ export default class InstalledIncomingWebhooks extends React.Component {
key={incomingWebhook.id}
incomingWebhook={incomingWebhook}
onDelete={this.deleteIncomingWebhook}
+ creator={this.state.users[incomingWebhook.user_id] || {}}
/>
);
});
diff --git a/webapp/components/integrations/components/installed_outgoing_webhook.jsx b/webapp/components/integrations/components/installed_outgoing_webhook.jsx
index 664439843..3ff2c01a4 100644
--- a/webapp/components/integrations/components/installed_outgoing_webhook.jsx
+++ b/webapp/components/integrations/components/installed_outgoing_webhook.jsx
@@ -4,7 +4,6 @@
import React from 'react';
import ChannelStore from 'stores/channel_store.jsx';
-import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
@@ -14,7 +13,8 @@ export default class InstalledOutgoingWebhook extends React.Component {
outgoingWebhook: React.PropTypes.object.isRequired,
onRegenToken: React.PropTypes.func.isRequired,
onDelete: React.PropTypes.func.isRequired,
- filter: React.PropTypes.string
+ filter: React.PropTypes.string,
+ creator: React.PropTypes.object.isRequired
};
}
@@ -195,7 +195,7 @@ export default class InstalledOutgoingWebhook extends React.Component {
id='installed_integrations.creation'
defaultMessage='Created by {creator} on {createAt, date, full}'
values={{
- creator: Utils.displayUsername(outgoingWebhook.creator_id),
+ creator: this.props.creator.username,
createAt: outgoingWebhook.create_at
}}
/>
diff --git a/webapp/components/integrations/components/installed_outgoing_webhooks.jsx b/webapp/components/integrations/components/installed_outgoing_webhooks.jsx
index 214e60a48..21176f8b7 100644
--- a/webapp/components/integrations/components/installed_outgoing_webhooks.jsx
+++ b/webapp/components/integrations/components/installed_outgoing_webhooks.jsx
@@ -1,16 +1,20 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import IntegrationStore from 'stores/integration_store.jsx';
import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {loadOutgoingHooks} from 'actions/integration_actions.jsx';
+
import * as Utils from 'utils/utils.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
+import React from 'react';
import {FormattedMessage} from 'react-intl';
-import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
export default class InstalledOutgoingWebhooks extends React.Component {
static get propTypes() {
@@ -23,7 +27,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
super(props);
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
-
+ this.handleUserChange = this.handleUserChange.bind(this);
this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
@@ -31,20 +35,23 @@ export default class InstalledOutgoingWebhooks extends React.Component {
this.state = {
outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId)
+ loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId),
+ users: UserStore.getProfiles()
};
}
componentDidMount() {
IntegrationStore.addChangeListener(this.handleIntegrationChange);
+ UserStore.addChangeListener(this.handleUserChange);
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- AsyncClient.listOutgoingHooks();
+ loadOutgoingHooks();
}
}
componentWillUnmount() {
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
+ UserStore.removeChangeListener(this.handleUserChange);
}
handleIntegrationChange() {
@@ -56,6 +63,10 @@ export default class InstalledOutgoingWebhooks extends React.Component {
});
}
+ handleUserChange() {
+ this.setState({users: UserStore.getProfiles()});
+ }
+
regenOutgoingWebhookToken(outgoingWebhook) {
AsyncClient.regenOutgoingHookToken(outgoingWebhook.id);
}
@@ -72,6 +83,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
outgoingWebhook={outgoingWebhook}
onRegenToken={this.regenOutgoingWebhookToken}
onDelete={this.deleteOutgoingWebhook}
+ creator={this.state.users[outgoingWebhook.creator_id] || {}}
/>
);
});
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
index 3b712ffe2..824e7b91d 100644
--- a/webapp/components/logged_in.jsx
+++ b/webapp/components/logged_in.jsx
@@ -1,21 +1,24 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
import LoadingScreen from 'components/loading_screen.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
+
import UserStore from 'stores/user_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
-import * as Utils from 'utils/utils.jsx';
+
import * as GlobalActions from 'actions/global_actions.jsx';
import * as WebSocketActions from 'actions/websocket_actions.jsx';
+import {loadEmoji} from 'actions/emoji_actions.jsx';
+
+import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
const BACKSPACE_CHAR = 8;
+import $ from 'jquery';
import React from 'react';
// import the EmojiStore so that it'll register to receive the results of the listEmojis call further down
@@ -148,7 +151,7 @@ export default class LoggedIn extends React.Component {
// Get custom emoji from the server
if (window.mm_config.EnableCustomEmoji === 'true') {
- AsyncClient.listEmoji();
+ loadEmoji(false);
}
}
diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx
index 9f18fba33..a3e43af28 100644
--- a/webapp/components/member_list_team.jsx
+++ b/webapp/components/member_list_team.jsx
@@ -1,62 +1,94 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import FilteredUserList from './filtered_user_list.jsx';
-import TeamMembersDropdown from './team_members_dropdown.jsx';
+import SearchableUserList from 'components/searchable_user_list.jsx';
+import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
+
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
+
+import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
+import {getTeamStats} from 'utils/async_client.jsx';
+
+import Constants from 'utils/constants.jsx';
import React from 'react';
+const USERS_PER_PAGE = 50;
+
export default class MemberListTeam extends React.Component {
constructor(props) {
super(props);
- this.getUsers = this.getUsers.bind(this);
this.onChange = this.onChange.bind(this);
- this.onTeamChange = this.onTeamChange.bind(this);
+ this.onStatsChange = this.onStatsChange.bind(this);
+ this.search = this.search.bind(this);
+ this.loadComplete = this.loadComplete.bind(this);
+
+ const stats = TeamStore.getCurrentStats();
this.state = {
- users: this.getUsers(),
- teamMembers: TeamStore.getMembersForTeam()
+ users: UserStore.getProfileListInTeam(),
+ teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
+ total: stats.member_count,
+ search: false,
+ loading: true
};
}
componentDidMount() {
- UserStore.addChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onTeamChange);
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
+ UserStore.addInTeamChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
+ TeamStore.addStatsChangeListener(this.onStatsChange);
+
+ loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), this.loadComplete);
+ getTeamStats(TeamStore.getCurrentId());
}
componentWillUnmount() {
- UserStore.removeChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onTeamChange);
+ UserStore.removeInTeamChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
+ TeamStore.removeStatsChangeListener(this.onStatsChange);
}
- getUsers() {
- const profiles = UserStore.getProfiles();
- const users = [];
+ loadComplete() {
+ this.setState({loading: false});
+ }
- for (const id of Object.keys(profiles)) {
- users.push(profiles[id]);
+ onChange() {
+ if (!this.state.search) {
+ this.setState({users: UserStore.getProfileListInTeam()});
}
- users.sort((a, b) => a.username.localeCompare(b.username));
+ this.setState({teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
+ }
- return users;
+ onStatsChange() {
+ const stats = TeamStore.getCurrentStats();
+ this.setState({total: stats.member_count});
}
- onChange() {
- this.setState({
- users: this.getUsers()
- });
+ nextPage(page) {
+ loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
}
- onTeamChange() {
- this.setState({
- teamMembers: TeamStore.getMembersForTeam()
- });
+ search(term) {
+ if (term === '') {
+ this.setState({search: false, users: UserStore.getProfileListInTeam()});
+ return;
+ }
+
+ searchUsers(
+ term,
+ TeamStore.getCurrentId(),
+ {},
+ (users) => {
+ this.setState({loading: true, search: true, users});
+ loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
+ }
+ );
}
render() {
@@ -65,12 +97,38 @@ export default class MemberListTeam extends React.Component {
teamMembersDropdown = [TeamMembersDropdown];
}
+ const teamMembers = this.state.teamMembers;
+ const users = this.state.users;
+ const actionUserProps = {};
+
+ let usersToDisplay;
+ if (this.state.loading) {
+ usersToDisplay = null;
+ } else {
+ usersToDisplay = [];
+
+ for (let i = 0; i < users.length; i++) {
+ const user = users[i];
+
+ if (teamMembers[user.id]) {
+ usersToDisplay.push(user);
+ actionUserProps[user.id] = {
+ teamMember: teamMembers[user.id]
+ };
+ }
+ }
+ }
+
return (
- <FilteredUserList
+ <SearchableUserList
style={this.props.style}
- users={this.state.users}
- teamMembers={this.state.teamMembers}
+ users={usersToDisplay}
+ usersPerPage={USERS_PER_PAGE}
+ total={this.state.total}
+ nextPage={this.nextPage}
+ search={this.search}
actions={teamMembersDropdown}
+ actionUserProps={actionUserProps}
/>
);
}
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx
index 24718387e..11849f718 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels.jsx
@@ -1,73 +1,67 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import FilteredUserList from 'components/filtered_user_list.jsx';
+import SearchableUserList from 'components/searchable_user_list.jsx';
import SpinnerButton from 'components/spinner_button.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-import {getMoreDmList} from 'actions/user_actions.jsx';
+import {searchUsers} from 'actions/user_actions.jsx';
+import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
+const USERS_PER_PAGE = 50;
+
export default class MoreDirectChannels extends React.Component {
constructor(props) {
super(props);
this.handleHide = this.handleHide.bind(this);
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
- this.handleUserChange = this.handleUserChange.bind(this);
- this.onTeamChange = this.onTeamChange.bind(this);
+ this.onChange = this.onChange.bind(this);
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
+ this.toggleList = this.toggleList.bind(this);
+ this.nextPage = this.nextPage.bind(this);
+ this.search = this.search.bind(this);
+ this.loadComplete = this.loadComplete.bind(this);
this.state = {
- users: UserStore.getProfilesForDmList(),
- teamMembers: TeamStore.getMembersForTeam(),
+ users: UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true),
loadingDMChannel: -1,
- usersLoaded: false,
- teamMembersLoaded: false
+ listType: 'team',
+ loading: false,
+ search: false
};
}
componentDidMount() {
- UserStore.addDmListChangeListener(this.handleUserChange);
- TeamStore.addChangeListener(this.onTeamChange);
+ UserStore.addChangeListener(this.onChange);
+ UserStore.addInTeamChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
+
+ AsyncClient.getProfiles(0, Constants.PROFILE_CHUNK_SIZE);
+ AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, Constants.PROFILE_CHUNK_SIZE);
}
componentWillUnmount() {
- UserStore.removeDmListChangeListener(this.handleUserChange);
- TeamStore.removeChangeListener(this.onTeamChange);
+ UserStore.removeChangeListener(this.onChange);
+ UserStore.removeInTeamChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
}
- shouldComponentUpdate(nextProps, nextState) {
- if (nextProps.show !== this.props.show) {
- return true;
- }
-
- if (nextProps.onModalDismissed.toString() !== this.props.onModalDismissed.toString()) {
- return true;
- }
-
- if (nextState.loadingDMChannel !== this.state.loadingDMChannel) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.users, this.state.users)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.teamMembers, this.state.teamMembers)) {
- return true;
- }
-
- return false;
+ loadComplete() {
+ this.setState({loading: false});
}
handleHide() {
@@ -84,7 +78,7 @@ export default class MoreDirectChannels extends React.Component {
}
this.setState({loadingDMChannel: teammate.id});
- Utils.openDirectChannelToUser(
+ openDirectChannelToUser(
teammate,
(channel) => {
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name);
@@ -97,17 +91,35 @@ export default class MoreDirectChannels extends React.Component {
);
}
- handleUserChange() {
+ onChange(force) {
+ if (this.state.search && !force) {
+ return;
+ }
+
+ let users;
+ if (this.state.listType === 'any') {
+ users = UserStore.getProfileList();
+ } else {
+ users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true);
+ }
+
this.setState({
- users: UserStore.getProfilesForDmList(),
- usersLoaded: true
+ users
});
}
- onTeamChange() {
+ toggleList(e) {
+ const listType = e.target.value;
+ let users;
+ if (listType === 'any') {
+ users = UserStore.getProfileList();
+ } else {
+ users = UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true);
+ }
+
this.setState({
- teamMembers: TeamStore.getMembersForTeam(),
- teamMembersLoaded: true
+ users,
+ listType
});
}
@@ -126,38 +138,96 @@ export default class MoreDirectChannels extends React.Component {
);
}
+ nextPage(page) {
+ if (this.state.listType === 'any') {
+ AsyncClient.getProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ } else {
+ AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ }
+ }
+
+ search(term) {
+ if (term === '') {
+ this.onChange(true);
+ this.setState({search: false});
+ return;
+ }
+
+ let teamId;
+ if (this.state.listType === 'any') {
+ teamId = '';
+ } else {
+ teamId = TeamStore.getCurrentId();
+ }
+
+ searchUsers(
+ term,
+ teamId,
+ {},
+ (users) => {
+ for (let i = 0; i < users.length; i++) {
+ if (users[i].id === UserStore.getCurrentId()) {
+ users.splice(i, 1);
+ break;
+ }
+ }
+ this.setState({search: true, users});
+ }
+ );
+ }
+
render() {
let maxHeight = 1000;
if (Utils.windowHeight() <= 1200) {
maxHeight = Utils.windowHeight() - 300;
}
- var body = null;
- if (!this.state.usersLoaded || !this.state.teamMembersLoaded) {
- body = (<LoadingScreen/>);
- } else {
- var showTeamToggle = false;
- if (global.window.mm_config.RestrictDirectMessage === 'any') {
- showTeamToggle = true;
- }
-
- body = (
- <FilteredUserList
- style={{maxHeight}}
- users={this.state.users}
- teamMembers={this.state.teamMembers}
- actions={[this.createJoinDirectChannelButton]}
- showTeamToggle={showTeamToggle}
- />
+ let teamToggle;
+ if (global.window.mm_config.RestrictDirectMessage === 'any') {
+ teamToggle = (
+ <div className='member-select__container'>
+ <select
+ className='form-control'
+ id='restrictList'
+ ref='restrictList'
+ defaultValue='team'
+ onChange={this.toggleList}
+ >
+ <option value='any'>
+ <FormattedMessage
+ id='filtered_user_list.any_team'
+ defaultMessage='All Users'
+ />
+ </option>
+ <option value='team'>
+ <FormattedMessage
+ id='filtered_user_list.team_only'
+ defaultMessage='Members of this Team'
+ />
+ </option>
+ </select>
+ <span
+ className='member-show'
+ >
+ <FormattedMessage
+ id='filtered_user_list.show'
+ defaultMessage='Filter:'
+ />
+ </span>
+ </div>
);
}
+ let users = this.state.users;
+ if (this.state.loading) {
+ users = null;
+ }
+
return (
<Modal
dialogClassName='more-modal more-direct-channels'
show={this.props.show}
onHide={this.handleHide}
- onEntered={getMoreDmList}
>
<Modal.Header closeButton={true}>
<Modal.Title>
@@ -168,7 +238,16 @@ export default class MoreDirectChannels extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- {body}
+ {teamToggle}
+ <SearchableUserList
+ key={'moreDirectChannelsList_' + this.state.listType}
+ style={{maxHeight}}
+ users={users}
+ usersPerPage={USERS_PER_PAGE}
+ nextPage={this.nextPage}
+ search={this.search}
+ actions={[this.createJoinDirectChannelButton]}
+ />
</Modal.Body>
<Modal.Footer>
<button
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index 72066780e..865e2ac78 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -69,8 +69,8 @@ export default class Navbar extends React.Component {
return {
channel: ChannelStore.getCurrent(),
member: ChannelStore.getCurrentMember(),
- users: ChannelStore.getCurrentExtraInfo().members,
- userCount: ChannelStore.getCurrentExtraInfo().member_count,
+ users: [],
+ userCount: ChannelStore.getCurrentStats().member_count,
currentUser: UserStore.getCurrentUser()
};
}
@@ -81,7 +81,7 @@ export default class Navbar extends React.Component {
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
- ChannelStore.addExtraInfoChangeListener(this.onChange);
+ ChannelStore.addStatsChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
$('.inner-wrap').click(this.hideSidebars);
document.addEventListener('keydown', this.showChannelSwitchModal);
@@ -89,7 +89,7 @@ export default class Navbar extends React.Component {
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
- ChannelStore.removeExtraInfoChangeListener(this.onChange);
+ ChannelStore.removeStatsChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
document.removeEventListener('keydown', this.showChannelSwitchModal);
}
diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx
index f7244018d..e210fcbee 100644
--- a/webapp/components/needs_team.jsx
+++ b/webapp/components/needs_team.jsx
@@ -13,6 +13,7 @@ import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
+import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
import Constants from 'utils/constants.jsx';
const TutorialSteps = Constants.TutorialSteps;
const Preferences = Constants.Preferences;
@@ -80,6 +81,7 @@ export default class NeedsTeam extends React.Component {
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial');
}
+ stopPeriodicStatusUpdates();
}
componentDidMount() {
@@ -89,6 +91,8 @@ export default class NeedsTeam extends React.Component {
// Emit view action
GlobalActions.viewLoggedIn();
+ startPeriodicStatusUpdates();
+
// Set up tracking for whether the window is active
window.isActive = true;
$(window).on('focus', () => {
diff --git a/webapp/components/notify_counts.jsx b/webapp/components/notify_counts.jsx
index 8f9eadab7..6ccbd228b 100644
--- a/webapp/components/notify_counts.jsx
+++ b/webapp/components/notify_counts.jsx
@@ -7,7 +7,7 @@ import ChannelStore from 'stores/channel_store.jsx';
function getCountsStateFromStores() {
var count = 0;
var channels = ChannelStore.getAll();
- var members = ChannelStore.getAllMembers();
+ var members = ChannelStore.getMyMembers();
channels.forEach((channel) => {
var channelMember = members[channel.id];
diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx
index bfbe66677..9cea3922a 100644
--- a/webapp/components/popover_list_members.jsx
+++ b/webapp/components/popover_list_members.jsx
@@ -6,9 +6,11 @@ import ProfilePicture from 'components/profile_picture.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
+import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
+
+import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
import $ from 'jquery';
import React from 'react';
@@ -22,20 +24,18 @@ export default class PopoverListMembers extends React.Component {
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.closePopover = this.closePopover.bind(this);
+
+ this.state = {showPopover: false};
}
componentDidUpdate() {
$('.member-list__popover .popover-content').perfectScrollbar();
}
- componentWillMount() {
- this.setState({showPopover: false});
- }
-
handleShowDirectChannel(teammate, e) {
e.preventDefault();
- Utils.openDirectChannelToUser(
+ openDirectChannelToUser(
teammate,
(channel, channelAlreadyExisted) => {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
@@ -90,12 +90,6 @@ export default class PopoverListMembers extends React.Component {
}
if (name) {
- let status;
- if (m.status) {
- status = m.status;
- } else {
- status = UserStore.getStatus(m.id);
- }
popoverHtml.push(
<div
className='more-modal__row'
@@ -103,7 +97,6 @@ export default class PopoverListMembers extends React.Component {
>
<ProfilePicture
src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`}
- status={status}
width='26'
height='26'
/>
@@ -123,19 +116,27 @@ export default class PopoverListMembers extends React.Component {
);
}
});
- }
-
- let count = this.props.memberCount;
- let countText = '-';
- // fall back to checking the length of the member list if the count isn't set
- if (!count && members) {
- count = members.length;
+ popoverHtml.push(
+ <div
+ className='more-modal__row'
+ key={'popover-member-more'}
+ >
+ <div className='col-sm-5'/>
+ <div className='more-modal__details'>
+ <div
+ className='more-modal__name'
+ >
+ {'...'}
+ </div>
+ </div>
+ </div>
+ );
}
- if (count > Constants.MAX_CHANNEL_POPOVER_COUNT) {
- countText = Constants.MAX_CHANNEL_POPOVER_COUNT + '+';
- } else if (count > 0) {
+ const count = this.props.memberCount;
+ let countText = '-';
+ if (count > 0) {
countText = count.toString();
}
@@ -151,7 +152,10 @@ export default class PopoverListMembers extends React.Component {
id='member_popover'
className='member-popover__trigger'
ref='member_popover_target'
- onClick={(e) => this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover})}
+ onClick={(e) => {
+ this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
+ AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
+ }}
>
<div>
{countText}
diff --git a/webapp/components/post_view/components/pending_post_options.jsx b/webapp/components/post_view/components/pending_post_options.jsx
index 711ea832c..44f4794ef 100644
--- a/webapp/components/post_view/components/pending_post_options.jsx
+++ b/webapp/components/post_view/components/pending_post_options.jsx
@@ -4,11 +4,10 @@
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
+import {loadPosts} from 'actions/post_actions.jsx';
+import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
-
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -29,13 +28,13 @@ export default class PendingPostOptions extends React.Component {
var post = this.props.post;
Client.createPost(post,
(data) => {
- AsyncClient.getPosts(post.channel_id);
+ loadPosts(post.channel_id);
var channel = ChannelStore.get(post.channel_id);
- var member = ChannelStore.getMember(post.channel_id);
+ var member = ChannelStore.getMyMember(post.channel_id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = (new Date()).getTime();
- ChannelStore.setChannelMember(member);
+ ChannelStore.storeMyChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST,
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index d686b28e5..46ce0ed67 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -66,6 +66,16 @@ export default class PostList extends React.Component {
}
}
+ componentWillReceiveProps(nextProps) {
+ // TODO: Clean-up intro text creation
+ if (this.props.channel && this.props.channel.type === Constants.DM_CHANNEL) {
+ const teammateId = Utils.getUserIdFromChannelName(this.props.channel);
+ if (!this.props.profiles[teammateId] && nextProps.profiles[teammateId]) {
+ this.introText = createChannelIntroMessage(this.props.channel, this.state.fullWidthIntro);
+ }
+ }
+ }
+
handleKeyDown(e) {
if (e.which === Constants.KeyCodes.ESCAPE && $('.popover.in,.modal.in').length === 0) {
e.preventDefault();
diff --git a/webapp/components/post_view/post_focus_view_controller.jsx b/webapp/components/post_view/post_focus_view_controller.jsx
index 4e21cb29f..8edec6970 100644
--- a/webapp/components/post_view/post_focus_view_controller.jsx
+++ b/webapp/components/post_view/post_focus_view_controller.jsx
@@ -35,10 +35,7 @@ export default class PostFocusView extends React.Component {
const focusedPostId = PostStore.getFocusedPostId();
const channel = ChannelStore.getCurrent();
- let profiles = UserStore.getProfiles();
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
+ const profiles = UserStore.getProfiles();
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
@@ -115,12 +112,7 @@ export default class PostFocusView extends React.Component {
}
onUserChange() {
- const channel = ChannelStore.getCurrent();
- let profiles = UserStore.getProfiles();
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
- this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
+ this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
}
onStatusChange() {
diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx
index 12fd5cd63..57b488b54 100644
--- a/webapp/components/post_view/post_view_controller.jsx
+++ b/webapp/components/post_view/post_view_controller.jsx
@@ -34,13 +34,10 @@ export default class PostViewController extends React.Component {
this.onBusy = this.onBusy.bind(this);
const channel = props.channel;
- let profiles = UserStore.getProfiles();
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
+ const profiles = UserStore.getProfiles();
let lastViewed = Number.MAX_VALUE;
- const member = ChannelStore.getMember(channel.id);
+ const member = ChannelStore.getMyMember(channel.id);
if (member != null) {
lastViewed = member.last_viewed_at;
}
@@ -107,12 +104,7 @@ export default class PostViewController extends React.Component {
}
onUserChange() {
- const channel = this.state.channel;
- let profiles = UserStore.getProfiles();
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
- this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))});
+ this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
}
onPostsChange() {
@@ -165,15 +157,12 @@ export default class PostViewController extends React.Component {
const channel = nextProps.channel;
let lastViewed = Number.MAX_VALUE;
- const member = ChannelStore.getMember(channel.id);
+ const member = ChannelStore.getMyMember(channel.id);
if (member != null) {
lastViewed = member.last_viewed_at;
}
- let profiles = UserStore.getProfiles();
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
+ const profiles = UserStore.getProfiles();
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 7d643bd38..27446c85a 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -61,6 +61,10 @@ export default class RhsRootPost extends React.Component {
return true;
}
+ if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
+ return true;
+ }
+
if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
return true;
}
@@ -85,7 +89,7 @@ export default class RhsRootPost extends React.Component {
var isOwner = this.props.currentUser.id === post.user_id;
var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
- var timestamp = UserStore.getProfile(post.user_id).update_at;
+ var timestamp = user.update_at;
var channel = ChannelStore.get(post.channel_id);
const flagIcon = Constants.FLAG_ICON_SVG;
diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx
index 7d0de8590..11c79d722 100644
--- a/webapp/components/rhs_thread.jsx
+++ b/webapp/components/rhs_thread.jsx
@@ -8,7 +8,6 @@ import RootPost from './rhs_root_post.jsx';
import Comment from './rhs_comment.jsx';
import FileUploadOverlay from './file_upload_overlay.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
@@ -238,12 +237,7 @@ export default class RhsThread extends React.Component {
render() {
const postsArray = this.state.postsArray;
const selected = this.state.selected;
- const channel = ChannelStore.get(this.state.selected.channel_id);
-
- let profiles = this.state.profiles || {};
- if (channel && channel.type === Constants.DM_CHANNEL) {
- profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
- }
+ const profiles = this.state.profiles || {};
if (postsArray == null || selected == null) {
return (
diff --git a/webapp/components/searchable_user_list.jsx b/webapp/components/searchable_user_list.jsx
new file mode 100644
index 000000000..8d4f74ab3
--- /dev/null
+++ b/webapp/components/searchable_user_list.jsx
@@ -0,0 +1,226 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import UserList from 'components/user_list.jsx';
+
+import * as Utils from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
+const KeyCodes = Constants.KeyCodes;
+
+import $ from 'jquery';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {FormattedMessage} from 'react-intl';
+
+const NEXT_BUTTON_TIMEOUT = 500;
+
+export default class SearchableUserList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.nextPage = this.nextPage.bind(this);
+ this.previousPage = this.previousPage.bind(this);
+ this.doSearch = this.doSearch.bind(this);
+ this.onSearchBoxKeyPress = this.onSearchBoxKeyPress.bind(this);
+ this.onSearchBoxChange = this.onSearchBoxChange.bind(this);
+
+ this.nextTimeoutId = 0;
+
+ this.state = {
+ page: 0,
+ search: false,
+ nextDisabled: false
+ };
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (this.state.page !== prevState.page) {
+ $(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
+ }
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.nextTimeoutId);
+ }
+
+ nextPage(e) {
+ e.preventDefault();
+ this.setState({page: this.state.page + 1, nextDisabled: true});
+ this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT);
+ this.props.nextPage(this.state.page + 1);
+ }
+
+ previousPage(e) {
+ e.preventDefault();
+ this.setState({page: this.state.page - 1});
+ }
+
+ doSearch() {
+ const term = this.refs.filter.value;
+ this.props.search(term);
+ if (term === '') {
+ this.setState({page: 0, search: false});
+ } else {
+ this.setState({search: true});
+ }
+ }
+
+ onSearchBoxKeyPress(e) {
+ if (e.charCode === KeyCodes.ENTER) {
+ e.preventDefault();
+ this.doSearch();
+ }
+ }
+
+ onSearchBoxChange(e) {
+ if (e.target.value === '') {
+ this.props.search(''); // clear search
+ this.setState({page: 0, search: false});
+ }
+ }
+
+ render() {
+ let nextButton;
+ let previousButton;
+ let usersToDisplay;
+ let count;
+
+ if (this.props.users == null) {
+ usersToDisplay = this.props.users;
+ } else if (this.state.search || this.props.users == null) {
+ usersToDisplay = this.props.users;
+
+ if (this.props.total) {
+ count = (
+ <FormattedMessage
+ id='filtered_user_list.countTotal'
+ defaultMessage='{count} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
+ values={{
+ count: usersToDisplay.length || 0,
+ total: this.props.total
+ }}
+ />
+ );
+ }
+ } else {
+ const pageStart = this.state.page * this.props.usersPerPage;
+ const pageEnd = pageStart + this.props.usersPerPage;
+ usersToDisplay = this.props.users.slice(pageStart, pageEnd);
+
+ if (usersToDisplay.length >= this.props.usersPerPage) {
+ nextButton = (
+ <button
+ className='btn btn-default filter-control filter-control__next'
+ onClick={this.nextPage}
+ disabled={this.state.nextDisabled}
+ >
+ {'Next'}
+ </button>
+ );
+ }
+
+ if (this.state.page > 0) {
+ previousButton = (
+ <button
+ className='btn btn-default filter-control filter-control__prev'
+ onClick={this.previousPage}
+ >
+ {'Previous'}
+ </button>
+ );
+ }
+
+ if (this.props.total) {
+ const startCount = this.state.page * this.props.usersPerPage;
+ const endCount = startCount + usersToDisplay.length;
+
+ count = (
+ <FormattedMessage
+ id='filtered_user_list.countTotalPage'
+ defaultMessage='{startCount, number} - {endCount, number} {count, plural, =0 {0 members} one {member} other {members}} of {total} total'
+ values={{
+ count: usersToDisplay.length,
+ startCount: startCount + 1,
+ endCount,
+ total: this.props.total
+ }}
+ />
+ );
+ }
+ }
+
+ return (
+ <div
+ className='filtered-user-list'
+ style={this.props.style}
+ >
+ <div className='filter-row'>
+ <div className='col-sm-5'>
+ <input
+ ref='filter'
+ className='form-control filter-textbox'
+ placeholder={Utils.localizeMessage('filtered_user_list.search', 'Press enter to search')}
+ onKeyPress={this.onSearchBoxKeyPress}
+ onChange={this.onSearchBoxChange}
+ />
+ </div>
+ <div className='col-sm-2 filter-button'>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.doSearch}
+ disabled={this.props.users == null}
+ >
+ <FormattedMessage
+ id='filtered_user_list.searchButton'
+ defaultMessage='Search'
+ />
+ </button>
+ </div>
+ <div className='col-sm-12'>
+ <span className='member-count pull-left'>{count}</span>
+ </div>
+ </div>
+ <div
+ ref='userList'
+ className='more-modal__list'
+ >
+ <UserList
+ users={usersToDisplay}
+ extraInfo={this.props.extraInfo}
+ actions={this.props.actions}
+ actionProps={this.props.actionProps}
+ actionUserProps={this.props.actionUserProps}
+ />
+ </div>
+ <div className='filter-controls'>
+ {previousButton}
+ {nextButton}
+ </div>
+ </div>
+ );
+ }
+}
+
+SearchableUserList.defaultProps = {
+ users: [],
+ usersPerPage: 50, //eslint-disable-line no-magic-numbers
+ extraInfo: {},
+ actions: [],
+ actionProps: {},
+ actionUserProps: {},
+ showTeamToggle: false
+};
+
+SearchableUserList.propTypes = {
+ users: React.PropTypes.arrayOf(React.PropTypes.object),
+ usersPerPage: React.PropTypes.number,
+ total: React.PropTypes.number,
+ extraInfo: React.PropTypes.object,
+ nextPage: React.PropTypes.func.isRequired,
+ search: React.PropTypes.func.isRequired,
+ actions: React.PropTypes.arrayOf(React.PropTypes.func),
+ actionProps: React.PropTypes.object,
+ actionUserProps: React.PropTypes.object,
+ style: React.PropTypes.object
+};
diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx
index 5f8d9f463..283299b37 100644
--- a/webapp/components/select_team/select_team.jsx
+++ b/webapp/components/select_team/select_team.jsx
@@ -46,7 +46,7 @@ export default class SelectTeam extends React.Component {
getStateFromStores(loaded) {
return {
teams: TeamStore.getAll(),
- teamMembers: TeamStore.getTeamMembers(),
+ teamMembers: TeamStore.getMyTeamMembers(),
teamListings: TeamStore.getTeamListings(),
loaded
};
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index dc52ebb91..c8a7e1eb9 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -19,6 +19,7 @@ import LocalizationStore from 'stores/localization_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as ChannelActions from 'actions/channel_actions.jsx';
+import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import Constants from 'utils/constants.jsx';
@@ -93,7 +94,7 @@ export default class Sidebar extends React.Component {
}
getStateFromStores() {
- const members = ChannelStore.getAllMembers();
+ const members = ChannelStore.getMyMembers();
const currentChannelId = ChannelStore.getCurrentId();
const currentUserId = UserStore.getCurrentId();
@@ -133,9 +134,9 @@ export default class Sidebar extends React.Component {
directChannel.teammate_id = teammateId;
directChannel.status = UserStore.getStatus(teammateId) || 'offline';
- if (UserStore.hasTeamProfile(teammateId) && TeamStore.hasActiveMemberForTeam(teammateId)) {
+ if (TeamStore.hasActiveMemberInTeam(TeamStore.getCurrentId(), teammateId)) {
directChannels.push(directChannel);
- } else {
+ } else if (TeamStore.hasMemberNotInTeam(TeamStore.getCurrentId(), teammateId)) {
directNonTeamChannels.push(directChannel);
}
}
@@ -164,6 +165,7 @@ export default class Sidebar extends React.Component {
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
+ UserStore.addInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
TeamStore.addChangeListener(this.onChange);
PreferenceStore.addChangeListener(this.onChange);
@@ -173,6 +175,8 @@ export default class Sidebar extends React.Component {
document.addEventListener('keydown', this.navigateChannelShortcut);
document.addEventListener('keydown', this.navigateUnreadChannelShortcut);
+
+ loadProfilesAndTeamMembersForDMSidebar();
}
shouldComponentUpdate(nextProps, nextState) {
@@ -205,6 +209,7 @@ export default class Sidebar extends React.Component {
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeChangeListener(this.onChange);
+ UserStore.removeInTeamChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
TeamStore.removeChangeListener(this.onChange);
PreferenceStore.removeChangeListener(this.onChange);
diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx
index dccac64b3..76ed6271a 100644
--- a/webapp/components/sidebar_header_dropdown.jsx
+++ b/webapp/components/sidebar_header_dropdown.jsx
@@ -54,7 +54,7 @@ export default class SidebarHeaderDropdown extends React.Component {
this.state = {
teams: TeamStore.getAll(),
- teamMembers: TeamStore.getTeamMembers(),
+ teamMembers: TeamStore.getMyTeamMembers(),
showDropdown: false
};
}
@@ -118,7 +118,7 @@ export default class SidebarHeaderDropdown extends React.Component {
onTeamChange() {
this.setState({
teams: TeamStore.getAll(),
- teamMembers: TeamStore.getTeamMembers()
+ teamMembers: TeamStore.getMyTeamMembers()
});
}
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index 9998e6357..d4f441f98 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -1,19 +1,19 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import Suggestion from './suggestion.jsx';
-import SuggestionStore from 'stores/suggestion_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
+
+import {autocompleteUsersInChannel} from 'actions/user_actions.jsx';
+
+import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
-import Constants from 'utils/constants.jsx';
+import {Constants, ActionTypes} from 'utils/constants.jsx';
+import React from 'react';
import {FormattedMessage} from 'react-intl';
-import Suggestion from './suggestion.jsx';
-
-const MaxUserSuggestions = 40;
class AtMentionSuggestion extends Suggestion {
render() {
@@ -99,92 +99,66 @@ class AtMentionSuggestion extends Suggestion {
}
}
-function filterUsersByPrefix(users, prefix, limit, type) {
- const filtered = [];
-
- for (const id of Object.keys(users)) {
- if (filtered.length >= limit) {
- break;
- }
-
- const user = users[id];
-
- if (user.delete_at > 0) {
- continue;
- }
-
- if (user.username.startsWith(prefix) ||
- (user.first_name && user.first_name.toLowerCase().startsWith(prefix)) ||
- (user.last_name && user.last_name.toLowerCase().startsWith(prefix)) ||
- (user.nickname && user.nickname.toLowerCase().startsWith(prefix))) {
- // create a new object here since we're mutating it by adding the type field
- filtered.push(Object.assign({}, user, {type}));
- }
- }
-
- return filtered;
-}
-
export default class AtMentionProvider {
constructor(channelId) {
this.channelId = channelId;
+ this.timeoutId = '';
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeoutId);
}
handlePretextChanged(suggestionId, pretext) {
+ clearTimeout(this.timeoutId);
+
const captured = (/@([a-z0-9\-\._]*)$/i).exec(pretext.toLowerCase());
if (captured) {
const prefix = captured[1];
- // Group users into members and nonmembers of the channel.
- const users = UserStore.getActiveOnlyProfiles(true);
- const channelMembers = {};
- const channelNonmembers = users;
- if (this.channelId != null) {
- const extraInfo = ChannelStore.getExtraInfo(this.channelId);
- for (let i = 0; i < extraInfo.members.length; i++) {
- const id = extraInfo.members[i].id;
- if (users[id]) {
- channelMembers[id] = users[id];
- Reflect.deleteProperty(channelNonmembers, id);
+ function autocomplete() {
+ autocompleteUsersInChannel(
+ prefix,
+ this.channelId,
+ (data) => {
+ const members = data.in_channel;
+ for (const id of Object.keys(members)) {
+ members[id].type = Constants.MENTION_MEMBERS;
+ }
+
+ const nonmembers = data.out_of_channel;
+ for (const id of Object.keys(nonmembers)) {
+ nonmembers[id].type = Constants.MENTION_NONMEMBERS;
+ }
+
+ let specialMentions = [];
+ if (!pretext.startsWith('/msg')) {
+ specialMentions = ['here', 'channel', 'all'].filter((item) => {
+ return item.startsWith(prefix);
+ }).map((name) => {
+ return {username: name, type: Constants.MENTION_SPECIAL};
+ });
+ }
+
+ const users = members.concat(specialMentions).concat(nonmembers);
+ const mentions = users.map((user) => '@' + user.username);
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
+ id: suggestionId,
+ matchedPretext: captured[0],
+ terms: mentions,
+ items: users,
+ component: AtMentionSuggestion
+ });
}
- }
- }
-
- // Filter users by prefix.
- const filteredMembers = filterUsersByPrefix(
- channelMembers, prefix, MaxUserSuggestions, Constants.MENTION_MEMBERS);
- const filteredNonmembers = filterUsersByPrefix(
- channelNonmembers, prefix, MaxUserSuggestions - filteredMembers.length, Constants.MENTION_NONMEMBERS);
- let filteredSpecialMentions = [];
- if (!pretext.startsWith('/msg')) {
- filteredSpecialMentions = ['here', 'channel', 'all'].filter((item) => {
- return item.startsWith(prefix);
- }).map((name) => {
- return {username: name, type: Constants.MENTION_SPECIAL};
- });
+ );
}
- // Sort users by username.
- [filteredMembers, filteredNonmembers].forEach((items) => {
- items.sort((a, b) => {
- const aPrefix = a.username.startsWith(prefix);
- const bPrefix = b.username.startsWith(prefix);
-
- if (aPrefix === bPrefix) {
- return a.username.localeCompare(b.username);
- } else if (aPrefix) {
- return -1;
- }
-
- return 1;
- });
- });
-
- const filtered = filteredMembers.concat(filteredSpecialMentions).concat(filteredNonmembers);
-
- const mentions = filtered.map((user) => '@' + user.username);
-
- SuggestionStore.addSuggestions(suggestionId, mentions, filtered, AtMentionSuggestion, captured[0]);
+ this.timeoutId = setTimeout(
+ autocomplete.bind(this),
+ Constants.AUTOCOMPLETE_TIMEOUT
+ );
}
}
}
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
index b5466cf39..baf91cd94 100644
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ b/webapp/components/suggestion/search_user_provider.jsx
@@ -1,13 +1,16 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
+import Suggestion from './suggestion.jsx';
+import {autocompleteUsersInTeam} from 'actions/user_actions.jsx';
+
+import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import Client from 'client/web_client.jsx';
-import SuggestionStore from 'stores/suggestion_store.jsx';
-import UserStore from 'stores/user_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+import {Constants, ActionTypes} from 'utils/constants.jsx';
-import Suggestion from './suggestion.jsx';
+import React from 'react';
class SearchUserSuggestion extends Suggestion {
render() {
@@ -18,6 +21,17 @@ class SearchUserSuggestion extends Suggestion {
className += ' selected';
}
+ const username = item.username;
+ let description = '';
+
+ if ((item.first_name || item.last_name) && item.nickname) {
+ description = `- ${Utils.getFullName(item)} (${item.nickname})`;
+ } else if (item.nickname) {
+ description = `- (${item.nickname})`;
+ } else if (item.first_name || item.last_name) {
+ description = `- ${Utils.getFullName(item)}`;
+ }
+
return (
<div
className={className}
@@ -27,34 +41,60 @@ class SearchUserSuggestion extends Suggestion {
className='profile-img rounded'
src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at}
/>
- <i className='fa fa fa-plus-square'/>{item.username}
+ <i className='fa fa fa-plus-square'/>
+ <div className='mention--align'>
+ <span>
+ {username}
+ </span>
+ <span className='mention__fullname'>
+ {' '}
+ {description}
+ </span>
+ </div>
</div>
);
}
}
export default class SearchUserProvider {
+ constructor() {
+ this.timeoutId = '';
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeoutId);
+ }
+
handlePretextChanged(suggestionId, pretext) {
+ clearTimeout(this.timeoutId);
+
const captured = (/\bfrom:\s*(\S*)$/i).exec(pretext.toLowerCase());
if (captured) {
const usernamePrefix = captured[1];
- const users = UserStore.getProfiles();
- let filtered = [];
-
- for (const id of Object.keys(users)) {
- const user = users[id];
+ function autocomplete() {
+ autocompleteUsersInTeam(
+ usernamePrefix,
+ (data) => {
+ const users = data.in_team;
+ const mentions = users.map((user) => user.username);
- if (user.username.startsWith(usernamePrefix)) {
- filtered.push(user);
- }
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
+ id: suggestionId,
+ matchedPretext: usernamePrefix,
+ terms: mentions,
+ items: users,
+ component: SearchUserSuggestion
+ });
+ }
+ );
}
- filtered = filtered.sort((a, b) => a.username.localeCompare(b.username));
-
- const usernames = filtered.map((user) => user.username);
-
- SuggestionStore.addSuggestions(suggestionId, usernames, filtered, SearchUserSuggestion, usernamePrefix);
+ this.timeoutId = setTimeout(
+ autocomplete.bind(this),
+ Constants.AUTOCOMPLETE_TIMEOUT
+ );
}
}
}
diff --git a/webapp/components/suggestion/suggestion_list.jsx b/webapp/components/suggestion/suggestion_list.jsx
index 7d8059e1e..65311a582 100644
--- a/webapp/components/suggestion/suggestion_list.jsx
+++ b/webapp/components/suggestion/suggestion_list.jsx
@@ -163,4 +163,4 @@ SuggestionList.propTypes = {
SuggestionList.defaultProps = {
renderDividers: false
-}; \ No newline at end of file
+};
diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx
index 70e95b9b1..94622b536 100644
--- a/webapp/components/suggestion/switch_channel_provider.jsx
+++ b/webapp/components/suggestion/switch_channel_provider.jsx
@@ -4,7 +4,6 @@
import React from 'react';
import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
import SuggestionStore from 'stores/suggestion_store.jsx';
import Suggestion from './suggestion.jsx';
import Constants from 'utils/constants.jsx';
@@ -58,7 +57,10 @@ export default class SwitchChannelProvider {
const channel = allChannels[id];
if (channel.display_name.toLowerCase().startsWith(channelPrefix.toLowerCase())) {
channels.push(channel);
- } else if (channel.type === Constants.DM_CHANNEL && Utils.getDirectTeammate(channel.id).username.startsWith(channelPrefix.toLowerCase())) {
+ }
+
+ // TODO: Fix with auto-complete refactor
+ /*else if (channel.type === Constants.DM_CHANNEL && Utils.getDirectTeammate(channel.id).username.startsWith(channelPrefix.toLowerCase())) {
// New channel to not modify existing channel
const otherUser = Utils.getDirectTeammate(channel.id);
const newChannel = {
@@ -68,7 +70,7 @@ export default class SwitchChannelProvider {
status: UserStore.getStatus(otherUser.id) || 'offline'
};
channels.push(newChannel);
- }
+ }*/
}
channels.sort((a, b) => {
diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx
index d459d0b02..3b6bc87f3 100644
--- a/webapp/components/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown.jsx
@@ -1,17 +1,20 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import ConfirmModal from './confirm_modal.jsx';
+
+import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+
+import {removeUserFromTeam} from 'actions/team_actions.jsx';
+
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import ConfirmModal from './confirm_modal.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
import React from 'react';
+import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
export default class TeamMembersDropdown extends React.Component {
@@ -44,8 +47,8 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user',
() => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
+ AsyncClient.getTeamMember(this.props.teamMember.team_id);
+ AsyncClient.getUser(this.props.user.id);
},
(err) => {
this.setState({serverError: err.message});
@@ -54,24 +57,23 @@ export default class TeamMembersDropdown extends React.Component {
}
}
handleRemoveFromTeam() {
- Client.removeUserFromTeam(
- '',
- this.props.user.id,
- () => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
+ removeUserFromTeam(
+ this.props.teamMember.team_id,
+ this.props.user.id,
+ () => {
+ AsyncClient.getTeamStats(this.props.teamMember.team_id);
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
}
handleMakeActive() {
Client.updateActive(this.props.user.id, true,
() => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
- AsyncClient.getChannelExtraInfo(ChannelStore.getCurrentId());
+ AsyncClient.getUser(this.props.user.id);
+ AsyncClient.getChannelStats(ChannelStore.getCurrentId());
+ AsyncClient.getTeamStats(this.props.teamMember.team_id);
},
(err) => {
this.setState({serverError: err.message});
@@ -81,9 +83,9 @@ export default class TeamMembersDropdown extends React.Component {
handleMakeNotActive() {
Client.updateActive(this.props.user.id, false,
() => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
- AsyncClient.getChannelExtraInfo(ChannelStore.getCurrentId());
+ AsyncClient.getUser(this.props.user.id);
+ AsyncClient.getChannelStats(ChannelStore.getCurrentId());
+ AsyncClient.getTeamStats(this.props.teamMember.team_id);
},
(err) => {
this.setState({serverError: err.message});
@@ -100,8 +102,8 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user team_admin',
() => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
+ AsyncClient.getTeamMember(this.props.teamMember.team_id, this.props.user.id);
+ AsyncClient.getUser(this.props.user.id);
},
(err) => {
this.setState({serverError: err.message});
@@ -133,8 +135,8 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
this.state.newRole,
() => {
- AsyncClient.getTeamMembers(TeamStore.getCurrentId());
- AsyncClient.getProfiles();
+ AsyncClient.getTeamMember(this.props.teamMember.team_id, this.props.user.id);
+ AsyncClient.getUser(this.props.user.id);
const teamUrl = TeamStore.getCurrentTeamUrl();
if (teamUrl) {
diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx
index fa2ffec1e..44468a67a 100644
--- a/webapp/components/textbox.jsx
+++ b/webapp/components/textbox.jsx
@@ -109,7 +109,7 @@ export default class Textbox extends React.Component {
}
componentWillReceiveProps(nextProps) {
- if (nextProps.channelId !== this.channelId) {
+ if (nextProps.channelId !== this.props.channelId) {
// Update channel id for AtMentionProvider.
const providers = this.suggestionProviders;
for (let i = 0; i < providers.length; i++) {
diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx
index 626cb3cf5..d34404c89 100644
--- a/webapp/components/user_list.jsx
+++ b/webapp/components/user_list.jsx
@@ -1,32 +1,29 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import {FormattedMessage} from 'react-intl';
import UserListRow from './user_list_row.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
import React from 'react';
+import {FormattedMessage} from 'react-intl';
export default class UserList extends React.Component {
render() {
const users = this.props.users;
let content;
- if (users.length > 0) {
+ if (users == null) {
+ return <LoadingScreen/>;
+ } else if (users.length > 0) {
content = users.map((user) => {
- var teamMember;
- for (var index in this.props.teamMembers) {
- if (this.props.teamMembers[index].user_id === user.id) {
- teamMember = this.props.teamMembers[index];
- }
- }
-
return (
<UserListRow
key={user.id}
user={user}
- teamMember={teamMember}
+ extraInfo={this.props.extraInfo[user.id]}
actions={this.props.actions}
actionProps={this.props.actionProps}
+ actionUserProps={this.props.actionUserProps[user.id]}
/>
);
});
@@ -56,14 +53,15 @@ export default class UserList extends React.Component {
UserList.defaultProps = {
users: [],
- teamMembers: [],
+ extraInfo: {},
actions: [],
actionProps: {}
};
UserList.propTypes = {
users: React.PropTypes.arrayOf(React.PropTypes.object),
- teamMembers: React.PropTypes.arrayOf(React.PropTypes.object),
+ extraInfo: React.PropTypes.object,
actions: React.PropTypes.arrayOf(React.PropTypes.func),
- actionProps: React.PropTypes.object
+ actionProps: React.PropTypes.object,
+ actionUserProps: React.PropTypes.object
};
diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx
index 9f80d4caa..ff381a30b 100644
--- a/webapp/components/user_list_row.jsx
+++ b/webapp/components/user_list_row.jsx
@@ -11,8 +11,9 @@ import * as Utils from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
import React from 'react';
+import {FormattedHTMLMessage} from 'react-intl';
-export default function UserListRow({user, teamMember, actions, actionProps}) {
+export default function UserListRow({user, extraInfo, actions, actionProps, actionUserProps}) {
const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', '');
let name = user.username;
@@ -29,15 +30,29 @@ export default function UserListRow({user, teamMember, actions, actionProps}) {
<Action
key={index.toString()}
user={user}
- teamMember={teamMember}
{...actionProps}
+ {...actionUserProps}
/>
);
});
}
+ // QUICK HACK, NEEDS A PROP FOR TOGGLING STATUS
+ let email = user.email;
+ let emailStyle = 'more-modal__description';
let status;
- if (user.status) {
+ if (extraInfo && extraInfo.length > 0) {
+ email = (
+ <FormattedHTMLMessage
+ id='admin.user_item.emailTitle'
+ defaultMessage='<strong>Email:</strong> {email}'
+ values={{
+ email: user.email
+ }}
+ />
+ );
+ emailStyle = '';
+ } else if (user.status) {
status = user.status;
} else {
status = UserStore.getStatus(user.id);
@@ -60,9 +75,10 @@ export default function UserListRow({user, teamMember, actions, actionProps}) {
<div className='more-modal__name'>
{name}
</div>
- <div className='more-modal__description'>
- {user.email}
+ <div className={emailStyle}>
+ {email}
</div>
+ {extraInfo}
</div>
<div
className='more-modal__actions'
@@ -74,17 +90,16 @@ export default function UserListRow({user, teamMember, actions, actionProps}) {
}
UserListRow.defaultProps = {
- teamMember: {
- team_id: '',
- roles: ''
- },
+ extraInfo: [],
actions: [],
- actionProps: {}
+ actionProps: {},
+ actionUserProps: {}
};
UserListRow.propTypes = {
user: React.PropTypes.object.isRequired,
- teamMember: React.PropTypes.object.isRequired,
+ extraInfo: React.PropTypes.arrayOf(React.PropTypes.object),
actions: React.PropTypes.arrayOf(React.PropTypes.func),
- actionProps: React.PropTypes.object
+ actionProps: React.PropTypes.object,
+ actionUserProps: React.PropTypes.object
};