summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/about_build_modal.jsx6
-rw-r--r--webapp/components/access_history_modal.jsx8
-rw-r--r--webapp/components/activity_log_modal.jsx8
-rw-r--r--webapp/components/admin_console/license_settings.jsx64
-rw-r--r--webapp/components/channel_header.jsx1
-rw-r--r--webapp/components/channel_invite_button.jsx79
-rw-r--r--webapp/components/channel_invite_modal.jsx51
-rw-r--r--webapp/components/create_comment.jsx8
-rw-r--r--webapp/components/create_post.jsx7
-rw-r--r--webapp/components/filtered_user_list.jsx5
-rw-r--r--webapp/components/invite_member_modal.jsx7
-rw-r--r--webapp/components/logged_in.jsx18
-rw-r--r--webapp/components/more_direct_channels.jsx18
-rw-r--r--webapp/components/msg_typing.jsx78
-rw-r--r--webapp/components/navbar.jsx11
-rw-r--r--webapp/components/popover_list_members.jsx6
-rw-r--r--webapp/components/rhs_comment.jsx12
-rw-r--r--webapp/components/rhs_root_post.jsx5
-rw-r--r--webapp/components/rhs_thread.jsx42
-rw-r--r--webapp/components/search_results.jsx3
-rw-r--r--webapp/components/sidebar.jsx18
-rw-r--r--webapp/components/sidebar_right.jsx65
-rw-r--r--webapp/components/spinner_button.jsx48
-rw-r--r--webapp/components/team_settings_modal.jsx8
-rw-r--r--webapp/components/tutorial/tutorial_intro_screens.jsx19
-rw-r--r--webapp/components/tutorial/tutorial_tip.jsx11
-rw-r--r--webapp/components/user_list.jsx22
-rw-r--r--webapp/components/user_list_row.jsx9
-rw-r--r--webapp/components/user_settings/user_settings_advanced.jsx46
-rw-r--r--webapp/components/user_settings/user_settings_display.jsx40
-rw-r--r--webapp/components/user_settings/user_settings_modal.jsx22
31 files changed, 420 insertions, 325 deletions
diff --git a/webapp/components/about_build_modal.jsx b/webapp/components/about_build_modal.jsx
index e2fefc44e..e73d842d0 100644
--- a/webapp/components/about_build_modal.jsx
+++ b/webapp/components/about_build_modal.jsx
@@ -24,7 +24,7 @@ export default class AboutBuildModal extends React.Component {
let title = (
<FormattedMessage
id='about.teamEditiont0'
- defaultMessage='Team Edition T0'
+ defaultMessage='Team Edition'
/>
);
@@ -33,14 +33,14 @@ export default class AboutBuildModal extends React.Component {
title = (
<FormattedMessage
id='about.teamEditiont1'
- defaultMessage='Team Edition T1'
+ defaultMessage='Enterprise Edition'
/>
);
if (license.IsLicensed === 'true') {
title = (
<FormattedMessage
id='about.enterpriseEditione1'
- defaultMessage='Enterprise Edition E1'
+ defaultMessage='Enterprise Edition'
/>
);
licensee = (
diff --git a/webapp/components/access_history_modal.jsx b/webapp/components/access_history_modal.jsx
index 94a10c97f..9c49c3879 100644
--- a/webapp/components/access_history_modal.jsx
+++ b/webapp/components/access_history_modal.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
import $ from 'jquery';
-import ReactDOM from 'react-dom';
import {Modal} from 'react-bootstrap';
import LoadingScreen from './loading_screen.jsx';
import AuditTable from './audit_table.jsx';
@@ -36,11 +35,8 @@ class AccessHistoryModal extends React.Component {
}
onShow() {
AsyncClient.getAudits();
-
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- } else {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150);
+ if (!Utils.isMobile()) {
+ $('.modal-body').perfectScrollbar();
}
}
onHide() {
diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx
index 9a4ff3ef2..f1dd4a26a 100644
--- a/webapp/components/activity_log_modal.jsx
+++ b/webapp/components/activity_log_modal.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
import $ from 'jquery';
-import ReactDOM from 'react-dom';
import UserStore from 'stores/user_store.jsx';
import * as Client from 'utils/client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
@@ -56,11 +55,8 @@ export default class ActivityLogModal extends React.Component {
}
onShow() {
AsyncClient.getSessions();
-
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- } else {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 150);
+ if (!Utils.isMobile()) {
+ $('.modal-body').perfectScrollbar();
}
}
onHide() {
diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx
index 5aa0dba7e..ad310d8e0 100644
--- a/webapp/components/admin_console/license_settings.jsx
+++ b/webapp/components/admin_console/license_settings.jsx
@@ -105,36 +105,27 @@ class LicenseSettings extends React.Component {
let licenseType;
let licenseKey;
+ const issued = Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10)) + ' ' + Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true);
+ const startsAt = Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10));
+ const expiresAt = Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10));
+
if (global.window.mm_license.IsLicensed === 'true') {
- edition = (
- <FormattedMessage
- id='admin.license.enterpriseEdition'
- defaultMessage='Mattermost Enterprise Edition. Designed for enterprise-scale communication.'
- />
- );
+ // Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
+ edition = 'Mattermost Enterprise Edition. Enterprise features on this server have been unlocked with a license key and a valid subscription.';
licenseType = (
- <FormattedHTMLMessage
- id='admin.license.enterpriseType'
- values={{
- terms: global.window.mm_config.TermsOfServiceLink,
- name: global.window.mm_license.Name,
- company: global.window.mm_license.Company,
- users: global.window.mm_license.Users,
- issued: Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10)) + ' ' + Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true),
- start: Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10)),
- expires: Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10)),
- ldap: global.window.mm_license.LDAP
- }}
- defaultMessage='<div><p>This compiled release of Mattermost platform is provided under a <a href="http://mattermost.com" target="_blank">commercial license</a> from Mattermost, Inc. based on your subscription level and is subject to the <a href="{terms}" target="_blank">Terms of Service.</a></p>
- <p>Your subscription details are as follows:</p>
- Name: {name}<br />
- Company or organization name: {company}<br/>
- Number of users: {users}<br/>
- License issued: {issued}<br/>
- Start date of license: {start}<br/>
- Expiry date of license: {expires}<br/>
- LDAP: {ldap}<br/></div>'
- />
+ <div>
+ <p>
+ {'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.\n\nYour subscription details are as follows:'}
+ </p>
+ {`Name: ${global.window.mm_license.Name}`}<br/>
+ {`Company or organization name: ${global.window.mm_license.Company}`}<br/>
+ {`Number of users: ${global.window.mm_license.Users}`}<br/>
+ {`License issued: ${issued}`}<br/>
+ {`Start date of license: ${startsAt}`}<br/>
+ {`Expiry date of license: ${expiresAt}`}<br/>
+ <br/>
+ {'See also '}<a href='https://about.mattermost.com/enterprise-edition-terms/'>{'Enterprise Edition Terms of Service'}</a>{' and '}<a href='https://about.mattermost.com/privacy/'>{'Privacy Policy.'}</a>
+ </div>
);
licenseKey = (
@@ -162,20 +153,15 @@ class LicenseSettings extends React.Component {
</div>
);
} else {
+ // Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
edition = (
- <FormattedMessage
- id='admin.license.teamEdition'
- defaultMessage='Mattermost Team Edition. Designed for teams from 5 to 50 users.'
- />
+ <p>
+ {'Mattermost Enterprise Edition. Unlock enterprise features in this software through the purchase of a subscription from '}
+ <a href='https://mattermost.com/'>{'https://mattermost.com/'}</a>
+ </p>
);
- licenseType = (
- <FormattedHTMLMessage
- id='admin.license.teamType'
- defaultMessage='<span><p>This compiled release of Mattermost platform is offered under an MIT license.</p>
- <p>See MIT-COMPILED-LICENSE.txt in your root install directory for details. See NOTICES.txt for information about open source software used in this system.</p></span>'
- />
- );
+ licenseType = 'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.';
let fileName;
if (this.state.fileName) {
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 7cd713942..369fa2dbb 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -80,6 +80,7 @@ export default class ChannelHeader extends React.Component {
SearchStore.addSearchChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
+ $('.sidebar--left .dropdown-menu').perfectScrollbar();
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx
new file mode 100644
index 000000000..e4af9f9ce
--- /dev/null
+++ b/webapp/components/channel_invite_button.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import * as Client from 'utils/client.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import SpinnerButton from 'components/spinner_button.jsx';
+
+export default class ChannelInviteButton extends React.Component {
+ static get propTypes() {
+ return {
+ user: React.PropTypes.object.isRequired,
+ channel: React.PropTypes.object.isRequired,
+ onInviteError: React.PropTypes.func.isRequired
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+
+ this.state = {
+ addingUser: false
+ };
+ }
+
+ handleClick() {
+ if (this.state.addingUser) {
+ return;
+ }
+
+ this.setState({
+ addingUser: true
+ });
+
+ const data = {
+ user_id: this.props.user.id
+ };
+
+ Client.addChannelMember(
+ this.props.channel.id,
+ data,
+ () => {
+ this.setState({
+ addingUser: false
+ });
+
+ this.props.onInviteError(null);
+ AsyncClient.getChannelExtraInfo();
+ },
+ (err) => {
+ this.setState({
+ addingUser: false
+ });
+
+ this.props.onInviteError(err);
+ }
+ );
+ }
+
+ render() {
+ return (
+ <SpinnerButton
+ onClick={this.handleClick}
+ spinning={this.state.addingUser}
+ >
+ <i className='glyphicon glyphicon-envelope'/>
+ <FormattedMessage
+ id='channel_invite.add'
+ defaultMessage=' Add'
+ />
+ </SpinnerButton>
+ );
+ }
+}
diff --git a/webapp/components/channel_invite_modal.jsx b/webapp/components/channel_invite_modal.jsx
index dfb0d4187..c7c1906a5 100644
--- a/webapp/components/channel_invite_modal.jsx
+++ b/webapp/components/channel_invite_modal.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import $ from 'jquery';
+import ChannelInviteButton from './channel_invite_button.jsx';
import FilteredUserList from './filtered_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
@@ -9,7 +10,6 @@ import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import * as Client from 'utils/client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {FormattedMessage} from 'react-intl';
@@ -23,9 +23,8 @@ export default class ChannelInviteModal extends React.Component {
super(props);
this.onListenerChange = this.onListenerChange.bind(this);
- this.handleInvite = this.handleInvite.bind(this);
this.getStateFromStores = this.getStateFromStores.bind(this);
- this.createInviteButton = this.createInviteButton.bind(this);
+ this.handleInviteError = this.handleInviteError.bind(this);
this.state = this.getStateFromStores();
}
@@ -120,36 +119,16 @@ export default class ChannelInviteModal extends React.Component {
this.setState(newState);
}
}
- handleInvite(user) {
- const data = {
- user_id: user.id
- };
-
- Client.addChannelMember(
- this.props.channel.id,
- data,
- () => {
- this.setState({inviteError: null});
- AsyncClient.getChannelExtraInfo();
- },
- (err) => {
- this.setState({inviteError: err.message});
- }
- );
- }
- createInviteButton({user}) {
- return (
- <a
- onClick={this.handleInvite.bind(this, user)}
- className='btn btn-sm btn-primary'
- >
- <i className='glyphicon glyphicon-envelope'/>
- <FormattedMessage
- id='channel_invite.add'
- defaultMessage=' Add'
- />
- </a>
- );
+ handleInviteError(err) {
+ if (err) {
+ this.setState({
+ inviteError: err.message
+ });
+ } else {
+ this.setState({
+ inviteError: null
+ });
+ }
}
render() {
var inviteError = null;
@@ -169,7 +148,11 @@ export default class ChannelInviteModal extends React.Component {
<FilteredUserList
style={{maxHeight}}
users={this.state.nonmembers}
- actions={[this.createInviteButton]}
+ actions={[ChannelInviteButton]}
+ actionProps={{
+ channel: this.props.channel,
+ onInviteError: this.handleInviteError
+ }}
/>
);
}
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index 0aeb70c57..177f282d3 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -6,7 +6,6 @@ import ReactDOM from 'react-dom';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Client from 'utils/client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import SocketStore from 'stores/socket_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import PostDeletedModal from './post_deleted_modal.jsx';
@@ -17,6 +16,7 @@ import MsgTyping from './msg_typing.jsx';
import FileUpload from './file_upload.jsx';
import FilePreview from './file_preview.jsx';
import * as Utils from 'utils/utils.jsx';
+import * as GlobalActions from 'action_creators/global_actions.jsx';
import Constants from 'utils/constants.jsx';
@@ -196,11 +196,7 @@ class CreateComment extends React.Component {
}
}
- const t = Date.now();
- if ((t - this.lastTime) > Constants.UPDATE_TYPING_MS) {
- SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}});
- this.lastTime = t;
- }
+ GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId);
}
handleUserInput(messageText) {
let draft = PostStore.getCommentDraft(this.props.rootId);
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index 36bfbf22d..e5e99debd 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -19,7 +19,6 @@ 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';
-import SocketStore from 'stores/socket_store.jsx';
import Constants from 'utils/constants.jsx';
@@ -213,11 +212,7 @@ class CreatePost extends React.Component {
}
}
- const t = Date.now();
- if ((t - this.lastTime) > Constants.UPDATE_TYPING_MS) {
- SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}});
- this.lastTime = t;
- }
+ GlobalActions.emitLocalUserTypingEvent(this.state.channelId, '');
}
handleUserInput(messageText) {
this.setState({messageText});
diff --git a/webapp/components/filtered_user_list.jsx b/webapp/components/filtered_user_list.jsx
index 1e4afd2be..bd6c49714 100644
--- a/webapp/components/filtered_user_list.jsx
+++ b/webapp/components/filtered_user_list.jsx
@@ -114,6 +114,7 @@ class FilteredUserList extends React.Component {
<UserList
users={users}
actions={this.props.actions}
+ actionProps={this.props.actionProps}
/>
</div>
</div>
@@ -123,13 +124,15 @@ class FilteredUserList extends React.Component {
FilteredUserList.defaultProps = {
users: [],
- actions: []
+ actions: [],
+ actionProps: {}
};
FilteredUserList.propTypes = {
intl: intlShape.isRequired,
users: React.PropTypes.arrayOf(React.PropTypes.object),
actions: React.PropTypes.arrayOf(React.PropTypes.func),
+ actionProps: React.PropTypes.object,
style: React.PropTypes.object
};
diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx
index d567183ac..1f8fd6133 100644
--- a/webapp/components/invite_member_modal.jsx
+++ b/webapp/components/invite_member_modal.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -176,12 +175,6 @@ class InviteMemberModal extends React.Component {
});
}
- componentDidUpdate(prevProps, prevState) {
- if (!prevState.show && this.state.show) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- }
- }
-
addInviteFields() {
var count = this.state.idCount + 1;
var inviteIds = this.state.inviteIds;
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
index 6d35ff8c2..c6f7b50b1 100644
--- a/webapp/components/logged_in.jsx
+++ b/webapp/components/logged_in.jsx
@@ -5,12 +5,13 @@ import $ from 'jquery';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import UserStore from 'stores/user_store.jsx';
-import SocketStore from 'stores/socket_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import BrowserStore from 'stores/browser_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import ErrorBar from 'components/error_bar.jsx';
+import * as Websockets from 'action_creators/websocket_actions.jsx';
import {browserHistory} from 'react-router';
@@ -66,11 +67,6 @@ export default class LoggedIn extends React.Component {
}
}
}
- onSocketChange(msg) {
- if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
- UserStore.setStatus(msg.user_id, 'online');
- }
- }
componentWillMount() {
// Emit view action
GlobalActions.viewLoggedIn();
@@ -78,8 +74,8 @@ export default class LoggedIn extends React.Component {
// Listen for user
UserStore.addChangeListener(this.onUserChanged);
- // Add listner for socker store
- SocketStore.addChangeListener(this.onSocketChange);
+ // Initalize websockets
+ Websockets.initialize();
// Get all statuses regularally. (Soon to be switched to websocket)
this.intervalId = setInterval(() => AsyncClient.getStatuses(), CLIENT_STATUS_INTERVAL);
@@ -89,7 +85,7 @@ export default class LoggedIn extends React.Component {
// when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
// make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
+ if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
return;
}
@@ -99,7 +95,7 @@ export default class LoggedIn extends React.Component {
if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
// make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (window.BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
+ if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
return;
}
@@ -178,7 +174,7 @@ export default class LoggedIn extends React.Component {
$(window).off('focus');
$(window).off('blur');
- SocketStore.removeChangeListener(this.onSocketChange);
+ Websockets.close();
UserStore.removeChangeListener(this.onUserChanged);
$('body').off('click.userpopover');
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx
index 57cac7229..d1446059d 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels.jsx
@@ -5,9 +5,9 @@ import {Modal} from 'react-bootstrap';
import FilteredUserList from './filtered_user_list.jsx';
import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
-import loadingGif from 'images/load.gif';
import {FormattedMessage} from 'react-intl';
+import SpinnerButton from 'components/spinner_button.jsx';
import React from 'react';
@@ -83,26 +83,16 @@ export default class MoreDirectChannels extends React.Component {
}
createJoinDirectChannelButton({user}) {
- if (this.state.loadingDMChannel === user.id) {
- return (
- <img
- className='channel-loading-gif'
- src={loadingGif}
- />
- );
- }
-
return (
- <button
- type='button'
- className='btn btn-primary btn-message'
+ <SpinnerButton
+ spinning={this.state.loadingDMChannel === user.id}
onClick={this.handleShowDirectChannel.bind(this, user)}
>
<FormattedMessage
id='more_direct_channels.message'
defaultMessage='Message'
/>
- </button>
+ </SpinnerButton>
);
}
diff --git a/webapp/components/msg_typing.jsx b/webapp/components/msg_typing.jsx
index b1781623c..b2d414287 100644
--- a/webapp/components/msg_typing.jsx
+++ b/webapp/components/msg_typing.jsx
@@ -1,21 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import SocketStore from 'stores/socket_store.jsx';
-import UserStore from 'stores/user_store.jsx';
+import UserTypingStore from 'stores/user_typing_store.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const SocketEvents = Constants.SocketEvents;
-
-const holders = defineMessages({
- someone: {
- id: 'msg_typing.someone',
- defaultMessage: 'Someone'
- }
-});
+import {FormattedMessage} from 'react-intl';
import React from 'react';
@@ -23,69 +11,40 @@ class MsgTyping extends React.Component {
constructor(props) {
super(props);
- this.onChange = this.onChange.bind(this);
+ this.onTypingChange = this.onTypingChange.bind(this);
this.updateTypingText = this.updateTypingText.bind(this);
this.componentWillReceiveProps = this.componentWillReceiveProps.bind(this);
- this.typingUsers = {};
this.state = {
text: ''
};
}
- componentDidMount() {
- SocketStore.addChangeListener(this.onChange);
+ componentWillMount() {
+ UserTypingStore.addChangeListener(this.onTypingChange);
+ this.onTypingChange();
+ }
+
+ componentWillUnmount() {
+ UserTypingStore.removeChangeListener(this.onTypingChange);
}
componentWillReceiveProps(nextProps) {
if (this.props.channelId !== nextProps.channelId) {
- for (const u in this.typingUsers) {
- if (!this.typingUsers.hasOwnProperty(u)) {
- continue;
- }
-
- clearTimeout(this.typingUsers[u]);
- }
- this.typingUsers = {};
- this.setState({text: ''});
+ this.updateTypingText(UserTypingStore.getUsersTyping(nextProps.channelId, nextProps.parentId));
}
}
- componentWillUnmount() {
- SocketStore.removeChangeListener(this.onChange);
+ onTypingChange() {
+ this.updateTypingText(UserTypingStore.getUsersTyping(this.props.channelId, this.props.parentId));
}
- onChange(msg) {
- let username = this.props.intl.formatMessage(holders.someone);
- if (msg.action === SocketEvents.TYPING &&
- this.props.channelId === msg.channel_id &&
- this.props.parentId === msg.props.parent_id) {
- if (UserStore.hasProfile(msg.user_id)) {
- username = UserStore.getProfile(msg.user_id).username;
- }
-
- if (this.typingUsers[username]) {
- clearTimeout(this.typingUsers[username]);
- }
-
- this.typingUsers[username] = setTimeout(function myTimer(user) {
- delete this.typingUsers[user];
- this.updateTypingText();
- }.bind(this, username), Constants.UPDATE_TYPING_MS);
-
- this.updateTypingText();
- } else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) {
- if (UserStore.hasProfile(msg.user_id)) {
- username = UserStore.getProfile(msg.user_id).username;
- }
- clearTimeout(this.typingUsers[username]);
- delete this.typingUsers[username];
- this.updateTypingText();
+ updateTypingText(typingUsers) {
+ if (!typingUsers) {
+ return;
}
- }
- updateTypingText() {
- const users = Object.keys(this.typingUsers);
+ const users = Object.keys(typingUsers);
let text = '';
switch (users.length) {
case 0:
@@ -129,9 +88,8 @@ class MsgTyping extends React.Component {
}
MsgTyping.propTypes = {
- intl: intlShape.isRequired,
channelId: React.PropTypes.string,
parentId: React.PropTypes.string
};
-export default injectIntl(MsgTyping); \ No newline at end of file
+export default MsgTyping;
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index fb3b25957..520f05ed0 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -8,6 +8,7 @@ import MessageWrapper from './message_wrapper.jsx';
import NotifyCounts from './notify_counts.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
+import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';
import RenameChannelModal from './rename_channel_modal.jsx';
@@ -433,6 +434,7 @@ export default class Navbar extends React.Component {
var editChannelHeaderModal = null;
var editChannelPurposeModal = null;
let renameChannelModal = null;
+ let channelMembersModal = null;
if (channel) {
popoverContent = (
@@ -523,6 +525,14 @@ export default class Navbar extends React.Component {
channel={channel}
/>
);
+
+ channelMembersModal = (
+ <ChannelMembersModal
+ show={this.state.showMembersModal}
+ onModalDismissed={() => this.setState({showMembersModal: false})}
+ channel={channel}
+ />
+ );
}
var collapseButtons = this.createCollapseButtons(currentId);
@@ -556,6 +566,7 @@ export default class Navbar extends React.Component {
{editChannelHeaderModal}
{editChannelPurposeModal}
{renameChannelModal}
+ {channelMembersModal}
</div>
);
}
diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx
index 7d048019c..819c7f590 100644
--- a/webapp/components/popover_list_members.jsx
+++ b/webapp/components/popover_list_members.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import $ from 'jquery';
+
import UserStore from 'stores/user_store.jsx';
import {Popover, Overlay} from 'react-bootstrap';
import * as Utils from 'utils/utils.jsx';
@@ -20,6 +22,10 @@ export default class PopoverListMembers extends React.Component {
this.closePopover = this.closePopover.bind(this);
}
+ componentDidUpdate() {
+ $('.member-list__popover .popover-content').perfectScrollbar();
+ }
+
componentWillMount() {
this.setState({showPopover: false});
}
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 29986d415..de99eb37d 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -5,7 +5,6 @@ import ReactDOM from 'react-dom';
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserProfile from './user_profile.jsx';
-import UserStore from 'stores/user_store.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -97,8 +96,8 @@ class RhsComment extends React.Component {
return '';
}
- var isOwner = UserStore.getCurrentId() === post.user_id;
- var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
+ const isOwner = this.props.currentUser.id === post.user_id;
+ const isAdmin = Utils.isAdmin(this.props.currentUser.roles);
var dropdownContents = [];
@@ -193,11 +192,11 @@ class RhsComment extends React.Component {
var post = this.props.post;
var currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id) {
+ if (this.props.currentUser === post.user_id) {
currentUserCss = 'current--user';
}
- var timestamp = UserStore.getCurrentUser().update_at;
+ var timestamp = this.props.currentUser.update_at;
let loading;
let postClass = '';
@@ -305,7 +304,8 @@ RhsComment.defaultProps = {
RhsComment.propTypes = {
intl: intlShape.isRequired,
post: React.PropTypes.object,
- user: React.PropTypes.object
+ user: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object.isRequired
};
export default injectIntl(RhsComment);
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index a2c7ee7f8..1aa4a555f 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -55,8 +55,8 @@ export default class RhsRootPost extends React.Component {
render() {
const post = this.props.post;
const user = this.props.user;
- var isOwner = user.id === post.user_id;
- var isAdmin = Utils.isAdmin(user.roles);
+ var isOwner = this.props.currentUser.id === post.user_id;
+ var isAdmin = Utils.isAdmin(this.props.currentUser.roles);
var timestamp = UserStore.getProfile(post.user_id).update_at;
var channel = ChannelStore.get(post.channel_id);
@@ -286,5 +286,6 @@ RhsRootPost.defaultProps = {
RhsRootPost.propTypes = {
post: React.PropTypes.object.isRequired,
user: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object.isRequired,
commentCount: React.PropTypes.number
};
diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx
index cc900f8e7..f0324d7ce 100644
--- a/webapp/components/rhs_thread.jsx
+++ b/webapp/components/rhs_thread.jsx
@@ -46,6 +46,9 @@ export default class RhsThread extends React.Component {
window.addEventListener('resize', this.handleResize);
this.mounted = true;
+ if (!Utils.isMobile()) {
+ $('.sidebar--right .post-right__scroll').perfectScrollbar();
+ }
}
componentDidUpdate() {
if ($('.post-right__scroll')[0]) {
@@ -130,7 +133,7 @@ export default class RhsThread extends React.Component {
}
// sort failed posts to bottom, followed by pending, and then regular posts
- postsArray.sort(function postSort(a, b) {
+ postsArray.sort((a, b) => {
if ((a.state === Constants.POST_LOADING || a.state === Constants.POST_FAILED) && (b.state !== Constants.POST_LOADING && b.state !== Constants.POST_FAILED)) {
return 1;
}
@@ -182,24 +185,26 @@ export default class RhsThread extends React.Component {
post={selected}
commentCount={postsArray.length}
user={profile}
+ currentUser={this.props.currentUser}
/>
<div className='post-right-comments-container'>
- {postsArray.map(function mapPosts(comPost) {
- let p;
- if (UserStore.getCurrentId() === comPost.user_id) {
- p = UserStore.getCurrentUser();
- } else {
- p = profiles[comPost.user_id];
- }
- return (
- <Comment
- ref={comPost.id}
- key={comPost.id + 'commentKey'}
- post={comPost}
- user={p}
- />
- );
- })}
+ {postsArray.map((comPost) => {
+ let p;
+ if (UserStore.getCurrentId() === comPost.user_id) {
+ p = UserStore.getCurrentUser();
+ } else {
+ p = profiles[comPost.user_id];
+ }
+ return (
+ <Comment
+ ref={comPost.id}
+ key={comPost.id + 'commentKey'}
+ post={comPost}
+ user={p}
+ currentUser={this.props.currentUser}
+ />
+ );
+ })}
</div>
<div className='post-create__container'>
<CreateComment
@@ -221,5 +226,6 @@ RhsThread.defaultProps = {
RhsThread.propTypes = {
fromSearch: React.PropTypes.string,
- isMentionSearch: React.PropTypes.bool
+ isMentionSearch: React.PropTypes.bool,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx
index 7619e41cd..c5baf50ef 100644
--- a/webapp/components/search_results.jsx
+++ b/webapp/components/search_results.jsx
@@ -61,6 +61,9 @@ export default class SearchResults extends React.Component {
UserStore.addChangeListener(this.onUserChange);
this.resize();
window.addEventListener('resize', this.handleResize);
+ if (!Utils.isMobile()) {
+ $('.sidebar--right .search-items-container').perfectScrollbar();
+ }
}
shouldComponentUpdate(nextProps, nextState) {
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index 49ae1bec6..0e1b7dd0e 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -93,12 +93,12 @@ export default class Sidebar extends React.Component {
const preferences = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
const directChannels = [];
- for (const preference of preferences) {
- if (preference.value !== 'true') {
+ for (const [name, value] of preferences) {
+ if (value !== 'true') {
continue;
}
- const teammateId = preference.name;
+ const teammateId = name;
let directChannel = channels.find(Utils.isDirectChannelForUser.bind(null, teammateId));
@@ -163,6 +163,9 @@ export default class Sidebar extends React.Component {
componentDidUpdate() {
this.updateTitle();
this.updateUnreadIndicators();
+ if (!Utils.isMobile()) {
+ $('.sidebar--left .nav-pills__container').perfectScrollbar();
+ }
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
@@ -239,11 +242,10 @@ export default class Sidebar extends React.Component {
if (!this.isLeaving.get(channel.id)) {
this.isLeaving.set(channel.id, true);
- const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, channel.teammate_id, 'false');
-
- // bypass AsyncClient since we've already saved the updated preferences
- Client.savePreferences(
- [preference],
+ AsyncClient.savePreference(
+ Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
+ channel.teammate_id,
+ 'false',
() => {
this.isLeaving.set(channel.id, false);
},
diff --git a/webapp/components/sidebar_right.jsx b/webapp/components/sidebar_right.jsx
index 1b3286963..a2e3914f3 100644
--- a/webapp/components/sidebar_right.jsx
+++ b/webapp/components/sidebar_right.jsx
@@ -8,6 +8,7 @@ import SearchResults from './search_results.jsx';
import RhsThread from './rhs_thread.jsx';
import SearchStore from 'stores/search_store.jsx';
import PostStore from 'stores/post_store.jsx';
+import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
const SIDEBAR_SCROLL_DELAY = 500;
@@ -22,33 +23,38 @@ export default class SidebarRight extends React.Component {
this.onSelectedChange = this.onSelectedChange.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.onShowSearch = this.onShowSearch.bind(this);
this.doStrangeThings = this.doStrangeThings.bind(this);
- this.state = this.getStateFromStores();
- }
- getStateFromStores() {
- return {
- search_visible: SearchStore.getSearchResults() != null,
- post_right_visible: PostStore.getSelectedPost() != null,
- is_mention_search: SearchStore.getIsMentionSearch()
+ this.state = {
+ searchVisible: !!SearchStore.getSearchResults(),
+ isMentionSearch: SearchStore.getIsMentionSearch(),
+ postRightVisible: !!PostStore.getSelectedPost(),
+ fromSearch: false,
+ currentUser: UserStore.getCurrentUser()
};
}
componentDidMount() {
SearchStore.addSearchChangeListener(this.onSearchChange);
PostStore.addSelectedPostChangeListener(this.onSelectedChange);
SearchStore.addShowSearchListener(this.onShowSearch);
+ UserStore.addChangeListener(this.onUserChange);
this.doStrangeThings();
}
componentWillUnmount() {
SearchStore.removeSearchChangeListener(this.onSearchChange);
PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
SearchStore.removeShowSearchListener(this.onShowSearch);
+ UserStore.removeChangeListener(this.onUserChange);
+ }
+ shouldComponentUpdate(nextProps, nextState) {
+ return !Utils.areObjectsEqual(nextState, this.state);
}
componentWillUpdate(nextProps, nextState) {
- const isOpen = this.state.search_visible || this.state.post_right_visible;
- const willOpen = nextState.search_visible || nextState.post_right_visible;
+ const isOpen = this.state.searchVisible || this.state.postRightVisible;
+ const willOpen = nextState.searchVisible || nextState.postRightVisible;
if (!isOpen && willOpen) {
setTimeout(() => PostStore.jumpPostsViewSidebarOpen(), SIDEBAR_SCROLL_DELAY);
@@ -66,7 +72,7 @@ export default class SidebarRight extends React.Component {
$('.sidebar--right').addClass('move--left');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
- if (this.state.search_visible || this.state.post_right_visible) {
+ if (this.state.searchVisible || this.state.postRightVisible) {
if (windowWidth > 960) {
velocity($('.inner-wrap'), {marginRight: sidebarRightWidth}, {duration: 500, easing: 'easeOutSine'});
velocity($('.sidebar--right'), {translateX: 0}, {duration: 500, easing: 'easeOutSine'});
@@ -98,35 +104,40 @@ export default class SidebarRight extends React.Component {
this.doStrangeThings();
}
onSelectedChange(fromSearch) {
- var newState = this.getStateFromStores(fromSearch);
- newState.from_search = fromSearch;
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ this.setState({
+ postRightVisible: !!PostStore.getSelectedPost(),
+ fromSearch
+ });
}
onSearchChange() {
- var newState = this.getStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ this.setState({
+ searchVisible: !!SearchStore.getSearchResults(),
+ isMentionSearch: SearchStore.getIsMentionSearch()
+ });
+ }
+ onUserChange() {
+ this.setState({
+ currentUser: UserStore.getCurrentUser()
+ });
}
onShowSearch() {
- if (!this.state.search_visible) {
+ if (!this.state.searchVisible) {
this.setState({
- search_visible: true
+ searchVisible: true
});
}
}
render() {
- var content = '';
+ let content = null;
- if (this.state.search_visible) {
- content = <SearchResults isMentionSearch={this.state.is_mention_search}/>;
- } else if (this.state.post_right_visible) {
+ if (this.state.searchVisible) {
+ content = <SearchResults isMentionSearch={this.state.isMentionSearch}/>;
+ } else if (this.state.postRightVisible) {
content = (
<RhsThread
- fromSearch={this.state.from_search}
- isMentionSearch={this.state.is_mention_search}
+ fromSearch={this.state.fromSearch}
+ isMentionSearch={this.state.isMentionSearch}
+ currentUser={this.state.currentUser}
/>
);
}
diff --git a/webapp/components/spinner_button.jsx b/webapp/components/spinner_button.jsx
new file mode 100644
index 000000000..fcc9af8cd
--- /dev/null
+++ b/webapp/components/spinner_button.jsx
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import loadingGif from 'images/load.gif';
+
+export default class SpinnerButton extends React.Component {
+ static get propTypes() {
+ return {
+ children: React.PropTypes.node,
+ spinning: React.PropTypes.bool.isRequired,
+ onClick: React.PropTypes.func
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick(e) {
+ if (this.props.onClick) {
+ this.props.onClick(e);
+ }
+ }
+
+ render() {
+ if (this.props.spinning) {
+ return (
+ <img
+ className='spinner-button__gif'
+ src={loadingGif}
+ />
+ );
+ }
+
+ return (
+ <button
+ onClick={this.handleClick}
+ className='btn btn-sm btn-primary'
+ >
+ {this.props.children}
+ </button>
+ );
+ }
+}
diff --git a/webapp/components/team_settings_modal.jsx b/webapp/components/team_settings_modal.jsx
index 7dbbd680a..c19787993 100644
--- a/webapp/components/team_settings_modal.jsx
+++ b/webapp/components/team_settings_modal.jsx
@@ -5,6 +5,7 @@ import $ from 'jquery';
import ReactDOM from 'react-dom';
import SettingsSidebar from './settings_sidebar.jsx';
import TeamSettings from './team_settings.jsx';
+import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
@@ -49,9 +50,16 @@ class TeamSettingsModal extends React.Component {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
+
+ if (!Utils.isMobile()) {
+ $('.settings-modal .settings-content').perfectScrollbar();
+ }
}
updateTab(tab) {
this.setState({activeTab: tab, activeSection: ''});
+ if (!Utils.isMobile()) {
+ $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
+ }
}
updateSection(section) {
this.setState({activeSection: section});
diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx
index 5db45523e..734842cad 100644
--- a/webapp/components/tutorial/tutorial_intro_screens.jsx
+++ b/webapp/components/tutorial/tutorial_intro_screens.jsx
@@ -36,17 +36,22 @@ export default class TutorialIntroScreens extends React.Component {
Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL));
- let preference = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ let step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
- const newValue = (parseInt(preference.value, 10) + 1).toString();
-
- preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), newValue);
- AsyncClient.savePreferences([preference]);
+ AsyncClient.savePreference(
+ Preferences.TUTORIAL_STEP,
+ UserStore.getCurrentId(),
+ step + 1
+ );
}
skipTutorial(e) {
e.preventDefault();
- const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999');
- AsyncClient.savePreferences([preference]);
+
+ AsyncClient.savePreference(
+ Preferences.TUTORIAL_STEP,
+ UserStore.getCurrentId(),
+ 999
+ );
}
createScreen() {
switch (this.state.currentScreen) {
diff --git a/webapp/components/tutorial/tutorial_tip.jsx b/webapp/components/tutorial/tutorial_tip.jsx
index ab49d4b04..d93fff1b1 100644
--- a/webapp/components/tutorial/tutorial_tip.jsx
+++ b/webapp/components/tutorial/tutorial_tip.jsx
@@ -29,12 +29,13 @@ export default class TutorialTip extends React.Component {
this.setState({show});
if (!show && this.state.currentScreen >= this.props.screens.length - 1) {
- let preference = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ let step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
- const newValue = (parseInt(preference.value, 10) + 1).toString();
-
- preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), newValue);
- AsyncClient.savePreferences([preference]);
+ AsyncClient.savePreference(
+ Preferences.TUTORIAL_STEP,
+ UserStore.getCurrentId(),
+ step + 1
+ );
}
}
handleNext() {
diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx
index 2140158e6..3652723be 100644
--- a/webapp/components/user_list.jsx
+++ b/webapp/components/user_list.jsx
@@ -18,16 +18,22 @@ export default class UserList extends React.Component {
key={user.id}
user={user}
actions={this.props.actions}
+ actionProps={this.props.actionProps}
/>
);
});
} else {
content = (
- <div key='no-users-found'>
- <FormattedMessage
- id='user_list.notFound'
- defaultMessage='No users found :('
- />
+ <div
+ key='no-users-found'
+ className='no-channel-message'
+ >
+ <p className='primary-message'>
+ <FormattedMessage
+ id='user_list.notFound'
+ defaultMessage='No users found :('
+ />
+ </p>
</div>
);
}
@@ -42,10 +48,12 @@ export default class UserList extends React.Component {
UserList.defaultProps = {
users: [],
- actions: []
+ actions: [],
+ actionProps: {}
};
UserList.propTypes = {
users: React.PropTypes.arrayOf(React.PropTypes.object),
- actions: React.PropTypes.arrayOf(React.PropTypes.func)
+ actions: React.PropTypes.arrayOf(React.PropTypes.func),
+ actionProps: React.PropTypes.object
};
diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx
index ed3a29a61..f6fd91688 100644
--- a/webapp/components/user_list_row.jsx
+++ b/webapp/components/user_list_row.jsx
@@ -6,7 +6,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
-export default function UserListRow({user, actions}) {
+export default function UserListRow({user, actions, actionProps}) {
const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', '');
let name = user.username;
@@ -21,6 +21,7 @@ export default function UserListRow({user, actions}) {
<Action
key={index.toString()}
user={user}
+ {...actionProps}
/>
);
});
@@ -56,10 +57,12 @@ export default function UserListRow({user, actions}) {
}
UserListRow.defaultProps = {
- actions: []
+ actions: [],
+ actionProps: {}
};
UserListRow.propTypes = {
user: React.PropTypes.object.isRequired,
- actions: React.PropTypes.arrayOf(React.PropTypes.func)
+ actions: React.PropTypes.arrayOf(React.PropTypes.func),
+ actionProps: React.PropTypes.object
};
diff --git a/webapp/components/user_settings/user_settings_advanced.jsx b/webapp/components/user_settings/user_settings_advanced.jsx
index 7c496f57b..4fcdc9a41 100644
--- a/webapp/components/user_settings/user_settings_advanced.jsx
+++ b/webapp/components/user_settings/user_settings_advanced.jsx
@@ -1,11 +1,12 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from 'utils/client.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import Constants from 'utils/constants.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import UserStore from 'stores/user_store.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
@@ -68,25 +69,27 @@ class AdvancedSettingsDisplay extends React.Component {
const preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures);
const advancedSettings = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS);
const settings = {
- send_on_ctrl_enter: PreferenceStore.getPreference(
+ send_on_ctrl_enter: PreferenceStore.get(
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
'send_on_ctrl_enter',
- {value: 'false'}
- ).value
+ 'false'
+ )
};
let enabledFeatures = 0;
- advancedSettings.forEach((setting) => {
- preReleaseFeaturesKeys.forEach((key) => {
+ for (const [name, value] of advancedSettings) {
+ for (const key of preReleaseFeaturesKeys) {
const feature = PreReleaseFeatures[key];
- if (setting.name === Constants.FeatureTogglePrefix + feature.label) {
- settings[setting.name] = setting.value;
- if (setting.value === 'true') {
- enabledFeatures++;
+
+ if (name === Constants.FeatureTogglePrefix + feature.label) {
+ settings[name] = value;
+
+ if (value === 'true') {
+ enabledFeatures += 1;
}
}
- });
- });
+ }
+ }
this.state = {preReleaseFeatures: PreReleaseFeatures, settings, preReleaseFeaturesKeys, enabledFeatures};
}
@@ -124,20 +127,21 @@ class AdvancedSettingsDisplay extends React.Component {
handleSubmit(settings) {
const preferences = [];
+ const userId = UserStore.getCurrentId();
+ // this should be refactored so we can actually be certain about what type everything is
(Array.isArray(settings) ? settings : [settings]).forEach((setting) => {
- preferences.push(
- PreferenceStore.setPreference(
- Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
- setting,
- String(this.state.settings[setting])
- )
- );
+ preferences.push({
+ user_id: userId,
+ category: Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
+ name: setting,
+ value: this.state.settings[setting]
+ });
});
- Client.savePreferences(preferences,
+ AsyncClient.savePreferences(
+ preferences,
() => {
- PreferenceStore.emitChange();
this.updateSection('');
},
(err) => {
diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx
index 3299588f7..e56156049 100644
--- a/webapp/components/user_settings/user_settings_display.jsx
+++ b/webapp/components/user_settings/user_settings_display.jsx
@@ -6,24 +6,22 @@ import SettingItemMax from '../setting_item_max.jsx';
import ManageLanguages from './manage_languages.jsx';
import ThemeSetting from './user_settings_theme.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as I18n from 'i18n/i18n.jsx';
import Constants from 'utils/constants.jsx';
+const Preferences = Constants.Preferences;
-import {savePreferences} from 'utils/client.jsx';
import {FormattedMessage} from 'react-intl';
function getDisplayStateFromStores() {
- const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'});
- const nameFormat = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'username'});
- const selectedFont = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', {value: Constants.DEFAULT_FONT});
-
return {
- militaryTime: militaryTime.value,
- nameFormat: nameFormat.value,
- selectedFont: selectedFont.value
+ militaryTime: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', 'false'),
+ nameFormat: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'username'),
+ selectedFont: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT)
};
}
@@ -44,13 +42,29 @@ export default class UserSettingsDisplay extends React.Component {
this.state = getDisplayStateFromStores();
}
handleSubmit() {
- const timePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', this.state.militaryTime);
- const namePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', this.state.nameFormat);
- const fontPreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', this.state.selectedFont);
+ const userId = UserStore.getCurrentId();
+
+ const timePreference = {
+ user_id: userId,
+ category: Preferences.CATEGORY_DISPLAY_SETTINGS,
+ name: 'use_military_time',
+ value: this.state.militaryTime
+ };
+ const namePreference = {
+ user_id: userId,
+ category: Preferences.CATEGORY_DISPLAY_SETTINGS,
+ name: 'name_format',
+ value: this.state.nameFormat
+ };
+ const fontPreference = {
+ user_id: userId,
+ category: Preferences.CATEGORY_DISPLAY_SETTINGS,
+ name: 'selected_font',
+ value: this.state.selectedFont
+ };
- savePreferences([timePreference, namePreference, fontPreference],
+ AsyncClient.savePreferences([timePreference, namePreference, fontPreference],
() => {
- PreferenceStore.emitChange();
this.updateSection('');
},
(err) => {
diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx
index bd1df6ea5..d1c1f0fe2 100644
--- a/webapp/components/user_settings/user_settings_modal.jsx
+++ b/webapp/components/user_settings/user_settings_modal.jsx
@@ -64,7 +64,6 @@ class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
- this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
this.handleHidden = this.handleHidden.bind(this);
this.handleCollapse = this.handleCollapse.bind(this);
@@ -95,24 +94,13 @@ class UserSettingsModal extends React.Component {
}
componentDidMount() {
- if (this.props.show) {
- this.handleShow();
- }
UserStore.addChangeListener(this.onUserChanged);
}
- componentDidUpdate(prevProps) {
- if (this.props.show && !prevProps.show) {
- this.handleShow();
- }
+ componentDidUpdate() {
UserStore.removeChangeListener(this.onUserChanged);
- }
-
- handleShow() {
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- } else {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 50);
+ if (!Utils.isMobile()) {
+ $('.settings-modal .modal-body').perfectScrollbar();
}
}
@@ -222,6 +210,10 @@ class UserSettingsModal extends React.Component {
active_section: ''
});
}
+
+ if (!Utils.isMobile()) {
+ $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
+ }
}
updateSection(section, skipConfirm) {