summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2016-02-04 10:55:44 -0800
committer=Corey Hulen <corey@hulen.com>2016-02-04 10:55:44 -0800
commitcfd4e4de963703191e750ba0abb901c5f6c0c3e4 (patch)
treed01a656eeeaba9603568a54a164f378608c4a939 /web/react/components
parentf1bf70624288bd192b9523764f239eee2a022304 (diff)
parent44c19ee443831e0e94b5738ecb21a64ce7643247 (diff)
downloadchat-cfd4e4de963703191e750ba0abb901c5f6c0c3e4.tar.gz
chat-cfd4e4de963703191e750ba0abb901c5f6c0c3e4.tar.bz2
chat-cfd4e4de963703191e750ba0abb901c5f6c0c3e4.zip
Merge branch 'master' into PLT-1851
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/admin_console/service_settings.jsx104
-rw-r--r--web/react/components/channel_loader.jsx10
-rw-r--r--web/react/components/create_comment.jsx5
-rw-r--r--web/react/components/create_post.jsx7
-rw-r--r--web/react/components/file_upload.jsx6
-rw-r--r--web/react/components/search_results_item.jsx118
-rw-r--r--web/react/components/user_settings/manage_command_hooks.jsx652
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx43
9 files changed, 876 insertions, 71 deletions
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index 7021900eb..2cc68d1ed 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -75,6 +75,8 @@ class ServiceSettings extends React.Component {
config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked;
config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked;
config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked;
+ config.ServiceSettings.EnableCommands = ReactDOM.findDOMNode(this.refs.EnableCommands).checked;
+ config.ServiceSettings.EnableOnlyAdminIntegrations = ReactDOM.findDOMNode(this.refs.EnableOnlyAdminIntegrations).checked;
//config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
@@ -389,11 +391,105 @@ class ServiceSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
+ htmlFor='EnableCommands'
+ >
+ <FormattedMessage
+ id='admin.service.cmdsTitle'
+ defaultMessage='Enable Slash Commands: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableCommands'
+ value='true'
+ ref='EnableCommands'
+ defaultChecked={this.props.config.ServiceSettings.EnableCommands}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableCommands'
+ value='false'
+ defaultChecked={!this.props.config.ServiceSettings.EnableCommands}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.cmdsDesc'
+ defaultMessage='When true, user created slash commands will be allowed.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='EnableOnlyAdminIntegrations'
+ >
+ <FormattedMessage
+ id='admin.service.integrationAdmin'
+ defaultMessage='Enable Integrations for Admin Only: '
+ />
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableOnlyAdminIntegrations'
+ value='true'
+ ref='EnableOnlyAdminIntegrations'
+ defaultChecked={this.props.config.ServiceSettings.EnableOnlyAdminIntegrations}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.service.true'
+ defaultMessage='true'
+ />
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='EnableOnlyAdminIntegrations'
+ value='false'
+ defaultChecked={!this.props.config.ServiceSettings.EnableOnlyAdminIntegrations}
+ onChange={this.handleChange}
+ />
+ <FormattedMessage
+ id='admin.service.false'
+ defaultMessage='false'
+ />
+ </label>
+ <p className='help-text'>
+ <FormattedMessage
+ id='admin.service.integrationAdminDesc'
+ defaultMessage='When true, user created integrations can only be created by admins.'
+ />
+ </p>
+ </div>
+ </div>
+
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
htmlFor='EnablePostUsernameOverride'
>
<FormattedMessage
id='admin.service.overrideTitle'
- defaultMessage='Enable Overriding Usernames from Webhooks: '
+ defaultMessage='Enable Overriding Usernames from Webhooks and Slash Commands: '
/>
</label>
<div className='col-sm-8'>
@@ -427,7 +523,7 @@ class ServiceSettings extends React.Component {
<p className='help-text'>
<FormattedMessage
id='admin.service.overrideDescription'
- defaultMessage='When true, webhooks will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'
+ defaultMessage='When true, webhooks and slash commands will be allowed to change the username they are posting as. Note, combined with allowing icon overriding, this could open users up to phishing attacks.'
/>
</p>
</div>
@@ -440,7 +536,7 @@ class ServiceSettings extends React.Component {
>
<FormattedMessage
id='admin.service.iconTitle'
- defaultMessage='Enable Overriding Icon from Webhooks: '
+ defaultMessage='Enable Overriding Icon from Webhooks and Slash Commands: '
/>
</label>
<div className='col-sm-8'>
@@ -474,7 +570,7 @@ class ServiceSettings extends React.Component {
<p className='help-text'>
<FormattedMessage
id='admin.service.iconDescription'
- defaultMessage='When true, webhooks will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'
+ defaultMessage='When true, webhooks and slash commands will be allowed to change the icon they post with. Note, combined with allowing username overriding, this could open users up to phishing attacks.'
/>
</p>
</div>
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 15571ad93..174c8c4e1 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -128,6 +128,16 @@ class ChannelLoader extends React.Component {
}
});
+ $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
+ } else {
+ $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
+ }
+ });
+
$('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
if (ev.type === 'mouseenter') {
$(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 1b552838a..8c49315e7 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -202,8 +202,7 @@ class CreateComment extends React.Component {
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault();
- const channelId = ChannelStore.getCurrentId();
- const lastPost = PostStore.getCurrentUsersLatestPost(channelId, this.props.rootId);
+ const lastPost = PostStore.getCurrentUsersLatestPost(this.props.channelId, this.props.rootId);
if (!lastPost) {
return;
}
@@ -402,4 +401,4 @@ CreateComment.propTypes = {
rootId: React.PropTypes.string.isRequired
};
-export default injectIntl(CreateComment); \ No newline at end of file
+export default injectIntl(CreateComment);
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index ed672cd34..20892898e 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -155,15 +155,10 @@ class CreatePost extends React.Component {
post.message,
false,
(data) => {
- if (data.response === 'not implemented') {
- this.sendMessage(post);
- return;
- }
-
PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- if (data.goto_location.length > 0) {
+ if (data.goto_location && data.goto_location.length > 0) {
window.location.href = data.goto_location;
}
},
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index 626dbc5b3..746289653 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -101,9 +101,9 @@ class FileUpload extends React.Component {
} else if (tooLargeFiles.length > 1) {
var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
- this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), files: tooLargeFilenames}));
+ this.props.onUploadError(formatMessage(holders.filesAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filenames: tooLargeFilenames}));
} else if (tooLargeFiles.length > 0) {
- this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), file: tooLargeFiles[0].name}));
+ this.props.onUploadError(formatMessage(holders.fileAbove, {max: (Constants.MAX_FILE_SIZE / 1000000), filename: tooLargeFiles[0].name}));
}
}
@@ -329,4 +329,4 @@ FileUpload.propTypes = {
postType: React.PropTypes.string
};
-export default injectIntl(FileUpload); \ No newline at end of file
+export default injectIntl(FileUpload);
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 0ad091d5b..544ba920a 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -59,64 +59,74 @@ export default class SearchResultsItem extends React.Component {
};
return (
- <div
- className='search-item-container post'
- >
- <div className='search-channel__name'>{channelName}</div>
- <div className='post__content'>
- <div className='post__img'>
- <img
- src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
- height='36'
- width='36'
+ <div className='search-item__container'>
+ <div className='date-separator'>
+ <hr className='separator__hr' />
+ <div className='separator__text'>
+ <FormattedDate
+ value={this.props.post.create_at}
+ day='numeric'
+ month='long'
+ year='numeric'
/>
</div>
- <div>
- <ul className='post__header'>
- <li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
- <li className='col'>
- <time className='search-item-time'>
- <FormattedDate
- value={this.props.post.create_at}
- day='numeric'
- month='long'
- year='numeric'
- hour12={true}
- hour='2-digit'
- minute='2-digit'
- />
- </time>
- </li>
- <li>
- <a
- href='#'
- className='search-item__jump'
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='search_item.jump'
- defaultMessage='Jump'
- />
- </a>
- </li>
- <li>
- <a
- href='#'
- className='comment-icon__container search-item__comment'
- onClick={this.handleFocusRHSClick}
- >
- <span
- className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
- />
- </a>
- </li>
- </ul>
- <div className='search-item-snippet'>
- <span
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
+ </div>
+ <div
+ className='post'
+ >
+ <div className='search-channel__name'>{channelName}</div>
+ <div className='post__content'>
+ <div className='post__img'>
+ <img
+ src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
+ height='36'
+ width='36'
/>
</div>
+ <div>
+ <ul className='post__header'>
+ <li className='col__name'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
+ <li className='col'>
+ <time className='search-item-time'>
+ <FormattedDate
+ value={this.props.post.create_at}
+ hour12={true}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ </time>
+ </li>
+ <li>
+ <a
+ href='#'
+ className='search-item__jump'
+ onClick={this.handleClick}
+ >
+ <FormattedMessage
+ id='search_item.jump'
+ defaultMessage='Jump'
+ />
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
+ className='comment-icon__container search-item__comment'
+ onClick={this.handleFocusRHSClick}
+ >
+ <span
+ className='comment-icon'
+ dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
+ />
+ </a>
+ </li>
+ </ul>
+ <div className='search-item-snippet'>
+ <span
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, formattingOptions)}}
+ />
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx
new file mode 100644
index 000000000..bcf0a6c82
--- /dev/null
+++ b/web/react/components/user_settings/manage_command_hooks.jsx
@@ -0,0 +1,652 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import LoadingScreen from '../loading_screen.jsx';
+
+import * as Client from '../../utils/client.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ requestTypePost: {
+ id: 'user.settings.cmds.request_type_post',
+ defaultMessage: 'POST'
+ },
+ requestTypeGet: {
+ id: 'user.settings.cmds.request_type_get',
+ defaultMessage: 'GET'
+ },
+ addDisplayNamePlaceholder: {
+ id: 'user.settings.cmds.add_display_name.placeholder',
+ defaultMessage: 'Display Name'
+ },
+ addUsernamePlaceholder: {
+ id: 'user.settings.cmds.add_username.placeholder',
+ defaultMessage: 'Username'
+ },
+ addTriggerPlaceholder: {
+ id: 'user.settings.cmds.add_trigger.placeholder',
+ defaultMessage: 'Command trigger e.g. "hello" not including the slash'
+ },
+ addAutoCompleteDescPlaceholder: {
+ id: 'user.settings.cmds.auto_complete_desc.placeholder',
+ defaultMessage: 'A short description of what this commands does.'
+ },
+ addAutoCompleteHintPlaceholder: {
+ id: 'user.settings.cmds.auto_complete_hint.placeholder',
+ defaultMessage: '[zipcode]'
+ },
+ adUrlPlaceholder: {
+ id: 'user.settings.cmds.url.placeholder',
+ defaultMessage: 'Must start with http:// or https://'
+ }
+});
+
+export default class ManageCommandCmds extends React.Component {
+ constructor() {
+ super();
+
+ this.getCmds = this.getCmds.bind(this);
+ this.addNewCmd = this.addNewCmd.bind(this);
+ this.emptyCmd = this.emptyCmd.bind(this);
+ this.updateTrigger = this.updateTrigger.bind(this);
+ this.updateURL = this.updateURL.bind(this);
+ this.updateMethod = this.updateMethod.bind(this);
+ this.updateUsername = this.updateUsername.bind(this);
+ this.updateIconURL = this.updateIconURL.bind(this);
+ this.updateDisplayName = this.updateDisplayName.bind(this);
+ this.updateAutoComplete = this.updateAutoComplete.bind(this);
+ this.updateAutoCompleteDesc = this.updateAutoCompleteDesc.bind(this);
+ this.updateAutoCompleteHint = this.updateAutoCompleteHint.bind(this);
+
+ this.state = {cmds: [], cmd: this.emptyCmd(), getCmdsComplete: false};
+ }
+
+ static propTypes() {
+ return {
+ intl: intlShape.isRequired
+ };
+ }
+
+ emptyCmd() {
+ var cmd = {};
+ cmd.url = '';
+ cmd.trigger = '';
+ cmd.method = 'P';
+ cmd.username = '';
+ cmd.icon_url = '';
+ cmd.auto_complete = false;
+ cmd.auto_complete_desc = '';
+ cmd.auto_complete_hint = '';
+ cmd.display_name = '';
+ return cmd;
+ }
+
+ componentDidMount() {
+ this.getCmds();
+ }
+
+ addNewCmd(e) {
+ e.preventDefault();
+
+ if (this.state.cmd.trigger === '' || this.state.cmd.url === '') {
+ return;
+ }
+
+ var cmd = this.state.cmd;
+ if (cmd.trigger.length !== 0) {
+ cmd.trigger = cmd.trigger.trim();
+ }
+ cmd.url = cmd.url.trim();
+
+ Client.addCommand(
+ cmd,
+ (data) => {
+ let cmds = Object.assign([], this.state.cmds);
+ if (!cmds) {
+ cmds = [];
+ }
+ cmds.push(data);
+ this.setState({cmds, addError: null, cmd: this.emptyCmd()});
+ },
+ (err) => {
+ this.setState({addError: err.message});
+ }
+ );
+ }
+
+ removeCmd(id) {
+ const data = {};
+ data.id = id;
+
+ Client.deleteCommand(
+ data,
+ () => {
+ const cmds = this.state.cmds;
+ let index = -1;
+ for (let i = 0; i < cmds.length; i++) {
+ if (cmds[i].id === id) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index !== -1) {
+ cmds.splice(index, 1);
+ }
+
+ this.setState({cmds});
+ },
+ (err) => {
+ this.setState({editError: err.message});
+ }
+ );
+ }
+
+ regenToken(id) {
+ const regenData = {};
+ regenData.id = id;
+
+ Client.regenCommandToken(
+ regenData,
+ (data) => {
+ const cmds = Object.assign([], this.state.cmds);
+ for (let i = 0; i < cmds.length; i++) {
+ if (cmds[i].id === id) {
+ cmds[i] = data;
+ break;
+ }
+ }
+
+ this.setState({cmds, editError: null});
+ },
+ (err) => {
+ this.setState({editError: err.message});
+ }
+ );
+ }
+
+ getCmds() {
+ Client.listTeamCommands(
+ (data) => {
+ if (data) {
+ this.setState({cmds: data, getCmdsComplete: true, editError: null});
+ }
+ },
+ (err) => {
+ this.setState({editError: err.message});
+ }
+ );
+ }
+
+ updateTrigger(e) {
+ var cmd = this.state.cmd;
+ cmd.trigger = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateURL(e) {
+ var cmd = this.state.cmd;
+ cmd.url = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateMethod(e) {
+ var cmd = this.state.cmd;
+ cmd.method = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateUsername(e) {
+ var cmd = this.state.cmd;
+ cmd.username = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateIconURL(e) {
+ var cmd = this.state.cmd;
+ cmd.icon_url = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateDisplayName(e) {
+ var cmd = this.state.cmd;
+ cmd.display_name = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateAutoComplete(e) {
+ var cmd = this.state.cmd;
+ cmd.auto_complete = e.target.checked;
+ this.setState(cmd);
+ }
+
+ updateAutoCompleteDesc(e) {
+ var cmd = this.state.cmd;
+ cmd.auto_complete_desc = e.target.value;
+ this.setState(cmd);
+ }
+
+ updateAutoCompleteHint(e) {
+ var cmd = this.state.cmd;
+ cmd.auto_complete_hint = e.target.value;
+ this.setState(cmd);
+ }
+
+ render() {
+ let addError;
+ if (this.state.addError) {
+ addError = <label className='has-error'>{this.state.addError}</label>;
+ }
+
+ let editError;
+ if (this.state.editError) {
+ addError = <label className='has-error'>{this.state.editError}</label>;
+ }
+
+ const cmds = [];
+ this.state.cmds.forEach((cmd) => {
+ let triggerDiv;
+ if (cmd.trigger && cmd.trigger.length !== 0) {
+ triggerDiv = (
+ <div className='padding-top'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.trigger'
+ defaultMessage='Trigger: '
+ />
+ </strong>{cmd.trigger}
+ </div>
+ );
+ }
+
+ cmds.push(
+ <div
+ key={cmd.id}
+ className='webcmd__item'
+ >
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.display_name'
+ defaultMessage='Display Name: '
+ />
+ </strong><span className='word-break--all'>{cmd.display_name}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.username'
+ defaultMessage='Username: '
+ />
+ </strong><span className='word-break--all'>{cmd.username}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.icon_url'
+ defaultMessage='Icon URL: '
+ />
+ </strong><span className='word-break--all'>{cmd.icon_url}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete'
+ defaultMessage='Auto Complete: '
+ />
+ </strong><span className='word-break--all'>{cmd.auto_complete ? 'yes' : 'no'}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_desc'
+ defaultMessage='Auto Complete Description: '
+ />
+ </strong><span className='word-break--all'>{cmd.auto_complete_desc}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_hint'
+ defaultMessage='Auto Complete Hint: '
+ />
+ </strong><span className='word-break--all'>{cmd.auto_complete_hint}</span>
+ </div>
+ <div className='padding-top x2'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.request_type'
+ defaultMessage='Request Type: '
+ />
+ </strong>
+ <span className='word-break--all'>
+ {
+ cmd.method === 'P' ?
+ <FormattedMessage
+ id='user.settings.cmds.request_type_post'
+ defaultMessage='POST'
+ /> :
+ <FormattedMessage
+ id='user.settings.cmds.request_type_get'
+ defaultMessage='GET'
+ />
+ }
+ </span>
+ </div>
+ <div className='padding-top x2 webcmd__url'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.url'
+ defaultMessage='URL: '
+ />
+ </strong><span className='word-break--all'>{cmd.url}</span>
+ </div>
+ {triggerDiv}
+ <div className='padding-top'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.token'
+ defaultMessage='Token: '
+ />
+ </strong>{cmd.token}
+ </div>
+ <div className='padding-top'>
+ <a
+ className='text-danger'
+ href='#'
+ onClick={this.regenToken.bind(this, cmd.id)}
+ >
+ <FormattedMessage
+ id='user.settings.cmds.regen'
+ defaultMessage='Regen Token'
+ />
+ </a>
+ <a
+ className='webcmd__remove'
+ href='#'
+ onClick={this.removeCmd.bind(this, cmd.id)}
+ >
+ <span aria-hidden='true'>{'×'}</span>
+ </a>
+ </div>
+ <div className='padding-top x2 divider-light'></div>
+ </div>
+ );
+ });
+
+ let displayCmds;
+ if (!this.state.getCmdsComplete) {
+ displayCmds = <LoadingScreen/>;
+ } else if (cmds.length > 0) {
+ displayCmds = cmds;
+ } else {
+ displayCmds = (
+ <div className='padding-top x2'>
+ <FormattedMessage
+ id='user.settings.cmds.none'
+ defaultMessage='None'
+ />
+ </div>
+ );
+ }
+
+ const existingCmds = (
+ <div className='webcmds__container'>
+ <label className='control-label padding-top x2'>
+ <FormattedMessage
+ id='user.settings.cmds.existing'
+ defaultMessage='Existing commands'
+ />
+ </label>
+ <div className='padding-top divider-light'></div>
+ <div className='webcmds__list'>
+ {displayCmds}
+ </div>
+ </div>
+ );
+
+ const disableButton = this.state.cmd.trigger === '' || this.state.cmd.url === '';
+
+ return (
+ <div key='addCommandCmd'>
+ <FormattedHTMLMessage
+ id='user.settings.cmds.add_desc'
+ defaultMessage='Create commands to send message events to an external integration. Please see <a href="http://mattermost.org/commands">http://mattermost.org/commands</a> to learn more.'
+ />
+ <div><label className='control-label padding-top x2'>{'Add a new command'}</label></div>
+ <div className='padding-top divider-light'></div>
+ <div className='padding-top'>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.display_name'
+ defaultMessage='Display Name: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='displayName'
+ className='form-control'
+ value={this.state.cmd.display_name}
+ onChange={this.updateDisplayName}
+ placeholder={this.props.intl.formatMessage(holders.addDisplayNamePlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>{'Command display name.'}</div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.username'
+ defaultMessage='Username: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='username'
+ className='form-control'
+ value={this.state.cmd.username}
+ onChange={this.updateUsername}
+ placeholder={this.props.intl.formatMessage(holders.addUsernamePlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.username_desc'
+ defaultMessage='The username to use when overriding the post.'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.icon_url'
+ defaultMessage='Icon URL: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='iconURL'
+ className='form-control'
+ value={this.state.cmd.icon_url}
+ onChange={this.updateIconURL}
+ placeholder='https://www.example.com/myicon.png'
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.icon_url_desc'
+ defaultMessage='URL to an icon'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.trigger'
+ defaultMessage='Trigger: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='trigger'
+ className='form-control'
+ value={this.state.cmd.trigger}
+ onChange={this.updateTrigger}
+ placeholder={this.props.intl.formatMessage(holders.addTriggerPlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.trigger_desc'
+ defaultMessage='Word to trigger on'
+ />
+ {''}</div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete'
+ defaultMessage='Auto Complete: '
+ />
+ </label>
+ <div className='padding-top'>
+ <label>
+ <input
+ type='checkbox'
+ checked={this.state.cmd.auto_complete}
+ onChange={this.updateAutoComplete}
+ />
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_desc_desc'
+ defaultMessage='A short description of what this commands does'
+ />
+ </label>
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_help'
+ defaultMessage='Show this command in autocomplete list.'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_desc'
+ defaultMessage='Auto Complete Description: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='autoCompleteDesc'
+ className='form-control'
+ value={this.state.cmd.auto_complete_desc}
+ onChange={this.updateAutoCompleteDesc}
+ placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_desc_desc'
+ defaultMessage='A short description of what this commands does'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_hint'
+ defaultMessage='Auto Complete Hint: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='autoCompleteHint'
+ className='form-control'
+ value={this.state.cmd.auto_complete_hint}
+ onChange={this.updateAutoCompleteHint}
+ placeholder={this.props.intl.formatMessage(holders.addAutoCompleteHintPlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.auto_complete_hint_desc'
+ defaultMessage='List parameters to be passed to the command.'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.request_type'
+ defaultMessage='Request Type: '
+ />
+ </label>
+ <div className='padding-top'>
+ <select
+ ref='method'
+ className='form-control'
+ value={this.state.cmd.method}
+ onChange={this.updateMethod}
+ >
+ <option value='P'>
+ {this.props.intl.formatMessage(holders.requestTypePost)}
+ </option>
+ <option value='G'>
+ {this.props.intl.formatMessage(holders.requestTypeGet)}
+ </option>
+ </select>
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.request_type_desc'
+ defaultMessage='Command request type issued to the callback URL.'
+ />
+ </div>
+ </div>
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.url'
+ defaultMessage='URL: '
+ />
+ </label>
+ <div className='padding-top'>
+ <input
+ ref='URL'
+ className='form-control'
+ value={this.state.cmd.url}
+ rows={1}
+ onChange={this.updateURL}
+ placeholder={this.props.intl.formatMessage(holders.adUrlPlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.url_desc'
+ defaultMessage='URL that will receive the HTTP POST or GET event'
+ />
+ </div>
+ {addError}
+ </div>
+ <div className='padding-top padding-bottom'>
+ <a
+ className={'btn btn-sm btn-primary'}
+ href='#'
+ disabled={disableButton}
+ onClick={this.addNewCmd}
+ >
+ {'Add'}
+ </a>
+ </div>
+ </div>
+ {existingCmds}
+ {editError}
+ </div>
+ );
+ }
+}
+
+export default injectIntl(ManageCommandCmds);
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index 3b2a2065b..776bde442 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -297,7 +297,7 @@ class UserSettingsDisplay extends React.Component {
if (this.state.nameFormat === 'username') {
describe = formatMessage(holders.showUsername);
} else if (this.state.nameFormat === 'full_name') {
- describe = formatMessage(holders.showFullName);
+ describe = formatMessage(holders.showFullname);
} else {
describe = formatMessage(holders.showNickname);
}
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index abd04a301..1a9edab03 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -5,6 +5,7 @@ import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import ManageIncomingHooks from './manage_incoming_hooks.jsx';
import ManageOutgoingHooks from './manage_outgoing_hooks.jsx';
+import ManageCommandHooks from './manage_command_hooks.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -24,6 +25,14 @@ const holders = defineMessages({
outDesc: {
id: 'user.settings.integrations.outWebhooksDescription',
defaultMessage: 'Manage your outgoing webhooks'
+ },
+ cmdName: {
+ id: 'user.settings.integrations.commands',
+ defaultMessage: 'Commands'
+ },
+ cmdDesc: {
+ id: 'user.settings.integrations.commandsDescription',
+ defaultMessage: 'Manage your commands'
}
});
@@ -41,6 +50,7 @@ class UserSettingsIntegrationsTab extends React.Component {
render() {
let incomingHooksSection;
let outgoingHooksSection;
+ let commandHooksSection;
var inputs = [];
const {formatMessage} = this.props.intl;
@@ -106,6 +116,37 @@ class UserSettingsIntegrationsTab extends React.Component {
}
}
+ if (global.window.mm_config.EnableCommands === 'true') {
+ if (this.props.activeSection === 'command-hooks') {
+ inputs.push(
+ <ManageCommandHooks key='command-hook-ui' />
+ );
+
+ commandHooksSection = (
+ <SettingItemMax
+ title={formatMessage(holders.cmdName)}
+ width='medium'
+ inputs={inputs}
+ updateSection={(e) => {
+ this.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ commandHooksSection = (
+ <SettingItemMin
+ title={formatMessage(holders.cmdName)}
+ width='medium'
+ describe={formatMessage(holders.cmdDesc)}
+ updateSection={() => {
+ this.updateSection('command-hooks');
+ }}
+ />
+ );
+ }
+ }
+
return (
<div>
<div className='modal-header'>
@@ -144,6 +185,8 @@ class UserSettingsIntegrationsTab extends React.Component {
<div className='divider-light'/>
{outgoingHooksSection}
<div className='divider-dark'/>
+ {commandHooksSection}
+ <div className='divider-dark'/>
</div>
</div>
);