summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/admin_console/admin_controller.jsx5
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx11
-rw-r--r--web/react/components/admin_console/line_chart.jsx50
-rw-r--r--web/react/components/admin_console/team_analytics.jsx353
-rw-r--r--web/react/components/channel_header.jsx8
-rw-r--r--web/react/components/create_post.jsx10
-rw-r--r--web/react/components/error_bar.jsx12
-rw-r--r--web/react/components/mention_list.jsx13
-rw-r--r--web/react/components/more_direct_channels.jsx62
-rw-r--r--web/react/components/popover_list_members.jsx145
-rw-r--r--web/react/components/search_bar.jsx16
-rw-r--r--web/react/components/search_results.jsx10
-rw-r--r--web/react/components/search_results_item.jsx4
-rw-r--r--web/react/components/sidebar_right.jsx7
-rw-r--r--web/react/components/textbox.jsx6
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx2
16 files changed, 602 insertions, 112 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index f770d166c..d309ced2e 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -18,6 +18,7 @@ var SqlSettingsTab = require('./sql_settings.jsx');
var TeamSettingsTab = require('./team_settings.jsx');
var ServiceSettingsTab = require('./service_settings.jsx');
var TeamUsersTab = require('./team_users.jsx');
+var TeamAnalyticsTab = require('./team_analytics.jsx');
export default class AdminController extends React.Component {
constructor(props) {
@@ -149,6 +150,10 @@ export default class AdminController extends React.Component {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
}
+ } else if (this.state.selected === 'team_analytics') {
+ if (this.state.teams) {
+ tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
+ }
}
}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index b0e01ff17..f2fb1c96d 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -24,7 +24,7 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
- history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
+ history.pushState({name, teamId}, null, `/admin_console/${name}/${teamId || ''}`);
}
isSelected(name, teamId) {
@@ -121,6 +121,15 @@ export default class AdminSidebar extends React.Component {
{'- Users'}
</a>
</li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('team_analytics', team.id)}
+ onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
+ >
+ {'- Statistics'}
+ </a>
+ </li>
</ul>
</li>
</ul>
diff --git a/web/react/components/admin_console/line_chart.jsx b/web/react/components/admin_console/line_chart.jsx
new file mode 100644
index 000000000..7e2f95c84
--- /dev/null
+++ b/web/react/components/admin_console/line_chart.jsx
@@ -0,0 +1,50 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class LineChart extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.initChart = this.initChart.bind(this);
+ this.chart = null;
+ }
+
+ componentDidMount() {
+ this.initChart(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.chart) {
+ this.chart.destroy();
+ this.initChart(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.chart) {
+ this.chart.destroy();
+ }
+ }
+
+ initChart(props) {
+ var el = ReactDOM.findDOMNode(this);
+ var ctx = el.getContext('2d');
+ this.chart = new Chart(ctx).Line(props.data, props.options || {}); //eslint-disable-line new-cap
+ }
+
+ render() {
+ return (
+ <canvas
+ width={this.props.width}
+ height={this.props.height}
+ />
+ );
+ }
+}
+
+LineChart.propTypes = {
+ width: React.PropTypes.string,
+ height: React.PropTypes.string,
+ data: React.PropTypes.object,
+ options: React.PropTypes.object
+};
diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx
new file mode 100644
index 000000000..dd8812ad0
--- /dev/null
+++ b/web/react/components/admin_console/team_analytics.jsx
@@ -0,0 +1,353 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Client = require('../../utils/client.jsx');
+var Utils = require('../../utils/utils.jsx');
+var LineChart = require('./line_chart.jsx');
+
+export default class TeamAnalytics extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getData = this.getData.bind(this);
+
+ this.state = {
+ users: null,
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ recent_active_users: null,
+ newly_created_users: null
+ };
+ }
+
+ componentDidMount() {
+ this.getData(this.props.team.id);
+ }
+
+ getData(teamId) {
+ Client.getAnalytics(
+ teamId,
+ 'standard',
+ (data) => {
+ for (var index in data) {
+ if (data[index].name === 'channel_open_count') {
+ this.setState({channel_open_count: data[index].value});
+ }
+
+ if (data[index].name === 'channel_private_count') {
+ this.setState({channel_private_count: data[index].value});
+ }
+
+ if (data[index].name === 'post_count') {
+ this.setState({post_count: data[index].value});
+ }
+ }
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getAnalytics(
+ teamId,
+ 'post_counts_day',
+ (data) => {
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Total Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({post_counts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getAnalytics(
+ teamId,
+ 'user_counts_with_posts_day',
+ (data) => {
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Active Users With Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({user_counts_with_posts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getProfilesForTeam(
+ teamId,
+ (users) => {
+ this.setState({users});
+
+ var usersList = [];
+ for (var id in users) {
+ if (users.hasOwnProperty(id)) {
+ usersList.push(users[id]);
+ }
+ }
+
+ usersList.sort((a, b) => {
+ if (a.last_activity_at < b.last_activity_at) {
+ return 1;
+ }
+
+ if (a.last_activity_at > b.last_activity_at) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ var recentActive = [];
+ for (let i = 0; i < usersList.length; i++) {
+ recentActive.push(usersList[i]);
+ if (i > 19) {
+ break;
+ }
+ }
+
+ this.setState({recent_active_users: recentActive});
+
+ usersList.sort((a, b) => {
+ if (a.create_at < b.create_at) {
+ return 1;
+ }
+
+ if (a.create_at > b.create_at) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ var newlyCreated = [];
+ for (let i = 0; i < usersList.length; i++) {
+ newlyCreated.push(usersList[i]);
+ if (i > 19) {
+ break;
+ }
+ }
+
+ this.setState({newly_created_users: newlyCreated});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ users: null,
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ recent_active_users: null,
+ newly_created_users: null
+ });
+
+ this.getData(newProps.team.id);
+ }
+
+ componentWillUnmount() {
+ }
+
+ render() {
+ var serverError = '';
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var totalCount = (
+ <div className='total-count text-center'>
+ <div>{'Total Users'}</div>
+ <div>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
+ </div>
+ );
+
+ var openChannelCount = (
+ <div className='total-count text-center'>
+ <div>{'Public Groups'}</div>
+ <div>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
+ </div>
+ );
+
+ var openPrivateCount = (
+ <div className='total-count text-center'>
+ <div>{'Private Groups'}</div>
+ <div>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
+ </div>
+ );
+
+ var postCount = (
+ <div className='total-count text-center'>
+ <div>{'Total Posts'}</div>
+ <div>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
+ </div>
+ );
+
+ var postCountsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.post_counts_day != null) {
+ postCountsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <LineChart
+ data={this.state.post_counts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ );
+ }
+
+ var usersWithPostsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.user_counts_with_posts_day != null) {
+ usersWithPostsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Active Users With Posts'}</div>
+ <LineChart
+ data={this.state.user_counts_with_posts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ );
+ }
+
+ var recentActiveUser = (
+ <div className='recent-active-users'>
+ <div>{'Recent Active Users'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.recent_active_users != null) {
+ recentActiveUser = (
+ <div className='recent-active-users'>
+ <div>{'Recent Active Users'}</div>
+ <table width='90%'>
+ <tbody>
+ {
+ this.state.recent_active_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td className='recent-active-users-td'>{user.email}</td>
+ <td className='recent-active-users-td'>{Utils.displayDateTime(user.last_activity_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+
+ var newUsers = (
+ <div className='recent-active-users'>
+ <div>{'Newly Created Users'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.newly_created_users != null) {
+ newUsers = (
+ <div className='recent-active-users'>
+ <div>{'Newly Created Users'}</div>
+ <table width='90%'>
+ <tbody>
+ {
+ this.state.newly_created_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td className='recent-active-users-td'>{user.email}</td>
+ <td className='recent-active-users-td'>{Utils.displayDateTime(user.create_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+
+ return (
+ <div className='wrapper--fixed'>
+ <h2>{'Statistics for ' + this.props.team.name}</h2>
+ {serverError}
+ {totalCount}
+ {postCount}
+ {openChannelCount}
+ {openPrivateCount}
+ {postCountsByDay}
+ {usersWithPostsByDay}
+ {recentActiveUser}
+ {newUsers}
+ </div>
+ );
+ }
+}
+
+TeamAnalytics.propTypes = {
+ team: React.PropTypes.object
+};
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 1b709336f..d66777cc6 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -3,7 +3,7 @@
const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx');
-const PostStore = require('../stores/post_store.jsx');
+const SearchStore = require('../stores/search_store.jsx');
const NavbarSearchBox = require('./search_bar.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const Client = require('../utils/client.jsx');
@@ -35,19 +35,19 @@ export default class ChannelHeader extends React.Component {
memberChannel: ChannelStore.getCurrentMember(),
memberTeam: UserStore.getCurrentUser(),
users: ChannelStore.getCurrentExtraInfo().members,
- searchVisible: PostStore.getSearchResults() !== null
+ searchVisible: SearchStore.getSearchResults() !== null
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
- PostStore.addSearchChangeListener(this.onListenerChange);
+ SearchStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
- PostStore.removeSearchChangeListener(this.onListenerChange);
+ SearchStore.removeSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
}
onListenerChange() {
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index b74f1871c..32ee31efe 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -253,8 +253,14 @@ export default class CreatePost extends React.Component {
this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
handleUploadError(err, clientId) {
+ let message = err;
+ if (message && typeof message !== 'string') {
+ // err is an AppError from the server
+ message = err.message;
+ }
+
if (clientId === -1) {
- this.setState({serverError: err});
+ this.setState({serverError: message});
} else {
const draft = PostStore.getDraft(this.state.channelId);
@@ -265,7 +271,7 @@ export default class CreatePost extends React.Component {
PostStore.storeDraft(this.state.channelId, draft);
- this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: message});
}
}
handleTextDrop(text) {
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index 6311d9460..f098384aa 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -9,12 +9,8 @@ export default class ErrorBar extends React.Component {
this.onErrorChange = this.onErrorChange.bind(this);
this.handleClose = this.handleClose.bind(this);
- this.prevTimer = null;
this.state = ErrorStore.getLastError();
- if (this.isValidError(this.state)) {
- this.prevTimer = setTimeout(this.handleClose, 10000);
- }
}
isValidError(s) {
@@ -56,16 +52,8 @@ export default class ErrorBar extends React.Component {
onErrorChange() {
var newState = ErrorStore.getLastError();
- if (this.prevTimer != null) {
- clearInterval(this.prevTimer);
- this.prevTimer = null;
- }
-
if (newState) {
this.setState(newState);
- if (!this.isConnectionError(newState)) {
- this.prevTimer = setTimeout(this.handleClose, 10000);
- }
} else {
this.setState({message: null});
}
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index 8c1da942d..61a24c09c 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
+var SearchStore = require('../stores/search_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Mention = require('./mention.jsx');
@@ -66,7 +66,7 @@ export default class MentionList extends React.Component {
}
}
componentDidMount() {
- PostStore.addMentionDataChangeListener(this.onListenerChange);
+ SearchStore.addMentionDataChangeListener(this.onListenerChange);
$('.post-right__scroll').scroll(this.onScroll);
@@ -74,7 +74,7 @@ export default class MentionList extends React.Component {
$(document).click(this.onClick);
}
componentWillUnmount() {
- PostStore.removeMentionDataChangeListener(this.onListenerChange);
+ SearchStore.removeMentionDataChangeListener(this.onListenerChange);
$('body').off('keydown.mentionlist', '#' + this.props.id);
}
@@ -217,12 +217,17 @@ export default class MentionList extends React.Component {
if (this.state.selectedMention === index) {
isFocused = 'mentions-focus';
}
+
+ if (!users[i].secondary_text) {
+ users[i].secondary_text = Utils.getFullName(users[i]);
+ }
+
mentions[index] = (
<Mention
key={'mention_key_' + index}
ref={'mention' + index}
username={users[i].username}
- secondary_text={Utils.getFullName(users[i])}
+ secondary_text={users[i].secondary_text}
id={users[i].id}
listId={index}
isFocused={isFocused}
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 41746d1d7..b0232fc08 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -1,13 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const AsyncClient = require('../utils/async_client.jsx');
-const ChannelStore = require('../stores/channel_store.jsx');
-const Constants = require('../utils/constants.jsx');
-const Client = require('../utils/client.jsx');
const Modal = ReactBootstrap.Modal;
-const PreferenceStore = require('../stores/preference_store.jsx');
-const TeamStore = require('../stores/team_store.jsx');
const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
@@ -70,52 +64,24 @@ export default class MoreDirectChannels extends React.Component {
}
handleShowDirectChannel(teammate, e) {
+ e.preventDefault();
+
if (this.state.loadingDMChannel !== -1) {
return;
}
- e.preventDefault();
-
- const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), teammate.id);
- let channel = ChannelStore.getByName(channelName);
-
- const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
- AsyncClient.savePreferences([preference]);
-
- if (channel) {
- Utils.switchChannel(channel);
-
- this.handleHide();
- } else {
- this.setState({loadingDMChannel: teammate.id});
-
- channel = {
- name: channelName,
- last_post_at: 0,
- total_msg_count: 0,
- type: 'D',
- display_name: teammate.username,
- teammate_id: teammate.id,
- status: UserStore.getStatus(teammate.id)
- };
-
- Client.createDirectChannel(
- channel,
- teammate.id,
- (data) => {
- this.setState({loadingDMChannel: -1});
-
- AsyncClient.getChannel(data.id);
- Utils.switchChannel(data);
-
- this.handleHide();
- },
- () => {
- this.setState({loadingDMChannel: -1});
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channelName;
- }
- );
- }
+ this.setState({loadingDMChannel: teammate.id});
+ Utils.openDirectChannelToUser(
+ teammate,
+ (channel) => {
+ Utils.switchChannel(channel);
+ this.setState({loadingDMChannel: -1});
+ this.handleHide();
+ },
+ () => {
+ this.setState({loadingDMChannel: -1});
+ }
+ );
}
handleUserChange() {
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index 155e88600..9cffa2400 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -3,9 +3,23 @@
var UserStore = require('../stores/user_store.jsx');
var Popover = ReactBootstrap.Popover;
-var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+var Overlay = ReactBootstrap.Overlay;
+const Utils = require('../utils/utils.jsx');
+
+const ChannelStore = require('../stores/channel_store.jsx');
export default class PopoverListMembers extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
+ this.closePopover = this.closePopover.bind(this);
+ }
+
+ componentWillMount() {
+ this.setState({showPopover: false});
+ }
+
componentDidMount() {
const originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function onLeave(obj) {
@@ -27,12 +41,36 @@ export default class PopoverListMembers extends React.Component {
}
};
}
+
+ handleShowDirectChannel(teammate, e) {
+ e.preventDefault();
+
+ Utils.openDirectChannelToUser(
+ teammate,
+ (channel, channelAlreadyExisted) => {
+ Utils.switchChannel(channel);
+ if (channelAlreadyExisted) {
+ this.closePopover();
+ }
+ },
+ () => {
+ this.closePopover();
+ }
+ );
+ }
+
+ closePopover() {
+ this.setState({showPopover: false});
+ }
+
render() {
let popoverHtml = [];
let count = 0;
let countText = '-';
const members = this.props.members;
const teamMembers = UserStore.getProfilesUsernameMap();
+ const currentUserId = UserStore.getCurrentId();
+ const ch = ChannelStore.getCurrent();
if (members && teamMembers) {
members.sort((a, b) => {
@@ -40,13 +78,74 @@ export default class PopoverListMembers extends React.Component {
});
members.forEach((m, i) => {
+ const details = [];
+
+ const fullName = Utils.getFullName(m);
+ if (fullName) {
+ details.push(
+ <span
+ key={`${m.id}__full-name`}
+ className='full-name'
+ >
+ {fullName}
+ </span>
+ );
+ }
+
+ if (m.nickname) {
+ const separator = fullName ? ' - ' : '';
+ details.push(
+ <span
+ key={`${m.nickname}__nickname`}
+ >
+ {separator + m.nickname}
+ </span>
+ );
+ }
+
+ let button = '';
+ if (currentUserId !== m.id && ch.type !== 'D') {
+ button = (
+ <button
+ type='button'
+ className='btn btn-primary btn-message'
+ onClick={(e) => this.handleShowDirectChannel(m, e)}
+ >
+ {'Message'}
+ </button>
+ );
+ }
+
if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
popoverHtml.push(
<div
className='text--nowrap'
key={'popover-member-' + i}
>
- {m.username}
+
+ <img
+ className='profile-img pull-left'
+ width='38'
+ height='38'
+ src={`/api/v1/users/${m.id}/image?time=${m.update_at}&${Utils.getSessionIndex()}`}
+ />
+ <div className='pull-left'>
+ <div
+ className='more-name'
+ >
+ {m.username}
+ </div>
+ <div
+ className='more-description'
+ >
+ {details}
+ </div>
+ </div>
+ <div
+ className='pull-right profile-action'
+ >
+ {button}
+ </div>
</div>
);
count++;
@@ -61,29 +160,37 @@ export default class PopoverListMembers extends React.Component {
}
return (
- <OverlayTrigger
- trigger='click'
- placement='bottom'
- rootClose={true}
- overlay={
+ <div>
+ <div
+ id='member_popover'
+ ref='member_popover_target'
+ onClick={(e) => this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover})}
+ >
+ <div>
+ {countText}
+ <span
+ className='fa fa-user'
+ aria-hidden='true'
+ />
+ </div>
+ </div>
+ <Overlay
+ rootClose={true}
+ onHide={this.closePopover}
+ show={this.state.showPopover}
+ target={() => this.state.popoverTarget}
+ placement='bottom'
+ >
<Popover
title='Members'
id='member-list-popover'
>
- {popoverHtml}
+ <div>
+ {popoverHtml}
+ </div>
</Popover>
- }
- >
- <div id='member_popover'>
- <div>
- {countText}
- <span
- className='fa fa-user'
- aria-hidden='true'
- />
- </div>
+ </Overlay>
</div>
- </OverlayTrigger>
);
}
}
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index 0da43e8cd..83c10494a 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -3,7 +3,7 @@
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-var PostStore = require('../stores/post_store.jsx');
+var SearchStore = require('../stores/search_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
@@ -30,17 +30,17 @@ export default class SearchBar extends React.Component {
this.state = state;
}
getSearchTermStateFromStores() {
- var term = PostStore.getSearchTerm() || '';
+ var term = SearchStore.getSearchTerm() || '';
return {
searchTerm: term
};
}
componentDidMount() {
- PostStore.addSearchTermChangeListener(this.onListenerChange);
+ SearchStore.addSearchTermChangeListener(this.onListenerChange);
this.mounted = true;
}
componentWillUnmount() {
- PostStore.removeSearchTermChangeListener(this.onListenerChange);
+ SearchStore.removeSearchTermChangeListener(this.onListenerChange);
this.mounted = false;
}
onListenerChange(doSearch, isMentionSearch) {
@@ -84,8 +84,8 @@ export default class SearchBar extends React.Component {
}
handleUserInput(e) {
var term = e.target.value;
- PostStore.storeSearchTerm(term);
- PostStore.emitSearchTermChange(false);
+ SearchStore.storeSearchTerm(term);
+ SearchStore.emitSearchTermChange(false);
this.setState({searchTerm: term});
this.refs.autocomplete.handleInputChange(e.target, term);
@@ -150,8 +150,8 @@ export default class SearchBar extends React.Component {
textbox.value = text;
utils.setCaretPosition(textbox, preText.length + word.length);
- PostStore.storeSearchTerm(text);
- PostStore.emitSearchTermChange(false);
+ SearchStore.storeSearchTerm(text);
+ SearchStore.emitSearchTermChange(false);
this.setState({searchTerm: text});
}
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 30e15d0ad..ce19c48f0 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var PostStore = require('../stores/post_store.jsx');
+var SearchStore = require('../stores/search_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var SearchBox = require('./search_bar.jsx');
var Utils = require('../utils/utils.jsx');
@@ -9,7 +9,7 @@ var SearchResultsHeader = require('./search_results_header.jsx');
var SearchResultsItem = require('./search_results_item.jsx');
function getStateFromStores() {
- return {results: PostStore.getSearchResults()};
+ return {results: SearchStore.getSearchResults()};
}
export default class SearchResults extends React.Component {
@@ -30,7 +30,7 @@ export default class SearchResults extends React.Component {
componentDidMount() {
this.mounted = true;
- PostStore.addSearchChangeListener(this.onChange);
+ SearchStore.addSearchChangeListener(this.onChange);
this.resize();
window.addEventListener('resize', this.handleResize);
}
@@ -40,7 +40,7 @@ export default class SearchResults extends React.Component {
}
componentWillUnmount() {
- PostStore.removeSearchChangeListener(this.onChange);
+ SearchStore.removeSearchChangeListener(this.onChange);
this.mounted = false;
window.removeEventListener('resize', this.handleResize);
}
@@ -78,7 +78,7 @@ export default class SearchResults extends React.Component {
searchForm = <SearchBox />;
}
var noResults = (!results || !results.order || !results.order.length);
- var searchTerm = PostStore.getSearchTerm();
+ var searchTerm = SearchStore.getSearchTerm();
var ctls = null;
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index d212e47a3..a8bd4db2c 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var PostStore = require('../stores/post_store.jsx');
+var SearchStore = require('../stores/search_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var UserProfile = require('./user_profile.jsx');
@@ -32,7 +32,7 @@ export default class SearchResultsItem extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST_SELECTED,
post_list: data,
- from_search: PostStore.getSearchTerm()
+ from_search: SearchStore.getSearchTerm()
});
AppDispatcher.handleServerAction({
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index 4e6985a86..51225cbbe 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -3,11 +3,12 @@
var SearchResults = require('./search_results.jsx');
var RhsThread = require('./rhs_thread.jsx');
+var SearchStore = require('../stores/search_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var Utils = require('../utils/utils.jsx');
function getStateFromStores() {
- return {search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch()};
+ return {search_visible: SearchStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: SearchStore.getIsMentionSearch()};
}
export default class SidebarRight extends React.Component {
@@ -22,11 +23,11 @@ export default class SidebarRight extends React.Component {
this.state = getStateFromStores();
}
componentDidMount() {
- PostStore.addSearchChangeListener(this.onSearchChange);
+ SearchStore.addSearchChangeListener(this.onSearchChange);
PostStore.addSelectedPostChangeListener(this.onSelectedChange);
}
componentWillUnmount() {
- PostStore.removeSearchChangeListener(this.onSearchChange);
+ SearchStore.removeSearchChangeListener(this.onSearchChange);
PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
}
componentDidUpdate() {
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 86bb42f62..707033d8f 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-const PostStore = require('../stores/post_store.jsx');
+const SearchStore = require('../stores/search_store.jsx');
const CommandList = require('./command_list.jsx');
const ErrorStore = require('../stores/error_store.jsx');
@@ -54,7 +54,7 @@ export default class Textbox extends React.Component {
}
componentDidMount() {
- PostStore.addAddMentionListener(this.onListenerChange);
+ SearchStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError);
this.resize();
@@ -62,7 +62,7 @@ export default class Textbox extends React.Component {
}
componentWillUnmount() {
- PostStore.removeAddMentionListener(this.onListenerChange);
+ SearchStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError);
}
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 70e559c30..1c8ce3c79 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -171,7 +171,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}.bind(this),
function imageUploadFailure(err) {
var state = this.setupInitialState(this.props);
- state.serverError = err;
+ state.serverError = err.message;
this.setState(state);
}.bind(this)
);