summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/post.go5
-rw-r--r--api/post_test.go4
-rw-r--r--doc/help/Team-Statistics.md24
-rw-r--r--web/react/components/access_history_modal.jsx70
-rw-r--r--web/react/components/activity_log_modal.jsx72
-rw-r--r--web/react/components/channel_header.jsx61
-rw-r--r--web/react/components/channel_invite_modal.jsx131
-rw-r--r--web/react/components/channel_members.jsx200
-rw-r--r--web/react/components/channel_members_modal.jsx193
-rw-r--r--web/react/components/confirm_modal.jsx79
-rw-r--r--web/react/components/invite_member_modal.jsx177
-rw-r--r--web/react/components/navbar.jsx69
-rw-r--r--web/react/components/navbar_dropdown.jsx17
-rw-r--r--web/react/components/posts_view_container.jsx6
-rw-r--r--web/react/components/sidebar_right_menu.jsx28
-rw-r--r--web/react/components/team_settings_modal.jsx6
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx6
-rw-r--r--web/react/components/user_settings/user_settings.jsx40
-rw-r--r--web/react/components/user_settings/user_settings_advanced.jsx23
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx70
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx11
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx20
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx25
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx22
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx215
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx28
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx77
-rw-r--r--web/react/pages/channel.jsx36
-rw-r--r--web/react/stores/modal_store.jsx42
-rw-r--r--web/react/stores/user_store.jsx20
-rw-r--r--web/react/utils/channel_intro_mssages.jsx5
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/sass-files/sass/partials/_settings.scss4
-rw-r--r--web/templates/channel.html2
34 files changed, 996 insertions, 795 deletions
diff --git a/api/post.go b/api/post.go
index 31a7ab3b5..b52db8752 100644
--- a/api/post.go
+++ b/api/post.go
@@ -890,7 +890,10 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
channels := []store.StoreChannel{}
for _, params := range paramsList {
- channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
+ // don't allow users to search for everything
+ if params.Terms != "*" {
+ channels = append(channels, Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, params))
+ }
}
posts := &model.PostList{}
diff --git a/api/post_test.go b/api/post_test.go
index 3452c9788..0cb437e88 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -450,6 +450,10 @@ func TestSearchPosts(t *testing.T) {
if len(r3.Order) != 1 && r3.Order[0] == post3.Id {
t.Fatal("wrong serach")
}
+
+ if r4 := Client.Must(Client.SearchPosts("*")).Data.(*model.PostList); len(r4.Order) != 0 {
+ t.Fatal("searching for just * shouldn't return any results")
+ }
}
func TestSearchHashtagPosts(t *testing.T) {
diff --git a/doc/help/Team-Statistics.md b/doc/help/Team-Statistics.md
new file mode 100644
index 000000000..05d63794b
--- /dev/null
+++ b/doc/help/Team-Statistics.md
@@ -0,0 +1,24 @@
+## Team Statistics
+___
+Statistics on users, posts and channels are tracked for each team and viewable in the System Console. System Administrators can access statistics for your Mattermost teams by clicking the **three-dot menu**, then click **System Console**. Under the *Teams* section on the left side you’ll see a list of the teams that belong to your domain. Click **Statistics** under the team of interest to open the stats page. Here is some helpful terminology:
+
+**Total Users**
+The total number of accounts created, regardless of if the accounts are active or inactive.
+
+**Total Posts**
+The total number of posts made by your team, including deleted posts or those made by incoming and outgoing webhook integrations.
+
+**Public Groups**
+The number of public channels created by your team, including channels that may have been archived.
+
+**Private Group**
+The number of private groups created by your team, including groups that may have been archived.
+
+**Active Users With Posts**
+Users who logged in and made a post on a certain day.
+
+**Recently Active Users**
+Users that have logged in and had recent browser activity in Mattermost.
+
+**Newly Created Users**
+Users that have recently completed the signup process to create a Mattermost account on the team.
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index f0a31ce90..27959ec7e 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var Modal = ReactBootstrap.Modal;
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -30,16 +31,23 @@ export default class AccessHistoryModal extends React.Component {
}
onShow() {
AsyncClient.getAudits();
+
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
}
onHide() {
- $('#user_settings').modal('show');
this.setState({moreInfo: []});
+ this.props.onModalDismissed();
}
componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
}
componentWillUnmount() {
UserStore.removeAuditsChangeListener(this.onAuditChange);
@@ -380,43 +388,23 @@ export default class AccessHistoryModal extends React.Component {
}
return (
- <div>
- <div
- className='modal fade'
- ref='modal'
- id='access-history'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog modal-lg'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- {'Access History'}
- </h4>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
+ <Modal
+ show={this.props.show}
+ onHide={this.onHide}
+ bsSize='large'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Access History'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ {content}
+ </Modal.Body>
+ </Modal>
);
}
}
+
+AccessHistoryModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 2c944913f..6a24870f6 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -4,6 +4,7 @@
const UserStore = require('../stores/user_store.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
+const Modal = ReactBootstrap.Modal;
const LoadingScreen = require('./loading_screen.jsx');
const Utils = require('../utils/utils.jsx');
@@ -49,16 +50,23 @@ export default class ActivityLogModal extends React.Component {
}
onShow() {
AsyncClient.getSessions();
+
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
}
onHide() {
- $('#user_settings').modal('show');
this.setState({moreInfo: []});
+ this.props.onModalDismissed();
}
componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.onShow);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
}
componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
@@ -151,44 +159,24 @@ export default class ActivityLogModal extends React.Component {
}
return (
- <div>
- <div
- className='modal fade'
- ref='modal'
- id='activity-log'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog modal-lg'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- Active Sessions
- </h4>
- </div>
- <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
+ <Modal
+ show={this.props.show}
+ onHide={this.onHide}
+ bsSize='large'
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Active Sessions'}</Modal.Title>
+ </Modal.Header>
+ <p className='session-help-text'>{'Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the \'Logout\' button below to end a session.'}</p>
+ <Modal.Body ref='modalBody'>
+ {content}
+ </Modal.Body>
+ </Modal>
);
}
}
+
+ActivityLogModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 20f106f30..895dc5fe4 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,20 +1,23 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const ChannelStore = require('../stores/channel_store.jsx');
-const UserStore = require('../stores/user_store.jsx');
-const SearchStore = require('../stores/search_store.jsx');
-const PreferenceStore = require('../stores/preference_store.jsx');
const NavbarSearchBox = require('./search_bar.jsx');
-const AsyncClient = require('../utils/async_client.jsx');
-const Client = require('../utils/client.jsx');
-const TextFormatting = require('../utils/text_formatting.jsx');
-const Utils = require('../utils/utils.jsx');
const MessageWrapper = require('./message_wrapper.jsx');
const PopoverListMembers = require('./popover_list_members.jsx');
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+const ChannelMembersModal = require('./channel_members_modal.jsx');
+
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const SearchStore = require('../stores/search_store.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Utils = require('../utils/utils.jsx');
+const TextFormatting = require('../utils/text_formatting.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -31,6 +34,8 @@ export default class ChannelHeader extends React.Component {
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
+ state.showInviteModal = false;
+ state.showMembersModal = false;
this.state = state;
}
getStateFromStores() {
@@ -86,7 +91,7 @@ export default class ChannelHeader extends React.Component {
let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- let termKeys = UserStore.getCurrentMentionKeys();
+ const termKeys = UserStore.getCurrentMentionKeys();
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1);
}
@@ -146,7 +151,7 @@ export default class ChannelHeader extends React.Component {
channelTerm = 'Group';
}
- let dropdownContents = [];
+ const dropdownContents = [];
if (isDirect) {
dropdownContents.push(
<li
@@ -162,7 +167,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set Channel Header...
+ {'Set Channel Header...'}
</a>
</li>
);
@@ -179,7 +184,7 @@ export default class ChannelHeader extends React.Component {
data-channelid={channel.id}
href='#'
>
- View Info
+ {'View Info'}
</a>
</li>
);
@@ -192,11 +197,10 @@ export default class ChannelHeader extends React.Component {
>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_invite'
href='#'
+ onClick={() => this.setState({showInviteModal: true})}
>
- Add Members
+ {'Add Members'}
</a>
</li>
);
@@ -209,11 +213,10 @@ export default class ChannelHeader extends React.Component {
>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_members'
href='#'
+ onClick={() => this.setState({showMembersModal: true})}
>
- Manage Members
+ {'Manage Members'}
</a>
</li>
);
@@ -234,7 +237,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set {channelTerm} Header...
+ {'Set '}{channelTerm}{' Header...'}
</a>
</li>
);
@@ -248,7 +251,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={() => this.setState({showEditChannelPurposeModal: true})}
>
- Set {channelTerm} Purpose...
+ {'Set '}{channelTerm}{' Purpose...'}
</a>
</li>
);
@@ -265,7 +268,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Notification Preferences
+ {'Notification Preferences'}
</a>
</li>
);
@@ -286,7 +289,7 @@ export default class ChannelHeader extends React.Component {
data-name={channel.name}
data-channelid={channel.id}
>
- Rename {channelTerm}...
+ {'Rename '}{channelTerm}{'...'}
</a>
</li>
);
@@ -303,7 +306,7 @@ export default class ChannelHeader extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Delete {channelTerm}...
+ {'Delete '}{channelTerm}{'...'}
</a>
</li>
);
@@ -319,7 +322,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={this.handleLeave}
>
- Leave {channelTerm}
+ {'Leave '}{channelTerm}
</a>
</li>
);
@@ -397,7 +400,7 @@ export default class ChannelHeader extends React.Component {
href='#'
onClick={this.searchMentions}
>
- Recent Mentions
+ {'Recent Mentions'}
</a>
</li>
</ul>
@@ -411,6 +414,14 @@ export default class ChannelHeader extends React.Component {
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
channel={channel}
/>
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
+ <ChannelMembersModal
+ show={this.state.showMembersModal}
+ onModalDismissed={() => this.setState({showMembersModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index e90d1a666..2dc12c9aa 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -1,26 +1,25 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var MemberList = require('./member_list.jsx');
-var LoadingScreen = require('./loading_screen.jsx');
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
+const MemberList = require('./member_list.jsx');
+const LoadingScreen = require('./loading_screen.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+
+const Modal = ReactBootstrap.Modal;
export default class ChannelInviteModal extends React.Component {
constructor() {
super();
- this.componentDidMount = this.componentDidMount.bind(this);
- this.componentWillUnmount = this.componentWillUnmount.bind(this);
- this.onShow = this.onShow.bind(this);
- this.onHide = this.onHide.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleInvite = this.handleInvite.bind(this);
- this.isShown = false;
this.state = this.getStateFromStores();
}
getStateFromStores() {
@@ -39,7 +38,7 @@ export default class ChannelInviteModal extends React.Component {
}
}
- nonmembers.sort(function sortByUsername(a, b) {
+ nonmembers.sort((a, b) => {
return a.username.localeCompare(b.username);
});
@@ -49,35 +48,27 @@ export default class ChannelInviteModal extends React.Component {
}
return {
- nonmembers: nonmembers,
- memberIds: memberIds,
- channelName: channelName,
- loading: loading
+ nonmembers,
+ memberIds,
+ channelName,
+ loading
};
}
- componentDidMount() {
- $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.onHide);
- $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.onShow);
-
- ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.addChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
- }
- componentWillUnmount() {
- ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
- ChannelStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
- }
- onShow() {
- this.isShown = true;
- this.onListenerChange();
- }
- onHide() {
- this.isShown = false;
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.show && nextProps.show) {
+ ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.addChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
+ this.onListenerChange();
+ } else if (this.props.show && !nextProps.show) {
+ ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
+ }
}
onListenerChange() {
var newState = this.getStateFromStores();
- if (!utils.areStatesEqual(this.state, newState) && this.isShown) {
+ if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
}
@@ -90,8 +81,8 @@ export default class ChannelInviteModal extends React.Component {
var data = {};
data.user_id = userId;
- client.addChannelMember(ChannelStore.getCurrentId(), data,
- function sucess() {
+ Client.addChannelMember(ChannelStore.getCurrentId(), data,
+ () => {
var nonmembers = this.state.nonmembers;
var memberIds = this.state.memberIds;
@@ -103,13 +94,12 @@ export default class ChannelInviteModal extends React.Component {
}
}
- this.setState({inviteError: null, memberIds: memberIds, nonmembers: nonmembers});
+ this.setState({inviteError: null, memberIds, nonmembers});
AsyncClient.getChannelExtraInfo(true);
- }.bind(this),
-
- function error(err) {
+ },
+ (err) => {
this.setState({inviteError: err.message});
- }.bind(this)
+ }
);
}
render() {
@@ -121,7 +111,7 @@ export default class ChannelInviteModal extends React.Component {
var currentMember = ChannelStore.getCurrentMember();
var isAdmin = false;
if (currentMember) {
- isAdmin = utils.isAdmin(currentMember.roles) || utils.isAdmin(UserStore.getCurrentUser().roles);
+ isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
}
var content;
@@ -138,43 +128,32 @@ export default class ChannelInviteModal extends React.Component {
}
return (
- <div
- className='modal fade more-modal'
- id='channel_invite'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
>
- <div
- className='modal-dialog'
- role='document'
- >
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4>
- </div>
- <div className='modal-body'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Add New Members to '}<span className='name'>{this.state.channelName}</span></Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
{inviteError}
{content}
- </div>
- <div className='modal-footer'>
+ </Modal.Body>
+ <Modal.Footer>
<button
type='button'
className='btn btn-default'
- data-dismiss='modal'
- >Close</button>
- </div>
- </div>
- </div>
- </div>
+ onClick={this.props.onModalDismissed}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
+
+ChannelInviteModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
deleted file mode 100644
index d0ea7278b..000000000
--- a/web/react/components/channel_members.jsx
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-const UserStore = require('../stores/user_store.jsx');
-const ChannelStore = require('../stores/channel_store.jsx');
-const AsyncClient = require('../utils/async_client.jsx');
-const MemberList = require('./member_list.jsx');
-const Client = require('../utils/client.jsx');
-const Utils = require('../utils/utils.jsx');
-
-export default class ChannelMembers 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.onHide = this.onHide.bind(this);
- this.onShow = this.onShow.bind(this);
-
- this.state = this.getStateFromStores();
- }
- getStateFromStores() {
- const users = UserStore.getActiveOnlyProfiles();
- let memberList = ChannelStore.getCurrentExtraInfo().members;
-
- let nonmemberList = [];
- for (let id in users) {
- if (users.hasOwnProperty(id)) {
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === id) {
- found = true;
- break;
- }
- }
- if (!found) {
- nonmemberList.push(users[id]);
- }
- }
- }
-
- function compareByUsername(a, b) {
- if (a.username < b.username) {
- return -1;
- } else if (a.username > b.username) {
- return 1;
- }
-
- return 0;
- }
-
- memberList.sort(compareByUsername);
- nonmemberList.sort(compareByUsername);
-
- const channel = ChannelStore.getCurrent();
- let channelName = '';
- if (channel) {
- channelName = channel.display_name;
- }
-
- return {
- nonmemberList: nonmemberList,
- memberList: memberList,
- channelName: channelName
- };
- }
- onHide() {
- this.setState({renderMembers: false});
- }
- onShow() {
- this.setState({renderMembers: true});
- }
- componentDidMount() {
- ChannelStore.addExtraInfoChangeListener(this.onChange);
- ChannelStore.addChangeListener(this.onChange);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.onHide);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
- }
- componentWillUnmount() {
- ChannelStore.removeExtraInfoChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
- }
- onChange() {
- const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(this.state, newState)) {
- this.setState(newState);
- }
- }
- handleRemove(userId) {
- // Make sure the user is a member of the channel
- let memberList = this.state.memberList;
- let found = false;
- for (let i = 0; i < memberList.length; i++) {
- if (memberList[i].id === userId) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return;
- }
-
- let data = {};
- data.user_id = userId;
-
- Client.removeChannelMember(ChannelStore.getCurrentId(), data,
- function handleRemoveSuccess() {
- let oldMember;
- for (let i = 0; i < memberList.length; i++) {
- if (userId === memberList[i].id) {
- oldMember = memberList[i];
- memberList.splice(i, 1);
- break;
- }
- }
-
- let nonmemberList = this.state.nonmemberList;
- if (oldMember) {
- nonmemberList.push(oldMember);
- }
-
- this.setState({memberList: memberList, nonmemberList: nonmemberList});
- AsyncClient.getChannelExtraInfo(true);
- }.bind(this),
- function handleRemoveError(err) {
- this.setState({inviteError: err.message});
- }.bind(this)
- );
- }
- render() {
- const currentMember = ChannelStore.getCurrentMember();
- let isAdmin = false;
- if (currentMember) {
- isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
- }
-
- var memberList = null;
- if (this.state.renderMembers) {
- memberList = (
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- );
- }
-
- return (
- <div
- className='modal fade more-modal'
- ref='modal'
- id='channel_members'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>×</span>
- </button>
- <h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4>
- <a
- className='btn btn-md btn-primary'
- data-toggle='modal'
- data-target='#channel_invite'
- >
- <i className='glyphicon glyphicon-envelope'/> Add New Members
- </a>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- {memberList}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- Close
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
new file mode 100644
index 000000000..1df0da9cf
--- /dev/null
+++ b/web/react/components/channel_members_modal.jsx
@@ -0,0 +1,193 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const MemberList = require('./member_list.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+const Modal = ReactBootstrap.Modal;
+
+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);
+
+ const state = this.getStateFromStores();
+ state.showInviteModal = false;
+ this.state = state;
+ }
+ getStateFromStores() {
+ const users = UserStore.getActiveOnlyProfiles();
+ const memberList = ChannelStore.getCurrentExtraInfo().members;
+
+ const nonmemberList = [];
+ for (const id in users) {
+ if (users.hasOwnProperty(id)) {
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ nonmemberList.push(users[id]);
+ }
+ }
+ }
+
+ function compareByUsername(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ } else if (a.username > b.username) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ memberList.sort(compareByUsername);
+ nonmemberList.sort(compareByUsername);
+
+ const channel = ChannelStore.getCurrent();
+ let channelName = '';
+ if (channel) {
+ channelName = channel.display_name;
+ }
+
+ return {
+ nonmemberList,
+ memberList,
+ channelName
+ };
+ }
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.show && nextProps.show) {
+ ChannelStore.addExtraInfoChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
+ } else if (this.props.show && !nextProps.show) {
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ }
+ onChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
+ }
+ }
+ handleRemove(userId) {
+ // Make sure the user is a member of the channel
+ const memberList = this.state.memberList;
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === userId) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ const data = {};
+ data.user_id = userId;
+
+ Client.removeChannelMember(ChannelStore.getCurrentId(), data,
+ () => {
+ let oldMember;
+ for (let i = 0; i < memberList.length; i++) {
+ if (userId === memberList[i].id) {
+ oldMember = memberList[i];
+ memberList.splice(i, 1);
+ break;
+ }
+ }
+
+ const nonmemberList = this.state.nonmemberList;
+ if (oldMember) {
+ nonmemberList.push(oldMember);
+ }
+
+ this.setState({memberList, nonmemberList});
+ AsyncClient.getChannelExtraInfo(true);
+ },
+ (err) => {
+ this.setState({inviteError: err.message});
+ }
+ );
+ }
+ render() {
+ const currentMember = ChannelStore.getCurrentMember();
+ let isAdmin = false;
+ if (currentMember) {
+ isAdmin = Utils.isAdmin(currentMember.roles) || Utils.isAdmin(UserStore.getCurrentUser().roles);
+ }
+
+ return (
+ <div>
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
+ >
+ <Modal.Header closeButton={true}>
+ <Modal.Title><span className='name'>{this.state.channelName}</span>{' Members'}</Modal.Title>
+ <a
+ className='btn btn-md btn-primary'
+ href='#'
+ onClick={() => {
+ this.setState({showInviteModal: true});
+ this.props.onModalDismissed();
+ }}
+ >
+ <i className='glyphicon glyphicon-envelope'/>{' Add New Members'}
+ </a>
+ </Modal.Header>
+ <Modal.Body>
+ <div className='col-sm-12'>
+ <div className='team-member-list'>
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
+ </div>
+ </div>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onModalDismissed}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
+ </div>
+ );
+ }
+}
+
+ChannelMembersModal.defaultProps = {
+ show: false
+};
+
+ChannelMembersModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index 12002f33f..cdef1c1ea 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -1,70 +1,63 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+const Modal = ReactBootstrap.Modal;
+
export default class ConfirmModal extends React.Component {
constructor(props) {
super(props);
this.handleConfirm = this.handleConfirm.bind(this);
-
- this.state = {};
}
+
handleConfirm() {
- $('#' + this.props.parent_id).attr('data-confirm', 'true');
- $('#' + this.props.parent_id).modal('hide');
- $('#' + this.props.id).modal('hide');
+ this.props.onConfirm();
}
+
render() {
return (
- <div
- className='modal fade'
- id={this.props.id}
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ className='modal-confirm'
+ show={this.props.show}
+ onHide={this.props.onCancel}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <h4 className='modal-title'>{this.props.title}</h4>
- </div>
- <div className='modal-body'>
- {this.props.message}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- Cancel
- </button>
- <button
- onClick={this.handleConfirm}
- type='button'
- className='btn btn-primary'
- >
- {this.props.confirm_button}
- </button>
- </div>
- </div>
- </div>
- </div>
+ <Modal.Header closeButton={false}>
+ <Modal.Title>{this.props.title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {this.props.message}
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onCancel}
+ >
+ {'Cancel'}
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.props.onConfirm}
+ >
+ {this.props.confirm_button}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
ConfirmModal.defaultProps = {
- parent_id: '',
- id: '',
title: '',
message: '',
confirm_button: ''
};
ConfirmModal.propTypes = {
- parent_id: React.PropTypes.string,
- id: React.PropTypes.string,
+ show: React.PropTypes.bool.isRequired,
title: React.PropTypes.string,
message: React.PropTypes.string,
- confirm_button: React.PropTypes.string
+ confirm_button: React.PropTypes.string,
+ onConfirm: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index bea700725..c09477a69 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -2,55 +2,50 @@
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
+var ActionTypes = require('../utils/constants.jsx').ActionTypes;
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Client = require('../utils/client.jsx');
+var ModalStore = require('../stores/modal_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
+const Modal = ReactBootstrap.Modal;
+
export default class InviteMemberModal extends React.Component {
constructor(props) {
super(props);
+ this.handleToggle = this.handleToggle.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleHide = this.handleHide.bind(this);
this.addInviteFields = this.addInviteFields.bind(this);
this.clearFields = this.clearFields.bind(this);
this.removeInviteFields = this.removeInviteFields.bind(this);
this.state = {
+ show: false,
inviteIds: [0],
idCount: 0,
emailErrors: {},
firstNameErrors: {},
lastNameErrors: {},
- emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
+ emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
+ showConfirmModal: false
};
}
componentDidMount() {
- var self = this;
- $('#invite_member').on('hide.bs.modal', function hide(e) {
- if ($('#invite_member').attr('data-confirm') === 'true') {
- $('#invite_member').attr('data-confirm', 'false');
- return;
- }
-
- var notEmpty = false;
- for (var i = 0; i < self.state.inviteIds.length; i++) {
- var index = self.state.inviteIds[i];
- if (ReactDOM.findDOMNode(self.refs['email' + index]).value.trim() !== '') {
- notEmpty = true;
- break;
- }
- }
+ ModalStore.addModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
+ }
- if (notEmpty) {
- $('#confirm_invite_modal').modal('show');
- e.preventDefault();
- }
- });
+ componentWillUnmount() {
+ ModalStore.removeModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
+ }
- $('#invite_member').on('hidden.bs.modal', function show() {
- self.clearFields();
+ handleToggle(value) {
+ this.setState({
+ show: value
});
}
@@ -94,25 +89,57 @@ export default class InviteMemberModal extends React.Component {
var data = {};
data.invites = invites;
- Client.inviteMembers(data,
- function success() {
- $(ReactDOM.findDOMNode(this.refs.modal)).attr('data-confirm', 'true');
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
- }.bind(this),
- function fail(err) {
+ Client.inviteMembers(
+ data,
+ () => {
+ this.handleHide(false);
+ },
+ (err) => {
if (err.message === 'This person is already on your team') {
emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors});
} else {
this.setState({serverError: err.message});
}
- }.bind(this)
+ }
);
}
- componentDidUpdate() {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll');
+ handleHide(requireConfirm) {
+ if (requireConfirm) {
+ var notEmpty = false;
+ for (var i = 0; i < this.state.inviteIds.length; i++) {
+ var index = this.state.inviteIds[i];
+ if (ReactDOM.findDOMNode(this.refs['email' + index]).value.trim() !== '') {
+ notEmpty = true;
+ break;
+ }
+ }
+
+ if (notEmpty) {
+ this.setState({
+ showConfirmModal: true
+ });
+
+ return;
+ }
+ }
+
+ this.clearFields();
+
+ this.setState({
+ show: false,
+ showConfirmModal: false
+ });
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (!prevState.show && this.state.show) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
}
addInviteFields() {
@@ -292,7 +319,7 @@ export default class InviteMemberModal extends React.Component {
);
} else {
var teamInviteLink = null;
- if (currentUser && this.props.teamType === 'O') {
+ if (currentUser && TeamStore.getCurrent().type === 'O') {
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id;
var link =
(
@@ -302,11 +329,7 @@ export default class InviteMemberModal extends React.Component {
data-target='#get_link'
data-title='Team Invite'
data-value={linkUrl}
- onClick={
- function click() {
- $('#invite_member').modal('hide');
- }
- }
+ onClick={() => this.handleHide(this, false)}
>Team Invite Link</a>
);
@@ -327,64 +350,54 @@ export default class InviteMemberModal extends React.Component {
return (
<div>
- <div
- className='modal fade'
- ref='modal'
- id='invite_member'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ className='modal-invite-member'
+ show={this.state.show}
+ onHide={this.handleHide.bind(this, true)}
+ enforceFocus={!this.state.showConfirmModal}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Invite New Member'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ <form role='form'>
+ {inviteSections}
+ </form>
+ {content}
+ </Modal.Body>
+ <Modal.Footer>
<button
type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
+ className='btn btn-default'
+ onClick={this.handleHide.bind(this, true)}
>
- <span aria-hidden='true'>×</span>
+ {'Cancel'}
</button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >Invite New Member</h4>
- </div>
- <div
- ref='modalBody'
- className='modal-body'
- >
- <form role='form'>
- {inviteSections}
- </form>
- {content}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >Cancel</button>
- {sendButton}
- </div>
- </div>
- </div>
- </div>
+ {sendButton}
+ </Modal.Footer>
+ </Modal>
<ConfirmModal
- id='confirm_invite_modal'
- parent_id='invite_member'
title='Discard Invitations?'
message='You have unsent invitations, are you sure you want to discard them?'
confirm_button='Yes, Discard'
+ show={this.state.showConfirmModal}
+ onConfirm={this.handleHide.bind(this, false)}
+ onCancel={() => this.setState({showConfirmModal: false})}
/>
</div>
);
}
- return <div/>;
+
+ return null;
+ }
+
+ static show() {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.TOGGLE_INVITE_MEMBER_MODAL,
+ value: true
+ });
}
}
InviteMemberModal.propTypes = {
- teamType: React.PropTypes.string
};
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index f7778f25f..ff53816c7 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -1,22 +1,26 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var MessageWrapper = require('./message_wrapper.jsx');
-var NotifyCounts = require('./notify_counts.jsx');
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
+const MessageWrapper = require('./message_wrapper.jsx');
+const NotifyCounts = require('./notify_counts.jsx');
+const ChannelMembersModal = require('./channel_members_modal.jsx');
+const ChannelInviteModal = require('./channel_invite_modal.jsx');
+
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const TeamStore = require('../stores/team_store.jsx');
+
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
const Utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Popover = ReactBootstrap.Popover;
-var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+const Popover = ReactBootstrap.Popover;
+const OverlayTrigger = ReactBootstrap.OverlayTrigger;
export default class Navbar extends React.Component {
constructor(props) {
@@ -29,6 +33,8 @@ export default class Navbar extends React.Component {
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
+ state.showMembersModal = false;
+ state.showInviteModal = false;
this.state = state;
}
getStateFromStores() {
@@ -45,17 +51,18 @@ export default class Navbar extends React.Component {
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChange);
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
}
handleSubmit(e) {
e.preventDefault();
}
handleLeave() {
Client.leaveChannel(this.state.channel.id,
- function success() {
+ () => {
AsyncClient.getChannels(true);
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
- function error(err) {
+ (err) => {
AsyncClient.dispatchError(err, 'handleLeave');
}
);
@@ -104,7 +111,7 @@ export default class Navbar extends React.Component {
data-channelid={channel.id}
href='#'
>
- View Info
+ {'View Info'}
</a>
</li>
);
@@ -120,7 +127,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Set Channel Header...
+ {'Set Channel Header...'}
</a>
</li>
);
@@ -145,11 +152,10 @@ export default class Navbar extends React.Component {
<li role='presentation'>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_invite'
href='#'
+ onClick={() => this.setState({showInviteModal: false})}
>
- Add Members
+ {'Add Members'}
</a>
</li>
);
@@ -161,7 +167,7 @@ export default class Navbar extends React.Component {
href='#'
onClick={this.handleLeave}
>
- Leave Channel
+ {'Leave Channel'}
</a>
</li>
);
@@ -175,11 +181,10 @@ export default class Navbar extends React.Component {
<li role='presentation'>
<a
role='menuitem'
- data-toggle='modal'
- data-target='#channel_members'
href='#'
+ onClick={() => this.setState({showMembersModal: true})}
>
- Manage Members
+ {'Manage Members'}
</a>
</li>
);
@@ -195,7 +200,7 @@ export default class Navbar extends React.Component {
data-name={channel.name}
data-channelid={channel.id}
>
- Rename Channel...
+ {'Rename Channel...'}
</a>
</li>
);
@@ -210,7 +215,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Delete Channel...
+ {'Delete Channel...'}
</a>
</li>
);
@@ -228,7 +233,7 @@ export default class Navbar extends React.Component {
data-title={channel.display_name}
data-channelid={channel.id}
>
- Notification Preferences
+ {'Notification Preferences'}
</a>
</li>
);
@@ -299,7 +304,7 @@ export default class Navbar extends React.Component {
data-toggle='collapse'
data-target='#navbar-collapse-1'
>
- <span className='sr-only'>Toggle sidebar</span>
+ <span className='sr-only'>{'Toggle sidebar'}</span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
@@ -315,7 +320,7 @@ export default class Navbar extends React.Component {
data-target='#sidebar-nav'
onClick={this.toggleLeftSidebar}
>
- <span className='sr-only'>Toggle sidebar</span>
+ <span className='sr-only'>{'Toggle sidebar'}</span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
@@ -426,6 +431,14 @@ export default class Navbar extends React.Component {
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
channel={channel}
/>
+ <ChannelMembersModal
+ show={this.state.showMembersModal}
+ onModalDismissed={() => this.setState({showMembersModal: false})}
+ />
+ <ChannelInviteModal
+ show={this.state.showInviteModal}
+ onModalDismissed={() => this.setState({showInviteModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 029b9c137..0b755f377 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -7,6 +7,8 @@ var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var AboutBuildModal = require('./about_build_modal.jsx');
+var InviteMemberModal = require('./invite_member_modal.jsx');
+var UserSettingsModal = require('./user_settings/user_settings_modal.jsx');
var Constants = require('../utils/constants.jsx');
@@ -33,7 +35,10 @@ export default class NavbarDropdown extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- this.state = getStateFromStores();
+ const state = getStateFromStores();
+ state.showUserSettingsModal = false;
+ state.showAboutModal = false;
+ this.state = state;
}
handleLogoutClick(e) {
e.preventDefault();
@@ -88,8 +93,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#invite_member'
+ onClick={InviteMemberModal.show}
>
{'Invite New Member'}
</a>
@@ -210,8 +214,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#user_settings'
+ onClick={() => this.setState({showUserSettingsModal: true})}
>
{'Account Settings'}
</a>
@@ -256,6 +259,10 @@ export default class NavbarDropdown extends React.Component {
{'About Mattermost'}
</a>
</li>
+ <UserSettingsModal
+ show={this.state.showUserSettingsModal}
+ onModalDismissed={() => this.setState({showUserSettingsModal: false})}
+ />
<AboutBuildModal
show={this.state.showAboutModal}
onModalDismissed={this.aboutModalDismissed}
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 7671ca01d..301057990 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -81,7 +81,7 @@ export default class PostsViewContainer extends React.Component {
}
}
onChannelChange() {
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const channels = this.state.channels.slice();
const channelId = ChannelStore.getCurrentId();
@@ -112,7 +112,7 @@ export default class PostsViewContainer extends React.Component {
postLists});
}
onChannelLeave(id) {
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const channels = this.state.channels.slice();
const index = channels.indexOf(id);
if (index !== -1) {
@@ -123,7 +123,7 @@ export default class PostsViewContainer extends React.Component {
}
onPostsChange() {
const channels = this.state.channels;
- const postLists = Object.assign({}, this.state.postLists);
+ const postLists = this.state.postLists.slice();
const newPostsView = this.getChannelPosts(channels[this.state.currentChannelIndex]);
postLists[this.state.currentChannelIndex] = newPostsView;
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 9350bbd42..2135e3ef3 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var InviteMemberModal = require('./invite_member_modal.jsx');
+var UserSettingsModal = require('./user_settings/user_settings_modal.jsx');
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var client = require('../utils/client.jsx');
@@ -15,6 +17,10 @@ export default class SidebarRightMenu extends React.Component {
super(props);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
+
+ this.state = {
+ showUserSettingsModal: false
+ };
}
handleLogoutClick(e) {
@@ -38,10 +44,12 @@ export default class SidebarRightMenu extends React.Component {
inviteLink = (
<li>
- <a href='#'
- data-toggle='modal'
- data-target='#invite_member'
- ><i className='glyphicon glyphicon-user'></i>Invite New Member</a>
+ <a
+ href='#'
+ onClick={InviteMemberModal.show}
+ >
+ <i className='glyphicon glyphicon-user'></i>Invite New Member
+ </a>
</li>
);
@@ -115,9 +123,11 @@ export default class SidebarRightMenu extends React.Component {
<li>
<a
href='#'
- data-toggle='modal'
- data-target='#user_settings'
- ><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ onClick={() => this.setState({showUserSettingsModal: true})}
+ >
+ <i className='glyphicon glyphicon-cog'></i>Account Settings
+ </a>
+ </li>
{teamSettingsLink}
{inviteLink}
{teamLink}
@@ -141,6 +151,10 @@ export default class SidebarRightMenu extends React.Component {
><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
</ul>
</div>
+ <UserSettingsModal
+ show={this.state.showUserSettingsModal}
+ onModalDismissed={() => this.setState({showUserSettingsModal: false})}
+ />
</div>
);
}
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 17fe31c65..4d47db2a8 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -17,11 +17,13 @@ export default class TeamSettingsModal extends React.Component {
};
}
componentDidMount() {
- $('body').on('click', '.modal-back', function handleBackClick() {
+ const modal = $(ReactDOM.findDOMNode(this.refs.modal));
+
+ modal.on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
$(this).closest('.modal-dialog').find('.settings-table .nav li.active').removeClass('active');
});
- $('body').on('click', '.modal-header .close', () => {
+ modal.on('click', '.modal-header .close', () => {
setTimeout(() => {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 1a9ac0ad3..24da106d0 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+const ModalStore = require('../../stores/modal_store.jsx');
const UserStore = require('../../stores/user_store.jsx');
const Utils = require('../../utils/utils.jsx');
const Client = require('../../utils/client.jsx');
@@ -24,10 +25,10 @@ export default class ImportThemeModal extends React.Component {
};
}
componentDidMount() {
- UserStore.addImportModalListener(this.updateShow);
+ ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
}
componentWillUnmount() {
- UserStore.removeImportModalListener(this.updateShow);
+ ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
}
updateShow(show) {
this.setState({show});
@@ -74,7 +75,6 @@ export default class ImportThemeModal extends React.Component {
this.setState({show: false});
Utils.applyTheme(theme);
- $('#user_settings').modal('show');
},
(err) => {
var state = this.getStateFromStores();
diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx
index 546e26ca3..e089ce973 100644
--- a/web/react/components/user_settings/user_settings.jsx
+++ b/web/react/components/user_settings/user_settings.jsx
@@ -16,6 +16,7 @@ export default class UserSettings extends React.Component {
constructor(props) {
super(props);
+ this.getActiveTab = this.getActiveTab.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.state = {user: UserStore.getCurrentUser()};
@@ -29,10 +30,14 @@ export default class UserSettings extends React.Component {
UserStore.removeChangeListener(this.onListenerChange);
}
+ getActiveTab() {
+ return this.refs.activeTab;
+ }
+
onListenerChange() {
var user = UserStore.getCurrentUser();
if (!utils.areStatesEqual(this.state.user, user)) {
- this.setState({user: user});
+ this.setState({user});
}
}
@@ -41,10 +46,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<GeneralTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -52,10 +60,14 @@ export default class UserSettings extends React.Component {
return (
<div>
<SecurityTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
+ setEnforceFocus={this.props.setEnforceFocus}
/>
</div>
);
@@ -63,10 +75,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<NotificationsTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -74,9 +89,14 @@ export default class UserSettings extends React.Component {
return (
<div>
<AppearanceTab
+ ref='activeTab'
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
+ setEnforceFocus={this.props.setEnforceFocus}
+ setRequireConfirm={this.props.setRequireConfirm}
/>
</div>
);
@@ -84,8 +104,11 @@ export default class UserSettings extends React.Component {
return (
<div>
<DeveloperTab
+ ref='activeTab'
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -93,10 +116,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<IntegrationsTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -104,10 +130,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<DisplayTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -115,10 +144,13 @@ export default class UserSettings extends React.Component {
return (
<div>
<AdvancedTab
+ ref='activeTab'
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
+ closeModal={this.props.closeModal}
+ collapseModal={this.props.collapseModal}
/>
</div>
);
@@ -132,5 +164,9 @@ UserSettings.propTypes = {
activeTab: React.PropTypes.string,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired,
+ setRequireConfirm: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx
index 910444735..2616981ba 100644
--- a/web/react/components/user_settings/user_settings_advanced.jsx
+++ b/web/react/components/user_settings/user_settings_advanced.jsx
@@ -13,7 +13,6 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.updateSection = this.updateSection.bind(this);
this.updateSetting = this.updateSetting.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
this.state = this.setupInitialState();
@@ -59,18 +58,6 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- }
-
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
-
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
-
render() {
const serverError = this.state.serverError || null;
let ctrlSendSection;
@@ -139,6 +126,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -146,7 +134,10 @@ export default class AdvancedSettingsDisplay extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Advanced Settings'}
</h4>
</div>
@@ -165,5 +156,7 @@ AdvancedSettingsDisplay.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7b4b54e27..425645c1f 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -20,13 +20,13 @@ export default class UserSettingsAppearance extends React.Component {
this.submitTheme = this.submitTheme.bind(this);
this.updateTheme = this.updateTheme.bind(this);
this.updateCodeTheme = this.updateCodeTheme.bind(this);
- this.handleClose = this.handleClose.bind(this);
+ this.deactivate = this.deactivate.bind(this);
+ this.resetFields = this.resetFields.bind(this);
this.handleImportModal = this.handleImportModal.bind(this);
this.state = this.getStateFromStores();
- this.originalTheme = this.state.theme;
- this.originalCodeTheme = this.state.theme.codeTheme;
+ this.originalTheme = Object.assign({}, this.state.theme);
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
@@ -34,7 +34,6 @@ export default class UserSettingsAppearance extends React.Component {
if (this.props.activeSection === 'theme') {
$(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
}
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
}
componentDidUpdate() {
if (this.props.activeSection === 'theme') {
@@ -44,14 +43,13 @@ export default class UserSettingsAppearance extends React.Component {
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
}
getStateFromStores() {
const user = UserStore.getCurrentUser();
let theme = null;
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
- theme = user.theme_props;
+ theme = Object.assign({}, user.theme_props);
} else {
theme = $.extend(true, {}, Constants.THEMES.default);
}
@@ -73,6 +71,8 @@ export default class UserSettingsAppearance extends React.Component {
if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
+
+ this.props.setEnforceFocus(true);
}
submitTheme(e) {
e.preventDefault();
@@ -86,11 +86,11 @@ export default class UserSettingsAppearance extends React.Component {
me: data
});
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateTab('general');
+ this.props.setRequireConfirm(false);
+ this.originalTheme = Object.assign({}, this.state.theme);
+
$('.ps-container.modal-body').scrollTop(0);
$('.ps-container.modal-body').perfectScrollbar('update');
- $('#user_settings').modal('hide');
},
(err) => {
var state = this.getStateFromStores();
@@ -103,37 +103,53 @@ export default class UserSettingsAppearance extends React.Component {
if (!theme.codeTheme) {
theme.codeTheme = this.state.theme.codeTheme;
}
+
+ let themeChanged = this.state.theme.length === theme.length;
+ if (!themeChanged) {
+ for (const field in theme) {
+ if (theme.hasOwnProperty(field)) {
+ if (this.state.theme[field] !== theme[field]) {
+ themeChanged = true;
+ break;
+ }
+ }
+ }
+ }
+
+ this.props.setRequireConfirm(themeChanged);
+
this.setState({theme});
Utils.applyTheme(theme);
}
updateCodeTheme(codeTheme) {
var theme = this.state.theme;
theme.codeTheme = codeTheme;
- this.setState({theme});
- Utils.applyTheme(theme);
+ this.updateTheme(theme);
}
updateType(type) {
this.setState({type});
}
- handleClose() {
+ deactivate() {
const state = this.getStateFromStores();
- state.serverError = null;
- state.theme.codeTheme = this.originalCodeTheme;
Utils.applyTheme(state.theme);
-
+ }
+ resetFields() {
+ const state = this.getStateFromStores();
+ state.serverError = null;
this.setState(state);
- $('.ps-container.modal-body').scrollTop(0);
- $('.ps-container.modal-body').perfectScrollbar('update');
- $('#user_settings').modal('hide');
+ Utils.applyTheme(state.theme);
+
+ this.props.setRequireConfirm(false);
}
handleImportModal() {
- $('#user_settings').modal('hide');
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
value: true
});
+
+ this.props.setEnforceFocus(false);
}
render() {
var serverError;
@@ -204,7 +220,7 @@ export default class UserSettingsAppearance extends React.Component {
<a
className='btn btn-sm theme'
href='#'
- onClick={this.handleClose}
+ onClick={this.resetFields}
>
{'Cancel'}
</a>
@@ -218,8 +234,8 @@ export default class UserSettingsAppearance extends React.Component {
<button
type='button'
className='close'
- data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -227,7 +243,11 @@ export default class UserSettingsAppearance extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>{'Appearance Settings'}
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Appearance Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -253,5 +273,9 @@ UserSettingsAppearance.defaultProps = {
};
UserSettingsAppearance.propTypes = {
activeSection: React.PropTypes.string,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setRequireConfirm: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index c2d7a9710..e6adba1d4 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -63,6 +63,7 @@ export default class DeveloperTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -70,7 +71,11 @@ export default class DeveloperTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>{'Developer Settings'}
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Developer Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -89,5 +94,7 @@ DeveloperTab.defaultProps = {
};
DeveloperTab.propTypes = {
activeSection: React.PropTypes.string,
- updateSection: React.PropTypes.func
+ updateSection: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index d086c78a9..3c12ead23 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -25,7 +25,6 @@ export default class UserSettingsDisplay extends React.Component {
this.handleClockRadio = this.handleClockRadio.bind(this);
this.handleNameRadio = this.handleNameRadio.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.state = getDisplayStateFromStores();
}
@@ -53,15 +52,6 @@ export default class UserSettingsDisplay extends React.Component {
this.setState(getDisplayStateFromStores());
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
render() {
const serverError = this.state.serverError || null;
let clockSection;
@@ -227,6 +217,7 @@ export default class UserSettingsDisplay extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -234,7 +225,10 @@ export default class UserSettingsDisplay extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Display Settings'}
</h4>
</div>
@@ -255,5 +249,7 @@ UserSettingsDisplay.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 3adac197a..9f0c16194 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -32,7 +32,6 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updatePicture = this.updatePicture.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
this.state = this.setupInitialState(props);
@@ -210,20 +209,6 @@ export default class UserSettingsGeneralTab extends React.Component {
this.submitActive = false;
this.props.updateSection(section);
}
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearForms() {
- this.value = '';
- });
-
- this.setState(assign({}, this.setupInitialState(this.props), {clientError: null, serverError: null, emailError: null}));
- this.props.updateSection('');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
setupInitialState(props) {
var user = props.user;
@@ -579,6 +564,7 @@ export default class UserSettingsGeneralTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -586,7 +572,10 @@ export default class UserSettingsGeneralTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'General Settings'}
</h4>
</div>
@@ -613,5 +602,7 @@ UserSettingsGeneralTab.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index 4a9915a1f..744a6beea 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -11,24 +11,12 @@ export default class UserSettingsIntegrationsTab extends React.Component {
super(props);
this.updateSection = this.updateSection.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.state = {};
}
updateSection(section) {
this.props.updateSection(section);
}
- handleClose() {
- this.updateSection('');
- $('.ps-container.modal-body').scrollTop(0);
- $('.ps-container.modal-body').perfectScrollbar('update');
- }
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- }
render() {
let incomingHooksSection;
let outgoingHooksSection;
@@ -104,6 +92,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
<span aria-hidden='true'>{'×'}</span>
</button>
@@ -111,7 +100,10 @@ export default class UserSettingsIntegrationsTab extends React.Component {
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
{'Integration Settings'}
</h4>
</div>
@@ -132,5 +124,7 @@ UserSettingsIntegrationsTab.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string
+ activeSection: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 18dd490e7..4dcf32cb9 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -1,34 +1,161 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingsSidebar = require('../settings_sidebar.jsx');
-var UserSettings = require('./user_settings.jsx');
+const ConfirmModal = require('../confirm_modal.jsx');
+const Modal = ReactBootstrap.Modal;
+const SettingsSidebar = require('../settings_sidebar.jsx');
+const UserSettings = require('./user_settings.jsx');
export default class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
+ this.handleHide = this.handleHide.bind(this);
+ this.handleHidden = this.handleHidden.bind(this);
+ this.handleCollapse = this.handleCollapse.bind(this);
+ this.handleConfirm = this.handleConfirm.bind(this);
+ this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this);
+
+ this.deactivateTab = this.deactivateTab.bind(this);
+ this.closeModal = this.closeModal.bind(this);
+ this.collapseModal = this.collapseModal.bind(this);
+
this.updateTab = this.updateTab.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.state = {active_tab: 'general', active_section: ''};
+ this.state = {
+ active_tab: 'general',
+ active_section: '',
+ showConfirmModal: false,
+ enforceFocus: true
+ };
+
+ this.requireConfirm = false;
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.show && this.props.show) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ }
+
+ // Called when the close button is pressed on the main modal
+ handleHide() {
+ if (this.requireConfirm) {
+ this.afterConfirm = () => this.handleHide();
+ this.showConfirmModal();
+
+ return false;
+ }
+
+ this.deactivateTab();
+ this.props.onModalDismissed();
}
- componentDidMount() {
- $('body').on('click', '.modal-back', function changeDisplay() {
- $(this).closest('.modal-dialog').removeClass('display--content');
+
+ // called after the dialog is fully hidden and faded out
+ handleHidden() {
+ this.setState({
+ active_tab: 'general',
+ active_section: ''
});
- $('body').on('click', '.modal-header .close', () => {
- setTimeout(() => {
- $('.modal-dialog.display--content').removeClass('display--content');
- }, 500);
+ }
+
+ // Called to hide the settings pane when on mobile
+ handleCollapse() {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).closest('.modal-dialog').removeClass('display--content');
+
+ this.deactivateTab();
+
+ this.setState({
+ active_tab: '',
+ active_section: ''
});
}
- updateTab(tab) {
- this.setState({active_tab: tab});
+
+ handleConfirm() {
+ this.setState({
+ showConfirmModal: false,
+ enforceFocus: true
+ });
+
+ this.requireConfirm = false;
+
+ if (this.afterConfirm) {
+ this.afterConfirm();
+ this.afterConfirm = null;
+ }
}
- updateSection(section) {
- this.setState({active_section: section});
+
+ handleCancelConfirmation() {
+ this.setState({
+ showConfirmModal: false,
+ enforceFocus: true
+ });
+
+ this.afterConfirm = null;
}
+
+ showConfirmModal(afterConfirm) {
+ this.setState({
+ showConfirmModal: true,
+ enforceFocus: false
+ });
+
+ if (afterConfirm) {
+ this.afterConfirm = afterConfirm;
+ }
+ }
+
+ // Called to let settings tab perform cleanup before being closed
+ deactivateTab() {
+ const activeTab = this.refs.userSettings.getActiveTab();
+ if (activeTab && activeTab.deactivate) {
+ activeTab.deactivate();
+ }
+ }
+
+ // Called by settings tabs when their close button is pressed
+ closeModal() {
+ if (this.requireConfirm) {
+ this.showConfirmModal(this.closeModal);
+ } else {
+ this.handleHide();
+ }
+ }
+
+ // Called by settings tabs when their back button is pressed
+ collapseModal() {
+ if (this.requireConfirm) {
+ this.showConfirmModal(this.collapseModal);
+ } else {
+ this.handleCollapse();
+ }
+ }
+
+ updateTab(tab, skipConfirm) {
+ if (!skipConfirm && this.requireConfirm) {
+ this.showConfirmModal(() => this.updateTab(tab, true));
+ } else {
+ this.deactivateTab();
+
+ this.setState({
+ active_tab: tab,
+ active_section: ''
+ });
+ }
+ }
+
+ updateSection(section, skipConfirm) {
+ if (!skipConfirm && this.requireConfirm) {
+ this.showConfirmModal(() => this.updateSection(section, true));
+ } else {
+ this.setState({active_section: section});
+ }
+ }
+
render() {
var tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
@@ -46,33 +173,17 @@ export default class UserSettingsModal extends React.Component {
tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
return (
- <div
- className='modal fade'
- ref='modal'
- id='user_settings'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
+ <Modal
+ dialogClassName='settings-modal'
+ show={this.props.show}
+ onHide={this.handleHide}
+ onExited={this.handleHidden}
+ enforceFocus={this.state.enforceFocus}
>
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- {'Account Settings'}
- </h4>
- </div>
- <div className='modal-body'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>{'Account Settings'}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
<div className='settings-table'>
<div className='settings-links'>
<SettingsSidebar
@@ -83,17 +194,33 @@ export default class UserSettingsModal extends React.Component {
</div>
<div className='settings-content minimize-settings'>
<UserSettings
+ ref='userSettings'
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
updateSection={this.updateSection}
updateTab={this.updateTab}
+ closeModal={this.closeModal}
+ collapseModal={this.collapseModal}
+ setEnforceFocus={(enforceFocus) => this.setState({enforceFocus})}
+ setRequireConfirm={(requireConfirm) => this.requireConfirm = requireConfirm}
/>
</div>
</div>
- </div>
- </div>
- </div>
- </div>
+ </Modal.Body>
+ <ConfirmModal
+ title='Discard Changes?'
+ message='You have unsaved changes, are you sure you want to discard them?'
+ confirm_button='Yes, Discard'
+ show={this.state.showConfirmModal}
+ onConfirm={this.handleConfirm}
+ onCancel={this.handleCancelConfirmation}
+ />
+ </Modal>
);
}
}
+
+UserSettingsModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index 2b904763c..c6f47804f 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -7,7 +7,6 @@ var SettingItemMax = require('../setting_item_max.jsx');
var client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var utils = require('../../utils/utils.jsx');
-var assign = require('object-assign');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
@@ -77,7 +76,6 @@ export default class NotificationsTab extends React.Component {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.updateSection = this.updateSection.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleNotifyRadio = this.handleNotifyRadio.bind(this);
@@ -128,27 +126,15 @@ export default class NotificationsTab extends React.Component {
}.bind(this)
);
}
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function clearField() {
- this.value = '';
- });
-
- this.setState(assign({}, getNotificationsStateFromStores(), {serverError: null}));
-
- this.props.updateTab('general');
- }
updateSection(section) {
this.setState(getNotificationsStateFromStores());
this.props.updateSection(section);
}
componentDidMount() {
UserStore.addChangeListener(this.onListenerChange);
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange);
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
}
onListenerChange() {
var newState = getNotificationsStateFromStores();
@@ -644,15 +630,19 @@ export default class NotificationsTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>
- Notifications
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Notification Settings'}
</h4>
</div>
<div
@@ -686,5 +676,7 @@ NotificationsTab.propTypes = {
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
activeSection: React.PropTypes.string,
- activeTab: React.PropTypes.string
+ activeTab: React.PropTypes.string,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 983a10df0..61d13ed8b 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -3,6 +3,8 @@
var SettingItemMin = require('../setting_item_min.jsx');
var SettingItemMax = require('../setting_item_max.jsx');
+var AccessHistoryModal = require('../access_history_modal.jsx');
+var ActivityLogModal = require('../activity_log_modal.jsx');
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var Constants = require('../../utils/constants.jsx');
@@ -11,14 +13,34 @@ export default class SecurityTab extends React.Component {
constructor(props) {
super(props);
+ this.showAccessHistoryModal = this.showAccessHistoryModal.bind(this);
+ this.showActivityLogModal = this.showActivityLogModal.bind(this);
+ this.hideModals = this.hideModals.bind(this);
this.submitPassword = this.submitPassword.bind(this);
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
- this.handleClose = this.handleClose.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
- this.state = this.setupInitialState();
+ const state = this.setupInitialState();
+ state.showAccessHistoryModal = false;
+ state.showActivityLogModal = false;
+ this.state = state;
+ }
+ showAccessHistoryModal() {
+ this.props.setEnforceFocus(false);
+ this.setState({showAccessHistoryModal: true});
+ }
+ showActivityLogModal() {
+ this.props.setEnforceFocus(false);
+ this.setState({showActivityLogModal: true});
+ }
+ hideModals() {
+ this.props.setEnforceFocus(true);
+ this.setState({
+ showAccessHistoryModal: false,
+ showActivityLogModal: false
+ });
}
submitPassword(e) {
e.preventDefault();
@@ -75,30 +97,9 @@ export default class SecurityTab extends React.Component {
updateConfirmPassword(e) {
this.setState({confirmPassword: e.target.value});
}
- handleHistoryOpen() {
- $('#user_settings').modal('hide');
- }
- handleDevicesOpen() {
- $('#user_settings').modal('hide');
- }
- handleClose() {
- $(ReactDOM.findDOMNode(this)).find('.form-control').each(function resetValue() {
- this.value = '';
- });
- this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
-
- this.props.updateTab('general');
- }
setupInitialState() {
return {currentPassword: '', newPassword: '', confirmPassword: ''};
}
- componentDidMount() {
- $('#user_settings').on('hidden.bs.modal', this.handleClose);
- }
- componentWillUnmount() {
- $('#user_settings').off('hidden.bs.modal', this.handleClose);
- this.props.updateSection('');
- }
render() {
var serverError;
if (this.state.serverError) {
@@ -236,14 +237,19 @@ export default class SecurityTab extends React.Component {
className='close'
data-dismiss='modal'
aria-label='Close'
+ onClick={this.props.closeModal}
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- <i className='modal-back'></i>Security Settings
+ <i
+ className='modal-back'
+ onClick={this.props.collapseModal}
+ />
+ {'Security Settings'}
</h4>
</div>
<div className='user-settings'>
@@ -253,25 +259,29 @@ export default class SecurityTab extends React.Component {
<div className='divider-dark'/>
<br></br>
<a
- data-toggle='modal'
className='security-links theme'
- data-target='#access-history'
href='#'
- onClick={this.handleHistoryOpen}
+ onClick={this.showAccessHistoryModal}
>
<i className='fa fa-clock-o'></i>View Access History
</a>
<b> </b>
<a
- data-toggle='modal'
className='security-links theme'
- data-target='#activity-log'
href='#'
- onClick={this.handleDevicesOpen}
+ onClick={this.showActivityLogModal}
>
<i className='fa fa-globe'></i>View and Logout of Active Sessions
</a>
</div>
+ <AccessHistoryModal
+ show={this.state.showAccessHistoryModal}
+ onModalDismissed={this.hideModals}
+ />
+ <ActivityLogModal
+ show={this.state.showActivityLogModal}
+ onModalDismissed={this.hideModals}
+ />
</div>
);
}
@@ -285,5 +295,8 @@ SecurityTab.propTypes = {
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired
};
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 067dcde50..8781d52a5 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -9,7 +9,6 @@ var ErrorStore = require('../stores/error_store.jsx');
var MentionList = require('../components/mention_list.jsx');
var GetLinkModal = require('../components/get_link_modal.jsx');
-var MemberInviteModal = require('../components/invite_member_modal.jsx');
var EditChannelModal = require('../components/edit_channel_modal.jsx');
var DeleteChannelModal = require('../components/delete_channel_modal.jsx');
var RenameChannelModal = require('../components/rename_channel_modal.jsx');
@@ -18,17 +17,13 @@ var DeletePostModal = require('../components/delete_post_modal.jsx');
var MoreChannelsModal = require('../components/more_channels.jsx');
var PostDeletedModal = require('../components/post_deleted_modal.jsx');
var ChannelNotificationsModal = require('../components/channel_notifications.jsx');
-var UserSettingsModal = require('../components/user_settings/user_settings_modal.jsx');
var TeamSettingsModal = require('../components/team_settings_modal.jsx');
-var ChannelMembersModal = require('../components/channel_members.jsx');
-var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx');
var ChannelInfoModal = require('../components/channel_info_modal.jsx');
-var AccessHistoryModal = require('../components/access_history_modal.jsx');
-var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx');
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
+var InviteMemberModal = require('../components/invite_member_modal.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
@@ -83,8 +78,8 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <UserSettingsModal />,
- document.getElementById('user_settings_modal')
+ <InviteMemberModal />,
+ document.getElementById('invite_member_modal')
);
ReactDOM.render(
@@ -103,11 +98,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <MemberInviteModal teamType={props.TeamType} />,
- document.getElementById('invite_member_modal')
- );
-
- ReactDOM.render(
<EditChannelModal />,
document.getElementById('edit_channel_modal')
);
@@ -128,16 +118,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <ChannelMembersModal />,
- document.getElementById('channel_members_modal')
- );
-
- ReactDOM.render(
- <ChannelInviteModal />,
- document.getElementById('channel_invite_modal')
- );
-
- ReactDOM.render(
<ChannelInfoModal />,
document.getElementById('channel_info_modal')
);
@@ -163,16 +143,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <AccessHistoryModal />,
- document.getElementById('access_history_modal')
- );
-
- ReactDOM.render(
- <ActivityLogModal />,
- document.getElementById('activity_log_modal')
- );
-
- ReactDOM.render(
<RemovedFromChannelModal />,
document.getElementById('removed_from_channel_modal')
);
diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx
new file mode 100644
index 000000000..dc65d48da
--- /dev/null
+++ b/web/react/stores/modal_store.jsx
@@ -0,0 +1,42 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const EventEmitter = require('events').EventEmitter;
+
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+class ModalStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.addModalListener = this.addModalListener.bind(this);
+ this.removeModalListener = this.removeModalListener.bind(this);
+
+ this.handleEventPayload = this.handleEventPayload.bind(this);
+ this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
+ }
+
+ addModalListener(action, callback) {
+ this.on(action, callback);
+ }
+
+ removeModalListener(action, callback) {
+ this.removeListener(action, callback);
+ }
+
+ handleEventPayload(payload) {
+ const action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
+ case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
+ this.emit(action.type, action.value);
+ break;
+ }
+ }
+}
+
+const ModalStore = new ModalStoreClass();
+export default ModalStore;
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index aedb3dc09..4fa7224b7 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -13,7 +13,6 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions';
var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses';
-var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal';
class UserStoreClass extends EventEmitter {
constructor() {
@@ -34,9 +33,6 @@ class UserStoreClass extends EventEmitter {
this.emitStatusesChange = this.emitStatusesChange.bind(this);
this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
- this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
- this.addImportModalListener = this.addImportModalListener.bind(this);
- this.removeImportModalListener = this.removeImportModalListener.bind(this);
this.getCurrentId = this.getCurrentId.bind(this);
this.getCurrentUser = this.getCurrentUser.bind(this);
this.setCurrentUser = this.setCurrentUser.bind(this);
@@ -124,18 +120,6 @@ class UserStoreClass extends EventEmitter {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
}
- emitToggleImportModal(value) {
- this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
- }
-
- addImportModalListener(callback) {
- this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
- }
-
- removeImportModalListener(callback) {
- this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
- }
-
getCurrentUser() {
if (this.getProfiles()[global.window.mm_user.id] == null) {
this.saveProfile(global.window.mm_user);
@@ -353,10 +337,6 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange();
break;
- case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
- UserStore.emitToggleImportModal(action.value);
- break;
-
default:
}
});
diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx
index b3f868456..161c79761 100644
--- a/web/react/utils/channel_intro_mssages.jsx
+++ b/web/react/utils/channel_intro_mssages.jsx
@@ -3,6 +3,7 @@
// See License.txt for license information.
const Utils = require('./utils.jsx');
+const InviteMemberModal = require('../components/invite_member_modal.jsx');
const UserProfile = require('../components/user_profile.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const Constants = require('../utils/constants.jsx');
@@ -109,8 +110,7 @@ export function createDefaultIntroMessage(channel) {
<a
className='intro-links'
href='#'
- data-toggle='modal'
- data-target='#invite_member'
+ onClick={InviteMemberModal.show}
>
<i className='fa fa-user-plus'></i>{'Invite others to this team'}
</a>
@@ -213,6 +213,7 @@ export function createStandardIntroMessage(channel) {
>
<i className='fa fa-user-plus'></i>{'Invite others to this ' + uiType}
</a>
+
</div>
);
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 39be577df..b8d346ba7 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -39,7 +39,8 @@ module.exports = {
RECIEVED_LOGS: null,
RECIEVED_ALL_TEAMS: null,
- TOGGLE_IMPORT_THEME_MODAL: null
+ TOGGLE_IMPORT_THEME_MODAL: null,
+ TOGGLE_INVITE_MEMBER_MODAL: null
}),
PayloadSources: keyMirror({
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 96a6cf2ab..3be83ed2f 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -13,6 +13,10 @@
.settings-modal {
width:800px;
max-width: 100%;
+ .modal-content {
+ width:800px;
+ max-width: 100%;
+ }
.modal-back {
width: 40px;
height: 56px;
diff --git a/web/templates/channel.html b/web/templates/channel.html
index aabd01ecb..c15cea178 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -25,8 +25,6 @@
<div id="new_channel_modal"></div>
<div id="post_deleted_modal"></div>
<div id="channel_notifications_modal"></div>
- <div id="channel_members_modal"></div>
- <div id="channel_invite_modal"></div>
<div id="team_members_modal"></div>
<div id="direct_channel_modal"></div>
<div id="channel_info_modal"></div>