summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md26
-rw-r--r--doc/install/Upgrade-Guide.md53
-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/confirm_modal.jsx79
-rw-r--r--web/react/components/invite_member_modal.jsx177
-rw-r--r--web/react/components/navbar_dropdown.jsx17
-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.jsx24
-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/react/utils/text_formatting.jsx26
-rw-r--r--web/sass-files/sass/partials/_settings.scss4
27 files changed, 710 insertions, 479 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0bf805e7d..3ac8a3261 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,32 @@ System Console
- IE 11 new minimum version for IE, since IE 10 share fell below 5% on desktop
- Safari 8 new minimum version for Safari, since Safari 7 fell below 1% on desktop
+#### Config.json Changes from v1.1 to v1.2
+
+Multiple settings were added to [`config.json`](./config/config.json). These options can be modified in the System Console, or manually updated in the existing config.json file. This is a list of changes and their new default values in a fresh install:
+- Under `TeamSettings` in `config.json`:
+ - Added: `"RestrictTeamNames": true` to control whether team names are restricted
+ - Added: `"EnableTeamListing": false` to control whether teams can be listed on the root page of the site
+- Under `ServiceSettings` in `config.json`
+ - Added: `EnableOutgoingWebhooks": true` to turn on outgoing webhooks
+
+#### Database Changes from v1.1 to v1.2
+
+The following is for informational purposes only, no action needed. Mattermost automatically upgrades database tables from the previous version's schema using only additions. Sessions table is dropped and rebuilt, no team data is affected by this.
+
+##### Channels Table
+1. Renamed `Description` to `Header`
+2. Added `Purpose` column with type `varchar(1024)`
+
+##### Preferences Table
+1. Added `Preferences` Table
+
+##### Teams Table
+1. Added `InviteId` column with type `varchar(32)`
+2. Added `AllowOpenInvite` column with type `tinyint(1)`
+3. Added `AllowTeamListing` column with type `tinyint(1)`
+4. Added `idx_teams_invite_id` index
+
#### Known Issues
- Microsoft Edge does not yet support drag and drop
diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md
index fc3a0711f..7f4eeaeb9 100644
--- a/doc/install/Upgrade-Guide.md
+++ b/doc/install/Upgrade-Guide.md
@@ -1,19 +1,53 @@
# Mattermost Upgrade Guide
-### Upgrading Mattermost in GitLab 8.0 to GitLab 8.1 with omnibus
+### Upgrading Mattermost to Next Major Release
+
+Each release of Mattermost contains logic to upgrade it from the previously major build version. For example, version 1.2 upgrades the database and configuration data schema for a Mattermost version 1.1 server. The following procedure outlines how to upgrade Mattermost to the next major release version.
+
+1. Download the **next major build release** of your server
+ 1. Determine the current version of your Mattermost server
+ 1. Go to any team site, opening the main menu at the top right of the left-hand sidebar and selecting **About Mattermost**
+ 2. Identify the next major build release of your Mattermost server from the list of [stable Mattermost releases](https://github.com/mattermost/platform/releases)
+ 1. For example, if your current version is 1.1.0, you want to select version 1.2.0.
+ 1. In some cases there will be **minor build releases**, such as 1.2.1 and 1.2.2. The minor build number indicates a bug fix or security issue release. Testing on minor build versions is less extensive than on major build versions and it is recommended that you use the minor build only if you need the specific additions included.
+ 3. Review Release Notes
+ 1. Check the release notes for the version of Mattermost you are able to install, and note any setting changes in the **Compatibility** section that apply to your deployment
+ 4. Download the `mattermost.tar.gz` file with the correct version for your upgrade
+ 1. You can use `wget` to retrieve a specific version. For example, to download v1.1.0 run `wget https://github.com/mattermost/platform/releases/download/v1.1.0/mattermost.tar.gz`
+2. Stop the Mattermost Server
+ 1. As best practice, consider posting to the Town Square channel of active teams pre-announcing the scheduled downtime to apply these upgrade procedures
+ 2. To stop the server run `sudo stop mattermost`
+2. Backup your data
+ 1. Back up your `config.json` file, which contains your system configuration. This will be used to restore your current settings after the new version is installed
+ 2. Backup your database using your organization's standard procedures for backing up MySQL or PostgreSQL
+ 3. If you're using local file storage, back up the location where files are stored
+4. Decompress `mattermost.tar.gz` and use its contents to replace the current version of Mattermost on disk
+ 1. Run `tar -xvzf mattermost.tar.gz`
+5. Restore the state of your server by copying the backed up version of `config.json` in place of the default `config.json`
+6. Start your server and address any setting changes relevant in the latest version of Mattermost
+ 1. Run `sudo start mattermost`
+ 2. The server will upgrade your database schema to be compatibile with the new release, as well as upgrade your `config.json` file to the latest format, using default values for new settings added
+ 3. Go to the System Console to update any settings that have been added or modified based on the **Compatibility** documentation in the release notes
+7. Test the system is working by going to the URL of an existing team
+
+### Upgrading from Mattermost Beta (Version 0.7)
+
+The following instructions apply to updating installations of Mattermost v0.7-Beta to Mattermost 1.1.
+
+#### Upgrading Mattermost in GitLab 8.0 to GitLab 8.1 with omnibus
Mattermost 0.7.1-beta in GitLab 8.0 was a pre-release of Mattermost and Mattermost v1.1.1 in GitLab 8.1 was [updated significantly](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#configjson-changes-from-v07-to-v10) to get to a stable, forwards-compatible platform for Mattermost.
The Mattermost team didn't think it made sense for GitLab omnibus to attempt an automated re-configuration of Mattermost (since 0.7.1-beta was a pre-release) given the scale of change, so we're providing instructions for GitLab users who have customized their Mattermost deployments in 8.0 to move to 8.1:
-1. Follow the [Upgrading Mattermost v0.7.1-beta to v1.1.1 instructions](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-v071-beta-to-v111) below to identify the settings in Mattermost's `config.json` file that differ from defaults and need to be updated from GitLab 8.0 to 8.1.
+1. Follow the [Upgrading Mattermost v0.7.1-beta to v1.1.1 instructions](https://github.com/mattermost/platform/blob/master/doc/install/Upgrade-Guide.md#upgrading-mattermost-v071-beta-to-v111) below to identify the settings in Mattermost's `config.json` file that differ from defaults and need to be updated from GitLab 8.0 to 8.1
2. Upgrade to GitLab 8.1 using omnibus, and allowing it overwrite `config.json` to the new Mattermost v1.1.1 format
-3. Manually update `config.json` to new settings identified in Step 1.
+3. Manually update `config.json` to new settings identified in Step 1
-Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
+Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
-### Upgrading Mattermost v0.7.1-beta to v1.1.1
+#### Upgrading Mattermost v0.7.1-beta to v1.1.1
_Note: [Mattermost v1.1.1](https://github.com/mattermost/platform/releases/tag/v1.1.1) is a special release of Mattermost v1.1 that upgrades the database to Mattermost v1.1 from EITHER Mattermost v0.7 or Mattermost v1.0. The following instructions are for upgrading from Mattermost v0.7.1-beta to v1.1.1 and skipping the upgrade to Mattermost v1.0._
@@ -23,11 +57,6 @@ If you've manually changed Mattermost v0.7.1-beta configuration by updating the
2. For each setting that you changed, check [the changelog documentation](https://github.com/mattermost/platform/blob/master/CHANGELOG.md#configjson-changes-from-v07-to-v10) on whether the configuration setting has changed between v0.7 and v1.1.1
-3. Update your new [`config.json` file in Mattermost v1.1](https://github.com/mattermost/platform/blob/v1.1.0/config/config.json), based on your preferences and the changelog documentation above.
-
-Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
-
-
-
-
+3. Update your new [`config.json` file in Mattermost v1.1](https://github.com/mattermost/platform/blob/v1.1.0/config/config.json), based on your preferences and the changelog documentation above
+Optionally, you can use the new [System Console user interface](https://github.com/mattermost/platform/blob/master/doc/install/Configuration-Settings.md) to make changes to your new `config.json` file.
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/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_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/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..2f872effd 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,15 @@ 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 +80,8 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <UserSettingsModal />,
- document.getElementById('user_settings_modal')
+ <InviteMemberModal />,
+ document.getElementById('invite_member_modal')
);
ReactDOM.render(
@@ -103,11 +100,6 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <MemberInviteModal teamType={props.TeamType} />,
- document.getElementById('invite_member_modal')
- );
-
- ReactDOM.render(
<EditChannelModal />,
document.getElementById('edit_channel_modal')
);
@@ -163,16 +155,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/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx
index 2de858a17..ac26107cc 100644
--- a/web/react/utils/text_formatting.jsx
+++ b/web/react/utils/text_formatting.jsx
@@ -64,22 +64,6 @@ export function doFormatText(text, options) {
return output;
}
-export function doFormatEmoticons(text) {
- const tokens = new Map();
-
- let output = Emoticons.handleEmoticons(text, tokens);
- output = replaceTokens(output, tokens);
-
- return output;
-}
-
-export function doFormatMentions(text) {
- const tokens = new Map();
- let output = autolinkAtMentions(text, tokens);
- output = replaceTokens(output, tokens);
- return output;
-}
-
export function sanitizeHtml(text) {
let output = text;
@@ -182,11 +166,15 @@ function autolinkAtMentions(text, tokens) {
}
let output = text;
- output = output.replace(/(^|\s)(@([a-z0-9.\-_]*))/gi, replaceAtMentionWithToken);
+ output = output.replace(/(^|[^a-z0-9])(@([a-z0-9.\-_]*))/gi, replaceAtMentionWithToken);
return output;
}
+function escapeRegex(text) {
+ return text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
function highlightCurrentMentions(text, tokens) {
let output = text;
@@ -226,7 +214,7 @@ function highlightCurrentMentions(text, tokens) {
}
for (const mention of UserStore.getCurrentMentionKeys()) {
- output = output.replace(new RegExp(`(^|\\W)(${mention})\\b`, 'gi'), replaceCurrentMentionWithToken);
+ output = output.replace(new RegExp(`(^|\\W)(${escapeRegex(mention)})\\b`, 'gi'), replaceCurrentMentionWithToken);
}
return output;
@@ -306,7 +294,7 @@ function highlightSearchTerm(text, tokens, searchTerm) {
return prefix + alias;
}
- return output.replace(new RegExp(`()(${searchTerm})`, 'gi'), replaceSearchTermWithToken);
+ return output.replace(new RegExp(`()(${escapeRegex(searchTerm)})`, 'gi'), replaceSearchTermWithToken);
}
function replaceTokens(text, tokens) {
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;