summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/admin_controller.jsx3
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx11
-rw-r--r--web/react/components/admin_console/legal_and_support_settings.jsx222
-rw-r--r--web/react/components/admin_console/service_settings.jsx2
-rw-r--r--web/react/components/edit_channel_header_modal.jsx52
-rw-r--r--web/react/components/invite_member_modal.jsx19
-rw-r--r--web/react/components/navbar_dropdown.jsx46
-rw-r--r--web/react/components/sidebar.jsx50
-rw-r--r--web/react/components/sidebar_right_menu.jsx43
-rw-r--r--web/react/components/team_general_tab.jsx133
-rw-r--r--web/react/components/tutorial/tutorial_intro_screens.jsx25
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx15
-rw-r--r--web/react/dispatcher/event_helpers.jsx7
-rw-r--r--web/react/stores/channel_store.jsx4
-rw-r--r--web/react/stores/preference_store.jsx14
-rw-r--r--web/react/stores/socket_store.jsx9
-rw-r--r--web/react/utils/client.jsx7
-rw-r--r--web/react/utils/constants.jsx4
-rw-r--r--web/react/utils/utils.jsx5
20 files changed, 515 insertions, 158 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 4f144b0dd..e587c4f84 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -18,6 +18,7 @@ import GitLabSettingsTab from './gitlab_settings.jsx';
import SqlSettingsTab from './sql_settings.jsx';
import TeamSettingsTab from './team_settings.jsx';
import ServiceSettingsTab from './service_settings.jsx';
+import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx';
import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from './team_analytics.jsx';
@@ -148,6 +149,8 @@ export default class AdminController extends React.Component {
tab = <TeamSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'service_settings') {
tab = <ServiceSettingsTab config={this.state.config} />;
+ } else if (this.state.selected === 'legal_and_support_settings') {
+ tab = <LegalAndSupportSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index cc98c495e..da445da37 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -108,7 +108,7 @@ export default class AdminSidebar extends React.Component {
<a
href='#'
onClick={this.handleClick.bind(this, 'team_users', team.id)}
- className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id)}
+ className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id) + ' ' + this.isSelected('team_analytics', team.id)}
>
{team.name}
<OverlayTrigger
@@ -252,6 +252,15 @@ export default class AdminSidebar extends React.Component {
{'GitLab Settings'}
</a>
</li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('legal_and_support_settings')}
+ onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
+ >
+ {'Legal and Support Settings'}
+ </a>
+ </li>
</ul>
<ul className='nav nav__sub-menu'>
<li>
diff --git a/web/react/components/admin_console/legal_and_support_settings.jsx b/web/react/components/admin_console/legal_and_support_settings.jsx
new file mode 100644
index 000000000..b00e4b6bd
--- /dev/null
+++ b/web/react/components/admin_console/legal_and_support_settings.jsx
@@ -0,0 +1,222 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Client from '../../utils/client.jsx';
+import * as AsyncClient from '../../utils/async_client.jsx';
+
+export default class LegalAndSupportSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ saveNeeded: false,
+ serverError: null
+ };
+ }
+
+ handleChange() {
+ var s = {saveNeeded: true, serverError: this.state.serverError};
+ this.setState(s);
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+ $('#save-button').button('loading');
+
+ var config = this.props.config;
+
+ config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim();
+ config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim();
+ config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim();
+ config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim();
+ config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim();
+ config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim();
+
+ Client.saveConfig(
+ config,
+ () => {
+ AsyncClient.getConfig();
+ this.setState({
+ serverError: null,
+ saveNeeded: false
+ });
+ $('#save-button').button('reset');
+ },
+ (err) => {
+ this.setState({
+ serverError: err.message,
+ saveNeeded: true
+ });
+ $('#save-button').button('reset');
+ }
+ );
+ }
+
+ render() {
+ var serverError = '';
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var saveClass = 'btn';
+ if (this.state.saveNeeded) {
+ saveClass = 'btn btn-primary';
+ }
+
+ return (
+ <div className='wrapper--fixed'>
+
+ <h3>{'Legal and Support Settings'}</h3>
+ <form
+ className='form-horizontal'
+ role='form'
+ >
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='TermsOfServiceLink'
+ >
+ {'Terms of Service link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='TermsOfServiceLink'
+ ref='TermsOfServiceLink'
+ defaultValue={this.props.config.SupportSettings.TermsOfServiceLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='PrivacyPolicyLink'
+ >
+ {'Privacy Policy link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='PrivacyPolicyLink'
+ ref='PrivacyPolicyLink'
+ defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='AboutLink'
+ >
+ {'About link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='AboutLink'
+ ref='AboutLink'
+ defaultValue={this.props.config.SupportSettings.AboutLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='HelpLink'
+ >
+ {'Help link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='HelpLink'
+ ref='HelpLink'
+ defaultValue={this.props.config.SupportSettings.HelpLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='ReportAProblemLink'
+ >
+ {'Report a Problem link:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='ReportAProblemLink'
+ ref='ReportAProblemLink'
+ defaultValue={this.props.config.SupportSettings.ReportAProblemLink}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='SupportEmail'
+ >
+ {'Support email:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='SupportEmail'
+ ref='SupportEmail'
+ defaultValue={this.props.config.SupportSettings.SupportEmail}
+ onChange={this.handleChange}
+ />
+ <p className='help-text'>{'Email shown during tutorial for end users to ask support questions.'}</p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <div className='col-sm-12'>
+ {serverError}
+ <button
+ disabled={!this.state.saveNeeded}
+ type='submit'
+ className={saveClass}
+ onClick={this.handleSubmit}
+ id='save-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ >
+ {'Save'}
+ </button>
+ </div>
+ </div>
+
+ </form>
+ </div>
+ );
+ }
+}
+
+LegalAndSupportSettings.propTypes = {
+ config: React.PropTypes.object
+};
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index 908eb709a..1f5faf1d4 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -36,7 +36,7 @@ export default class ServiceSettings extends React.Component {
config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
- config.ServiceSettings.EnableOutgoingWebhooks = React.findDOMNode(this.refs.EnableOutgoingWebhooks).checked;
+ config.ServiceSettings.EnableOutgoingWebhooks = ReactDOM.findDOMNode(this.refs.EnableOutgoingWebhooks).checked;
config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked;
config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked;
config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked;
diff --git a/web/react/components/edit_channel_header_modal.jsx b/web/react/components/edit_channel_header_modal.jsx
index 209e30fcc..e4817f6e4 100644
--- a/web/react/components/edit_channel_header_modal.jsx
+++ b/web/react/components/edit_channel_header_modal.jsx
@@ -1,8 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import * as Client from '../utils/client.jsx';
-import * as AsyncClient from '../utils/async_client.jsx';
+import Constants from '../utils/constants.jsx';
import * as Utils from '../utils/utils.jsx';
const Modal = ReactBootstrap.Modal;
@@ -11,12 +12,14 @@ export default class EditChannelHeaderModal extends React.Component {
constructor(props) {
super(props);
- this.handleEdit = this.handleEdit.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
this.onShow = this.onShow.bind(this);
this.onHide = this.onHide.bind(this);
this.state = {
+ header: props.channel.header,
serverError: ''
};
}
@@ -27,27 +30,38 @@ export default class EditChannelHeaderModal extends React.Component {
}
}
+ componentWillReceiveProps(nextProps) {
+ if (this.props.channel.header !== nextProps.channel.header) {
+ this.setState({
+ header: nextProps.channel.header
+ });
+ }
+ }
+
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
this.onShow();
}
}
- handleEdit() {
- var data = {};
- data.channel_id = this.props.channel.id;
-
- if (data.channel_id.length !== 26) {
- return;
- }
-
- data.channel_header = ReactDOM.findDOMNode(this.refs.textarea).value;
+ handleChange(e) {
+ this.setState({
+ header: e.target.value
+ });
+ }
- Client.updateChannelHeader(data,
- () => {
+ handleSubmit() {
+ Client.updateChannelHeader(
+ this.props.channel.id,
+ this.state.header,
+ (channel) => {
this.setState({serverError: ''});
- AsyncClient.getChannel(this.props.channel.id);
this.onHide();
+
+ AppDispatcher.handleServerAction({
+ type: Constants.ActionTypes.RECIEVED_CHANNEL,
+ channel
+ });
},
(err) => {
if (err.message === 'Invalid channel_header parameter') {
@@ -66,7 +80,8 @@ export default class EditChannelHeaderModal extends React.Component {
onHide() {
this.setState({
- serverError: ''
+ serverError: '',
+ header: this.props.channel.header
});
this.props.onHide();
@@ -94,7 +109,8 @@ export default class EditChannelHeaderModal extends React.Component {
rows='6'
id='edit_header'
maxLength='1024'
- defaultValue={this.props.channel.header}
+ value={this.state.header}
+ onChange={this.handleChange}
/>
{serverError}
</Modal.Body>
@@ -102,14 +118,14 @@ export default class EditChannelHeaderModal extends React.Component {
<button
type='button'
className='btn btn-default'
- onClick={this.props.onHide}
+ onClick={this.onHide}
>
{'Cancel'}
</button>
<button
type='button'
className='btn btn-primary'
- onClick={this.handleEdit}
+ onClick={this.handleSubmit}
>
{'Save'}
</button>
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 649ec7321..56bc00a7e 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -33,6 +33,7 @@ export default class InviteMemberModal extends React.Component {
firstNameErrors: {},
lastNameErrors: {},
emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
+ userCreationEnabled: global.window.mm_config.EnableUserCreation === 'true',
showConfirmModal: false,
isSendingEmails: false
};
@@ -252,7 +253,7 @@ export default class InviteMemberModal extends React.Component {
ref={'first_name' + index}
placeholder='First name'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{firstNameError}
@@ -266,7 +267,7 @@ export default class InviteMemberModal extends React.Component {
ref={'last_name' + index}
placeholder='Last name'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{lastNameError}
@@ -285,7 +286,7 @@ export default class InviteMemberModal extends React.Component {
className='form-control'
placeholder='email@domain.com'
maxLength='64'
- disabled={!this.state.emailEnabled}
+ disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
spellCheck='false'
/>
{emailError}
@@ -303,7 +304,7 @@ export default class InviteMemberModal extends React.Component {
var content = null;
var sendButton = null;
- if (this.state.emailEnabled) {
+ if (this.state.emailEnabled && this.state.userCreationEnabled) {
content = (
<div>
{serverError}
@@ -337,7 +338,7 @@ export default class InviteMemberModal extends React.Component {
{sendButtonLabel}
</button>
);
- } else {
+ } else if (this.state.userCreationEnabled) {
var teamInviteLink = null;
if (currentUser && TeamStore.getCurrent().type === 'O') {
var link = (
@@ -358,10 +359,16 @@ export default class InviteMemberModal extends React.Component {
content = (
<div>
- <p>Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.</p>
+ <p>{'Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.'}</p>
{teamInviteLink}
</div>
);
+ } else {
+ content = (
+ <div>
+ <p>{'User creation has been disabled for your team. Please ask your team administrator for details.'}</p>
+ </div>
+ );
}
return (
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index c286ee6f9..d4ec5a5f5 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -184,6 +184,34 @@ export default class NavbarDropdown extends React.Component {
);
}
+ let helpLink = null;
+ if (global.window.mm_config.HelpLink) {
+ helpLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.HelpLink}
+ >
+ {'Help'}
+ </a>
+ </li>
+ );
+ }
+
+ let reportLink = null;
+ if (global.window.mm_config.ReportAProblemLink) {
+ reportLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.ReportAProblemLink}
+ >
+ {'Report a Problem'}
+ </a>
+ </li>
+ );
+ }
+
return (
<ul className='nav navbar-nav navbar-right'>
<li
@@ -230,22 +258,8 @@ export default class NavbarDropdown extends React.Component {
{sysAdminLink}
{teams}
<li className='divider'></li>
- <li>
- <a
- target='_blank'
- href='/static/help/help.html'
- >
- {'Help'}
- </a>
- </li>
- <li>
- <a
- target='_blank'
- href='/static/help/report_problem.html'
- >
- {'Report a Problem'}
- </a>
- </li>
+ {helpLink}
+ {reportLink}
<li>
<a
href='#'
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 3d7f449d1..8393440cb 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -71,49 +71,47 @@ export default class Sidebar extends React.Component {
getStateFromStores() {
const members = ChannelStore.getAllMembers();
const currentChannelId = ChannelStore.getCurrentId();
+ const currentUserId = UserStore.getCurrentId();
const channels = Object.assign([], ChannelStore.getAll());
channels.sort((a, b) => a.display_name.localeCompare(b.display_name));
const publicChannels = channels.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
const privateChannels = channels.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
- const directChannels = channels.filter((channel) => channel.type === Constants.DM_CHANNEL);
const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
- var visibleDirectChannels = [];
- for (var i = 0; i < directChannels.length; i++) {
- const dm = directChannels[i];
- const teammate = Utils.getDirectTeammate(dm.id);
- if (!teammate) {
+ const directChannels = [];
+ for (const preference of preferences) {
+ if (preference.value !== 'true') {
continue;
}
- const member = members[dm.id];
- const msgCount = dm.total_msg_count - member.msg_count;
+ const teammateId = preference.name;
- // always show a channel if either it is the current one or if it is unread, but it is not currently being left
- const forceShow = (currentChannelId === dm.id || msgCount > 0) && !this.isLeaving.get(dm.id);
- const preferenceShow = preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'));
+ let directChannel = channels.find(Utils.isDirectChannelForUser.bind(null, teammateId));
- if (preferenceShow || forceShow) {
- dm.display_name = Utils.displayUsername(teammate.id);
- dm.teammate_id = teammate.id;
- dm.status = UserStore.getStatus(teammate.id);
+ // a direct channel doesn't exist yet so create a fake one
+ if (!directChannel) {
+ directChannel = {
+ name: Utils.getDirectChannelName(currentUserId, teammateId),
+ last_post_at: 0,
+ total_msg_count: 0,
+ type: Constants.DM_CHANNEL,
+ fake: true
+ };
+ }
- visibleDirectChannels.push(dm);
+ directChannel.display_name = Utils.displayUsername(teammateId);
+ directChannel.teammate_id = teammateId;
+ directChannel.status = UserStore.getStatus(teammateId);
- if (forceShow && !preferenceShow) {
- // make sure that unread direct channels are visible
- const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
- AsyncClient.savePreferences([preference]);
- }
- }
+ directChannels.push(directChannel);
}
- const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList(true).length - visibleDirectChannels.length;
+ directChannels.sort(this.sortChannelsByDisplayName);
- visibleDirectChannels.sort(this.sortChannelsByDisplayName);
+ const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList(true).length - directChannels.length;
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
@@ -122,7 +120,7 @@ export default class Sidebar extends React.Component {
members,
publicChannels,
privateChannels,
- visibleDirectChannels,
+ directChannels,
hiddenDirectChannelCount,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER
@@ -484,7 +482,7 @@ export default class Sidebar extends React.Component {
const privateChannelItems = this.state.privateChannels.map(this.createChannelElement);
- const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => {
+ const directMessageItems = this.state.directChannels.map((channel, index, arr) => {
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
});
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index d93d146d8..20c2bf696 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -49,7 +49,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={EventHelpers.showInviteMemberModal}
>
- <i className='fa fa-user'></i>Invite New Member
+ <i className='fa fa-user'></i>{'Invite New Member'}
</a>
</li>
);
@@ -75,7 +75,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='fa fa-globe'></i>Team Settings</a>
+ ><i className='fa fa-globe'></i>{'Team Settings'}</a>
</li>
);
manageLink = (
@@ -93,7 +93,7 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='fa fa-wrench'></i>System Console</a>
+ <i className='fa fa-wrench'></i>{'System Console'}</a>
</li>
);
}
@@ -107,6 +107,27 @@ export default class SidebarRightMenu extends React.Component {
teamDisplayName = this.props.teamDisplayName;
}
+ let helpLink = null;
+ if (global.window.mm_config.HelpLink) {
+ helpLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.HelpLink}
+ ><i className='fa fa-question'></i>{'Help'}</a></li>
+ );
+ }
+
+ let reportLink = null;
+ if (global.window.mm_config.ReportAProblemLink) {
+ reportLink = (
+ <li>
+ <a
+ target='_blank'
+ href={global.window.mm_config.ReportAProblemLink}
+ ><i className='fa fa-phone'></i>{'Report a Problem'}</a></li>
+ );
+ }
return (
<div>
<div className='team__header theme'>
@@ -123,7 +144,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='fa fa-cog'></i>Account Settings
+ <i className='fa fa-cog'></i>{'Account Settings'}
</a>
</li>
{teamSettingsLink}
@@ -135,18 +156,10 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='fa fa-sign-out'></i>Logout</a></li>
+ ><i className='fa fa-sign-out'></i>{'Logout'}</a></li>
<li className='divider'></li>
- <li>
- <a
- target='_blank'
- href='/static/help/help.html'
- ><i className='fa fa-question'></i>Help</a></li>
- <li>
- <a
- target='_blank'
- href='/static/help/report_problem.html'
- ><i className='fa fa-phone'></i>Report a Problem</a></li>
+ {helpLink}
+ {reportLink}
</ul>
</div>
<UserSettingsModal
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 03715d585..dc615f2e8 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -12,6 +12,7 @@ export default class GeneralTab extends React.Component {
constructor(props) {
super(props);
+ this.updateSection = this.updateSection.bind(this);
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this);
this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this);
@@ -27,11 +28,22 @@ export default class GeneralTab extends React.Component {
this.handleTeamListingRadio = this.handleTeamListingRadio.bind(this);
this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this);
- this.state = {
- name: props.team.display_name,
- invite_id: props.team.invite_id,
- allow_open_invite: props.team.allow_open_invite,
- allow_team_listing: props.team.allow_team_listing,
+ this.state = this.setupInitialState(props);
+ }
+
+ updateSection(section) {
+ this.setState(this.setupInitialState(this.props));
+ this.props.updateSection(section);
+ }
+
+ setupInitialState(props) {
+ const team = props.team;
+
+ return {
+ name: team.display_name,
+ invite_id: team.invite_id,
+ allow_open_invite: team.allow_open_invite,
+ allow_team_listing: team.allow_team_listing,
serverError: '',
clientError: ''
};
@@ -71,7 +83,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -91,7 +103,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -129,7 +141,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -164,7 +176,7 @@ export default class GeneralTab extends React.Component {
(team) => {
TeamStore.saveTeam(team);
TeamStore.emitChange();
- this.props.updateSection('');
+ this.updateSection('');
},
(err) => {
state.serverError = err.message;
@@ -180,8 +192,7 @@ export default class GeneralTab extends React.Component {
}
handleClose() {
- this.setState({clientError: '', serverError: ''});
- this.props.updateSection('');
+ this.updateSection('');
}
componentDidMount() {
@@ -195,36 +206,36 @@ export default class GeneralTab extends React.Component {
onUpdateNameSection(e) {
e.preventDefault();
if (this.props.activeSection === 'name') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('name');
+ this.updateSection('name');
}
}
onUpdateInviteIdSection(e) {
e.preventDefault();
if (this.props.activeSection === 'invite_id') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('invite_id');
+ this.updateSection('invite_id');
}
}
onUpdateOpenInviteSection(e) {
e.preventDefault();
if (this.props.activeSection === 'open_invite') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('open_invite');
+ this.updateSection('open_invite');
}
}
onUpdateTeamListingSection(e) {
e.preventDefault();
if (this.props.activeSection === 'team_listing') {
- this.props.updateSection('');
+ this.updateSection('');
} else {
- this.props.updateSection('team_listing');
+ this.updateSection('team_listing');
}
}
@@ -248,44 +259,59 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
+ const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true';
+
let teamListingSection;
if (this.props.activeSection === 'team_listing') {
- const inputs = [
- <div key='userTeamListingOptions'>
- <div className='radio'>
- <label>
- <input
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, true)}
- />
- {'Yes'}
- </label>
- <br/>
+ const inputs = [];
+ let submitHandle = null;
+
+ if (enableTeamListing) {
+ submitHandle = this.handleTeamListingSubmit;
+
+ inputs.push(
+ <div key='userTeamListingOptions'>
+ <div className='radio'>
+ <label>
+ <input
+ name='userTeamListingOptions'
+ type='radio'
+ defaultChecked={this.state.allow_team_listing}
+ onChange={this.handleTeamListingRadio.bind(this, true)}
+ />
+ {'Yes'}
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ ref='teamListingRadioNo'
+ name='userTeamListingOptions'
+ type='radio'
+ defaultChecked={!this.state.allow_team_listing}
+ onChange={this.handleTeamListingRadio.bind(this, false)}
+ />
+ {'No'}
+ </label>
+ <br/>
+ </div>
+ <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
</div>
- <div className='radio'>
- <label>
- <input
- ref='teamListingRadioNo'
- name='userTeamListingOptions'
- type='radio'
- defaultChecked={!this.state.allow_team_listing}
- onChange={this.handleTeamListingRadio.bind(this, false)}
- />
- {'No'}
- </label>
- <br/>
+ );
+ } else {
+ inputs.push(
+ <div key='userTeamListingOptions'>
+ <div><br/>{'Contact your system administrator to turn on the team directory on the system home page.'}</div>
</div>
- <div><br/>{'Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.'}</div>
- </div>
- ];
+ );
+ }
teamListingSection = (
<SettingItemMax
title='Include this team in the Team Directory'
inputs={inputs}
- submit={this.handleTeamListingSubmit}
+ submit={submitHandle}
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateTeamListingSection}
@@ -293,10 +319,15 @@ export default class GeneralTab extends React.Component {
);
} else {
let describe = '';
- if (this.state.allow_team_listing === true) {
- describe = 'Yes';
+
+ if (enableTeamListing) {
+ if (this.state.allow_team_listing === true) {
+ describe = 'Yes';
+ } else {
+ describe = 'No';
+ }
} else {
- describe = 'No';
+ describe = 'Team directory is turned off for this system.';
}
teamListingSection = (
diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx
index 9360d31f8..7ab1e5512 100644
--- a/web/react/components/tutorial/tutorial_intro_screens.jsx
+++ b/web/react/components/tutorial/tutorial_intro_screens.jsx
@@ -112,23 +112,30 @@ export default class TutorialIntroScreens extends React.Component {
const circles = this.createCircles();
- return (
- <div>
- <h3>{'You’re all set'}</h3>
- <p>
- {inviteModalLink}
- {' when you’re ready.'}
- </p>
+ let supportInfo = null;
+ if (global.window.mm_config.SupportEmail) {
+ supportInfo = (
<p>
{'Need anything, just email us at '}
<a
- href='mailto:feedback@mattermost.com'
+ href={'mailto:' + global.window.mm_config.SupportEmail}
target='_blank'
>
- {'feedback@mattermost.com'}
+ {global.window.mm_config.SupportEmail}
</a>
{'.'}
</p>
+ );
+ }
+
+ return (
+ <div>
+ <h3>{'You’re all set'}</h3>
+ <p>
+ {inviteModalLink}
+ {' when you’re ready.'}
+ </p>
+ {supportInfo}
{'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'}
{circles}
</div>
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 9c0fe3709..fdbac9831 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from '../loading_screen.jsx';
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 962efd7a2..7c1a1297f 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -51,7 +51,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
if (user.username === username) {
- this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -66,7 +66,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const nickname = this.state.nickname.trim();
if (user.nickname === nickname) {
- this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -82,7 +82,7 @@ export default class UserSettingsGeneralTab extends React.Component {
const lastName = this.state.lastName.trim();
if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''});
+ this.updateSection('');
return;
}
@@ -98,10 +98,6 @@ export default class UserSettingsGeneralTab extends React.Component {
const email = this.state.email.trim().toLowerCase();
const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
- if (user.email === email) {
- return;
- }
-
if (email === '' || !Utils.isEmail(email)) {
this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
return;
@@ -112,6 +108,11 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
+ if (user.email === email) {
+ this.updateSection('');
+ return;
+ }
+
user.email = email;
this.submitUser(user, true);
}
diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx
index bc1132765..297367ce9 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/dispatcher/event_helpers.jsx
@@ -173,3 +173,10 @@ export function emitClearSuggestions(suggestionId) {
id: suggestionId
});
}
+
+export function emitPreferenceChangedEvent(preference) {
+ AppDispatcher.handleServerAction({
+ type: Constants.ActionTypes.RECIEVED_PREFERENCE,
+ preference
+ });
+}
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 0bfde77b4..afc960fcf 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -317,7 +317,9 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.RECIEVED_CHANNEL:
ChannelStore.pStoreChannel(action.channel);
- ChannelStore.pStoreChannelMember(action.member);
+ if (action.member) {
+ ChannelStore.pStoreChannelMember(action.member);
+ }
currentId = ChannelStore.getCurrentId();
if (currentId) {
ChannelStore.resetCounts(currentId);
diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx
index 068bc29c2..543129aca 100644
--- a/web/react/stores/preference_store.jsx
+++ b/web/react/stores/preference_store.jsx
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from '../utils/constants.jsx';
@@ -90,8 +90,8 @@ class PreferenceStoreClass extends EventEmitter {
return preference;
}
- emitChange(preferences) {
- this.emit(CHANGE_EVENT, preferences);
+ emitChange() {
+ this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
@@ -106,6 +106,12 @@ class PreferenceStoreClass extends EventEmitter {
const action = payload.action;
switch (action.type) {
+ case ActionTypes.RECIEVED_PREFERENCE: {
+ const preference = action.preference;
+ this.setPreference(preference.category, preference.name, preference.value);
+ this.emitChange();
+ break;
+ }
case ActionTypes.RECIEVED_PREFERENCES: {
const preferences = this.getAllPreferences();
@@ -114,7 +120,7 @@ class PreferenceStoreClass extends EventEmitter {
}
this.setAllPreferences(preferences);
- this.emitChange(preferences);
+ this.emitChange();
break;
}
}
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index d5aed40cf..24fa79ca6 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -136,6 +136,10 @@ class SocketStoreClass extends EventEmitter {
handleChannelViewedEvent(msg);
break;
+ case SocketEvents.PREFERENCE_CHANGED:
+ handlePreferenceChangedEvent(msg);
+ break;
+
default:
}
}
@@ -281,6 +285,11 @@ function handleChannelViewedEvent(msg) {
}
}
+function handlePreferenceChangedEvent(msg) {
+ const preference = JSON.parse(msg.props.preference);
+ EventHelpers.emitPreferenceChangedEvent(preference);
+}
+
var SocketStore = new SocketStoreClass();
/*SocketStore.dispatchToken = AppDispatcher.register((payload) => {
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 09e962161..5d02a8c88 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -590,7 +590,12 @@ export function updateChannel(channel, success, error) {
track('api', 'api_channels_update');
}
-export function updateChannelHeader(data, success, error) {
+export function updateChannelHeader(channelId, header, success, error) {
+ const data = {
+ channel_id: channelId,
+ channel_header: header
+ };
+
$.ajax({
url: '/api/v1/channels/update_header',
dataType: 'json',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 891bc4624..d23c18b5d 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -35,6 +35,7 @@ export default {
RECIEVED_AUDITS: null,
RECIEVED_TEAMS: null,
RECIEVED_STATUSES: null,
+ RECIEVED_PREFERENCE: null,
RECIEVED_PREFERENCES: null,
RECIEVED_MSG: null,
@@ -74,7 +75,8 @@ export default {
NEW_USER: 'new_user',
USER_ADDED: 'user_added',
USER_REMOVED: 'user_removed',
- TYPING: 'typing'
+ TYPING: 'typing',
+ PREFERENCE_CHANGED: 'preference_changed'
},
//SPECIAL_MENTIONS: ['all', 'channel'],
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index c2e4276b0..fb8b89252 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1138,6 +1138,11 @@ export function getUserIdFromChannelName(channel) {
return otherUserId;
}
+// Returns true if the given channel is a direct channel between the current user and the given one
+export function isDirectChannelForUser(otherUserId, channel) {
+ return channel.type === Constants.DM_CHANNEL && getUserIdFromChannelName(channel) === otherUserId;
+}
+
export function importSlack(file, success, error) {
var formData = new FormData();
formData.append('file', file, file.name);