summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/service_settings.jsx70
-rw-r--r--web/react/components/admin_console/team_settings.jsx22
-rw-r--r--web/react/components/channel_loader.jsx9
-rw-r--r--web/react/components/channel_notifications.jsx16
-rw-r--r--web/react/components/file_attachment.jsx10
-rw-r--r--web/react/components/file_attachment_list.jsx16
-rw-r--r--web/react/components/post_body.jsx1
-rw-r--r--web/react/components/post_info.jsx2
-rw-r--r--web/react/components/rhs_comment.jsx6
-rw-r--r--web/react/components/rhs_root_post.jsx6
-rw-r--r--web/react/components/settings_sidebar.jsx3
-rw-r--r--web/react/components/team_feature_tab.jsx190
-rw-r--r--web/react/components/team_settings.jsx14
-rw-r--r--web/react/components/team_settings_modal.jsx13
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx65
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx6
-rw-r--r--web/react/components/view_image.jsx236
-rw-r--r--web/react/components/view_image_popover_bar.jsx66
-rw-r--r--web/react/utils/client.jsx17
-rw-r--r--web/react/utils/constants.jsx28
-rw-r--r--web/react/utils/emoticons.jsx7
22 files changed, 299 insertions, 506 deletions
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index 1bb1f053b..4b09fefc2 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -35,7 +35,7 @@ export default class ServiceSettings extends React.Component {
config.ServiceSettings.SegmentDeveloperKey = React.findDOMNode(this.refs.SegmentDeveloperKey).value.trim();
config.ServiceSettings.GoogleDeveloperKey = React.findDOMNode(this.refs.GoogleDeveloperKey).value.trim();
- config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
+ //config.ServiceSettings.EnableOAuthServiceProvider = React.findDOMNode(this.refs.EnableOAuthServiceProvider).checked;
config.ServiceSettings.EnableIncomingWebhooks = React.findDOMNode(this.refs.EnableIncomingWebhooks).checked;
config.ServiceSettings.EnableTesting = React.findDOMNode(this.refs.EnableTesting).checked;
@@ -173,42 +173,9 @@ export default class ServiceSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='EnableOAuthServiceProvider'
- >
- {'Enable OAuth Service Provider: '}
- </label>
- <div className='col-sm-8'>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableOAuthServiceProvider'
- value='true'
- ref='EnableOAuthServiceProvider'
- defaultChecked={this.props.config.ServiceSettings.EnableOAuthServiceProvider}
- onChange={this.handleChange}
- />
- {'true'}
- </label>
- <label className='radio-inline'>
- <input
- type='radio'
- name='EnableOAuthServiceProvider'
- value='false'
- defaultChecked={!this.props.config.ServiceSettings.EnableOAuthServiceProvider}
- onChange={this.handleChange}
- />
- {'false'}
- </label>
- <p className='help-text'>{'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
htmlFor='EnableIncomingWebhooks'
>
- {'EnableIncomingWebhooks: '}
+ {'Enable Incoming Webhooks: '}
</label>
<div className='col-sm-8'>
<label className='radio-inline'>
@@ -291,6 +258,39 @@ export default class ServiceSettings extends React.Component {
}
}
+// <div className='form-group'>
+// <label
+// className='control-label col-sm-4'
+// htmlFor='EnableOAuthServiceProvider'
+// >
+// {'Enable OAuth Service Provider: '}
+// </label>
+// <div className='col-sm-8'>
+// <label className='radio-inline'>
+// <input
+// type='radio'
+// name='EnableOAuthServiceProvider'
+// value='true'
+// ref='EnableOAuthServiceProvider'
+// defaultChecked={this.props.config.ServiceSettings.EnableOAuthServiceProvider}
+// onChange={this.handleChange}
+// />
+// {'true'}
+// </label>
+// <label className='radio-inline'>
+// <input
+// type='radio'
+// name='EnableOAuthServiceProvider'
+// value='false'
+// defaultChecked={!this.props.config.ServiceSettings.EnableOAuthServiceProvider}
+// onChange={this.handleChange}
+// />
+// {'false'}
+// </label>
+// <p className='help-text'>{'When enabled Mattermost will act as an Oauth2 Provider. Changing this will require a server restart before taking effect.'}</p>
+// </div>
+// </div>
+
ServiceSettings.propTypes = {
config: React.PropTypes.object
};
diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx
index fefc0e936..3e0890f98 100644
--- a/web/react/components/admin_console/team_settings.jsx
+++ b/web/react/components/admin_console/team_settings.jsx
@@ -28,7 +28,6 @@ export default class TeamSettings extends React.Component {
var config = this.props.config;
config.TeamSettings.SiteName = React.findDOMNode(this.refs.SiteName).value.trim();
- config.TeamSettings.DefaultThemeColor = React.findDOMNode(this.refs.DefaultThemeColor).value.trim();
config.TeamSettings.RestrictCreationToDomains = React.findDOMNode(this.refs.RestrictCreationToDomains).value.trim();
config.TeamSettings.EnableTeamCreation = React.findDOMNode(this.refs.EnableTeamCreation).checked;
config.TeamSettings.EnableUserCreation = React.findDOMNode(this.refs.EnableUserCreation).checked;
@@ -125,27 +124,6 @@ export default class TeamSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='DefaultThemeColor'
- >
- {'Default Theme Color:'}
- </label>
- <div className='col-sm-8'>
- <input
- type='text'
- className='form-control'
- id='DefaultThemeColor'
- ref='DefaultThemeColor'
- placeholder='Ex "#2389D7"'
- defaultValue={this.props.config.TeamSettings.DefaultThemeColor}
- onChange={this.handleChange}
- />
- <p className='help-text'>{'Default theme color for team sites.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
htmlFor='EnableTeamCreation'
>
{'Enable Team Creation: '}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 962ba26ee..39c86405c 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -109,6 +109,13 @@ export default class ChannelLoader extends React.Component {
$('.modal-body').css('overflow-y', 'auto');
$('.modal-body').css('max-height', $(window).height() * 0.7);
});
+
+ /* Prevent backspace from navigating back a page */
+ $(window).on('keydown.preventBackspace', (e) => {
+ if (e.which === 8 && !$(e.target).is('input, textarea')) {
+ e.preventDefault();
+ }
+ });
}
componentWillUnmount() {
clearInterval(this.intervalId);
@@ -123,6 +130,8 @@ export default class ChannelLoader extends React.Component {
$('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
$('.modal').off('show.bs.modal');
+
+ $(window).off('keydown.preventBackspace');
}
onSocketChange(msg) {
if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 83067240d..9eda68b38 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -163,10 +163,22 @@ export default class ChannelNotifications extends React.Component {
}.bind(this);
let curChannel = ChannelStore.get(this.state.channelId);
- let extraInfo = (<span>These settings will override the global notification settings</span>);
+ let extraInfo = (
+ <span>
+ These settings will override the global notification settings.
+ <br/>
+ Desktop notifications are available on Firefox, Safari, and Chrome.
+ </span>
+ );
if (curChannel && curChannel.display_name) {
- extraInfo = (<span>These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel</span>);
+ extraInfo = (
+ <span>
+ These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel.
+ <br/>
+ Desktop notifications are available on Firefox, Safari, and Chrome.
+ </span>
+ );
}
return (
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index c9aa06a97..888f24aa5 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -143,10 +143,7 @@ export default class FileAttachment extends React.Component {
>
<a className='post-image__thumbnail'
href='#'
- onClick={this.props.handleImageClick}
- data-img-id={this.props.index}
- data-toggle='modal'
- data-target={'#' + this.props.modalId}
+ onClick={() => this.props.handleImageClick(this.props.index)}
>
{thumbnail}
</a>
@@ -187,9 +184,6 @@ FileAttachment.propTypes = {
// the index of this attachment preview in the parent FileAttachmentList
index: React.PropTypes.number.isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
-
- // handler for when the thumbnail is clicked
+ // handler for when the thumbnail is clicked passed the index above
handleImageClick: React.PropTypes.func
};
diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx
index abe72089a..212d4a958 100644
--- a/web/react/components/file_attachment_list.jsx
+++ b/web/react/components/file_attachment_list.jsx
@@ -11,23 +11,21 @@ export default class FileAttachmentList extends React.Component {
this.handleImageClick = this.handleImageClick.bind(this);
- this.state = {startImgId: 0};
+ this.state = {showPreviewModal: false, startImgId: 0};
}
- handleImageClick(e) {
- this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)});
+ handleImageClick(indexClicked) {
+ this.setState({showPreviewModal: true, startImgId: indexClicked});
}
render() {
var filenames = this.props.filenames;
- var modalId = this.props.modalId;
var postFiles = [];
for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
postFiles.push(
<FileAttachment
- key={i}
+ key={'file_attachment_' + i}
filename={filenames[i]}
index={i}
- modalId={modalId}
handleImageClick={this.handleImageClick}
/>
);
@@ -39,9 +37,10 @@ export default class FileAttachmentList extends React.Component {
{postFiles}
</div>
<ViewImageModal
+ show={this.state.showPreviewModal}
+ onModalDismissed={() => this.setState({showPreviewModal: false})}
channelId={this.props.channelId}
userId={this.props.userId}
- modalId={modalId}
startId={this.state.startImgId}
filenames={filenames}
/>
@@ -55,9 +54,6 @@ FileAttachmentList.propTypes = {
// a list of file pathes displayed by this
filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
-
// the channel that this is part of
channelId: React.PropTypes.string,
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index dbbcdc409..6e98e4aba 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -141,7 +141,6 @@ export default class PostBody extends React.Component {
fileAttachmentHolder = (
<FileAttachmentList
filenames={filenames}
- modalId={`view_image_modal_${post.id}`}
channelId={post.channel_id}
userId={post.user_id}
/>
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index d2a0a4035..c38edf6a2 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -153,7 +153,7 @@ export default class PostInfo extends React.Component {
<li className='post-header-col'>
<time
className='post-profile-time'
- title={new Date(post.create_at).toString()}
+ title={`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}
>
{utils.displayDateTime(post.create_at)}
</time>
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 4d1892a69..5b4694eb1 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -163,7 +163,6 @@ export default class RhsComment extends React.Component {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
- modalId={'rhs_comment_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id}
/>
@@ -186,10 +185,7 @@ export default class RhsComment extends React.Component {
<strong><UserProfile userId={post.user_id} /></strong>
</li>
<li className='post-header-col'>
- <time
- className='post-profile-time'
- title={new Date(post.create_at).toString()}
- >
+ <time className='post-profile-time'>
{Utils.displayCommentDateTime(post.create_at)}
</time>
</li>
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index e661bdce1..13ab0c982 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -111,7 +111,6 @@ export default class RhsRootPost extends React.Component {
fileAttachment = (
<FileAttachmentList
filenames={post.filenames}
- modalId={'rhs_view_image_modal_' + post.id}
channelId={post.channel_id}
userId={post.user_id}
/>
@@ -133,10 +132,7 @@ export default class RhsRootPost extends React.Component {
<ul className='post-header'>
<li className='post-header-col'><strong><UserProfile userId={post.user_id} /></strong></li>
<li className='post-header-col'>
- <time
- className='post-profile-time'
- title={new Date(post.create_at).toString()}
- >
+ <time className='post-profile-time'>
{utils.displayCommentDateTime(post.create_at)}
</time>
</li>
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index e5cbd6e92..4c4675788 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -7,7 +7,8 @@ export default class SettingsSidebar extends React.Component {
this.handleClick = this.handleClick.bind(this);
}
- handleClick(tab) {
+ handleClick(tab, e) {
+ e.preventDefault();
this.props.updateTab(tab.name);
$('.settings-modal').addClass('display--content');
}
diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx
deleted file mode 100644
index 3251746b8..000000000
--- a/web/react/components/team_feature_tab.jsx
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-var SettingItemMin = require('./setting_item_min.jsx');
-var SettingItemMax = require('./setting_item_max.jsx');
-
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-
-export default class FeatureTab extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitValetFeature = this.submitValetFeature.bind(this);
- this.handleValetRadio = this.handleValetRadio.bind(this);
- this.onUpdateSection = this.onUpdateSection.bind(this);
- this.setupInitialState = this.setupInitialState.bind(this);
-
- this.state = this.setupInitialState();
- }
- componentWillReceiveProps(newProps) {
- var team = newProps.team;
-
- var allowValet = 'false';
- if (team && team.allow_valet) {
- allowValet = 'true';
- }
-
- this.setState({allowValet: allowValet});
- }
- submitValetFeature() {
- var data = {};
- data.allow_valet = this.state.allowValet;
-
- Client.updateValetFeature(data,
- function success() {
- this.props.updateSection('');
- AsyncClient.getMyTeam();
- }.bind(this),
- function fail(err) {
- var state = this.setupInitialState();
- state.serverError = err;
- this.setState(state);
- }.bind(this)
- );
- }
- handleValetRadio(val) {
- this.setState({allowValet: val});
- React.findDOMNode(this.refs.wrapper).focus();
- }
- onUpdateSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'valet') {
- this.props.updateSection('');
- } else {
- this.props.updateSection('valet');
- }
- }
- setupInitialState() {
- var allowValet;
- var team = this.props.team;
-
- if (team && team.allow_valet) {
- allowValet = 'true';
- } else {
- allowValet = 'false';
- }
-
- return {allowValet: allowValet};
- }
- render() {
- var clientError = null;
- var serverError = null;
- if (this.state.clientError) {
- clientError = this.state.clientError;
- }
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
-
- var valetSection;
-
- if (this.props.activeSection === 'valet') {
- var valetActive = [false, false];
- if (this.state.allowValet === 'false') {
- valetActive[1] = true;
- } else {
- valetActive[0] = true;
- }
-
- let inputs = [];
-
- inputs.push(
- <div key='teamValetSetting'>
- <div className='radio'>
- <label>
- <input
- type='radio'
- checked={valetActive[0]}
- onChange={this.handleValetRadio.bind(this, 'true')}
- >
- On
- </input>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- type='radio'
- checked={valetActive[1]}
- onChange={this.handleValetRadio.bind(this, 'false')}
- >
- Off
- </input>
- </label>
- <br/>
- </div>
- <div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
- </div>
- );
-
- valetSection = (
- <SettingItemMax
- title='Valet (Preview - EXPERTS ONLY)'
- inputs={inputs}
- submit={this.submitValetFeature}
- server_error={serverError}
- client_error={clientError}
- updateSection={this.onUpdateSection}
- />
- );
- } else {
- var describe = '';
- if (this.state.allowValet === 'false') {
- describe = 'Off';
- } else {
- describe = 'On';
- }
-
- valetSection = (
- <SettingItemMin
- title='Valet (Preview - EXPERTS ONLY)'
- describe={describe}
- updateSection={this.onUpdateSection}
- />
- );
- }
-
- return (
- <div>
- <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'
- ref='title'
- >
- <i className='modal-back'></i>Advanced Features
- </h4>
- </div>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <h3 className='tab-header'>Advanced Features</h3>
- <div className='divider-dark first'/>
- {valetSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-}
-
-FeatureTab.defaultProps = {
- team: {},
- activeSection: ''
-};
-FeatureTab.propTypes = {
- updateSection: React.PropTypes.func.isRequired,
- team: React.PropTypes.object.isRequired,
- activeSection: React.PropTypes.string.isRequired
-};
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 396521af9..e91aa20bc 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -4,7 +4,6 @@
var TeamStore = require('../stores/team_store.jsx');
var ImportTab = require('./team_import_tab.jsx');
var ExportTab = require('./team_export_tab.jsx');
-var FeatureTab = require('./team_feature_tab.jsx');
var GeneralTab = require('./team_general_tab.jsx');
var Utils = require('../utils/utils.jsx');
@@ -25,7 +24,7 @@ export default class TeamSettings extends React.Component {
onChange() {
var team = TeamStore.getCurrent();
if (!Utils.areStatesEqual(this.state.team, team)) {
- this.setState({team: team});
+ this.setState({team});
}
}
render() {
@@ -43,17 +42,6 @@ export default class TeamSettings extends React.Component {
</div>
);
break;
- case 'feature':
- result = (
- <div>
- <FeatureTab
- team={this.state.team}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- />
- </div>
- );
- break;
case 'import':
result = (
<div>
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 0513c811f..a96aadccf 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -20,8 +20,8 @@ export default class TeamSettingsModal extends React.Component {
$('body').on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function handleCloseClick() {
- setTimeout(function removeContent() {
+ $('body').on('click', '.modal-header .close', () => {
+ setTimeout(() => {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
@@ -33,11 +33,12 @@ export default class TeamSettingsModal extends React.Component {
this.setState({activeSection: section});
}
render() {
- let tabs = [];
+ const tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
- tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'});
- tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'});
+
+ // To enable export uncomment this line
+ //tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'});
return (
<div
@@ -63,7 +64,7 @@ export default class TeamSettingsModal extends React.Component {
className='modal-title'
ref='title'
>
- Team Settings
+ {'Team Settings'}
</h4>
</div>
<div className='modal-body'>
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index df089a403..12c041c7f 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -107,23 +107,23 @@ export default class ManageIncomingHooks extends React.Component {
this.state.hooks.forEach((hook) => {
const c = ChannelStore.get(hook.channel_id);
hooks.push(
- <div>
- <div className='divider-light'></div>
- <span>
- <strong>{'URL: '}</strong>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}
- </span>
- <br/>
- <span>
+ <div className='font--small'>
+ <div className='padding-top x2 divider-light'></div>
+ <div className='padding-top x2'>
+ <strong>{'URL: '}</strong><span className='word-break--all'>{Utils.getWindowLocationOrigin() + '/hooks/' + hook.id}</span>
+ </div>
+ <div className='padding-top'>
<strong>{'Channel: '}</strong>{c.name}
- </span>
- <br/>
- <a
- className={'btn btn-sm btn-primary'}
- href='#'
- onClick={this.removeHook.bind(this, hook.id)}
- >
- {'Remove'}
- </a>
+ </div>
+ <div className='padding-top'>
+ <a
+ className={'text-danger'}
+ href='#'
+ onClick={this.removeHook.bind(this, hook.id)}
+ >
+ {'Remove'}
+ </a>
+ </div>
</div>
);
});
@@ -134,41 +134,38 @@ export default class ManageIncomingHooks extends React.Component {
} else if (hooks.length > 0) {
displayHooks = hooks;
} else {
- displayHooks = <label>{'None'}</label>;
+ displayHooks = <label>{' None'}</label>;
}
const existingHooks = (
- <div>
- <label className='control-label'>{'Existing incoming webhooks'}</label>
- <br/>
+ <div className='padding-top x2'>
+ <label className='control-label padding-top x2'>{'Existing incoming webhooks'}</label>
{displayHooks}
</div>
);
return (
- <div
- key='addIncomingHook'
- className='form-group'
- >
+ <div key='addIncomingHook'>
<label className='control-label'>{'Add a new incoming webhook'}</label>
- <br/>
- <div>
+ <div className='padding-top'>
<select
ref='channelName'
+ className='form-control'
value={this.state.channelId}
onChange={this.updateChannelId}
>
{options}
</select>
- <br/>
{serverError}
- <a
- className={'btn btn-sm btn-primary' + disableButton}
- href='#'
- onClick={this.addNewHook}
- >
- {'Add'}
- </a>
+ <div className='padding-top'>
+ <a
+ className={'btn btn-sm btn-primary' + disableButton}
+ href='#'
+ onClick={this.addNewHook}
+ >
+ {'Add'}
+ </a>
+ </div>
</div>
{existingHooks}
</div>
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index 7617f04d1..4372069e7 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -81,6 +81,8 @@ export default class UserSettingsAppearance extends React.Component {
$('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateTab('general');
+ $('.ps-container.modal-body').scrollTop(0);
+ $('.ps-container.modal-body').perfectScrollbar('update');
$('#user_settings').modal('hide');
},
(err) => {
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index 8d364cde7..ba14f019f 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -265,9 +265,12 @@ export default class NotificationsTab extends React.Component {
e.preventDefault();
}.bind(this);
+ const extraInfo = <span>{'Desktop notifications are available on Firefox, Safari, and Chrome.'}</span>;
+
desktopSection = (
<SettingItemMax
title='Send desktop notifications'
+ extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -343,9 +346,12 @@ export default class NotificationsTab extends React.Component {
e.preventDefault();
}.bind(this);
+ const extraInfo = <span>{'Desktop notification sounds are available on Firefox, Safari, Chrome, Internet Explorer, and Edge.'}</span>;
+
soundSection = (
<SettingItemMax
title='Desktop notification sounds'
+ extraInfo={extraInfo}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index dafcdd9f9..e645878c1 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -3,6 +3,8 @@
var Client = require('../utils/client.jsx');
var Utils = require('../utils/utils.jsx');
+var ViewImagePopoverBar = require('./view_image_popover_bar.jsx');
+var Modal = ReactBootstrap.Modal;
export default class ViewImageModal extends React.Component {
constructor(props) {
@@ -16,6 +18,10 @@ export default class ViewImageModal extends React.Component {
this.handleKeyPress = this.handleKeyPress.bind(this);
this.getPublicLink = this.getPublicLink.bind(this);
this.getPreviewImagePath = this.getPreviewImagePath.bind(this);
+ this.onModalShown = this.onModalShown.bind(this);
+ this.onModalHidden = this.onModalHidden.bind(this);
+ this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
+ this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
var loaded = [];
var progress = [];
@@ -23,9 +29,20 @@ export default class ViewImageModal extends React.Component {
loaded.push(false);
progress.push(0);
}
- this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}};
+ this.state = {
+ imgId: this.props.startId,
+ imgHeight: '100%',
+ loaded: loaded,
+ progress: progress,
+ images: {},
+ fileSizes: {},
+ showFooter: false
+ };
}
- handleNext() {
+ handleNext(e) {
+ if (e) {
+ e.stopPropagation();
+ }
var id = this.state.imgId + 1;
if (id > this.props.filenames.length - 1) {
id = 0;
@@ -33,7 +50,10 @@ export default class ViewImageModal extends React.Component {
this.setState({imgId: id});
this.loadImage(id);
}
- handlePrev() {
+ handlePrev(e) {
+ if (e) {
+ e.stopPropagation();
+ }
var id = this.state.imgId - 1;
if (id < 0) {
id = this.props.filenames.length - 1;
@@ -50,15 +70,27 @@ export default class ViewImageModal extends React.Component {
this.handlePrev();
}
}
- componentWillReceiveProps(nextProps) {
+ onModalShown(nextProps) {
this.setState({imgId: nextProps.startId});
+ this.loadImage(nextProps.startId);
+ }
+ onModalHidden() {
+ if (this.refs.video) {
+ var video = React.findDOMNode(this.refs.video);
+ video.pause();
+ video.currentTime = 0;
+ }
+ }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.show === true && this.props.show === false) {
+ this.onModalShown(nextProps);
+ } else if (nextProps.show === false && this.props.show === true) {
+ this.onModalHidden();
+ }
}
loadImage(id) {
var imgHeight = $(window).height() - 100;
- if (this.state.loaded[id] || this.state.images[id]) {
- $('.modal .modal-image .image-wrapper img').css('max-height', imgHeight);
- return;
- }
+ this.setState({imgHeight});
var filename = this.props.filenames[id];
@@ -68,84 +100,27 @@ export default class ViewImageModal extends React.Component {
if (fileType === 'image') {
var img = new Image();
img.load(this.getPreviewImagePath(filename),
- function load() {
- var progress = this.state.progress;
- progress[id] = img.completedPercentage;
- this.setState({progress: progress});
- }.bind(this));
- img.onload = (function onload(imgid) {
- return function onloadReturn() {
- var loaded = this.state.loaded;
- loaded[imgid] = true;
- this.setState({loaded: loaded});
- $(React.findDOMNode(this.refs.image)).css('max-height', imgHeight);
- }.bind(this);
- }.bind(this)(id));
+ () => {
+ const progress = this.state.progress;
+ progress[id] = img.completedPercentage;
+ this.setState({progress});
+ });
+ img.onload = () => {
+ const loaded = this.state.loaded;
+ loaded[id] = true;
+ this.setState({loaded});
+ };
var images = this.state.images;
images[id] = img;
- this.setState({images: images});
+ this.setState({images});
} else {
// there's nothing to load for non-image files
var loaded = this.state.loaded;
loaded[id] = true;
- this.setState({loaded: loaded});
- }
- }
- componentDidUpdate() {
- if (this.state.loaded[this.state.imgId]) {
- if (this.refs.imageWrap) {
- $(React.findDOMNode(this.refs.imageWrap)).removeClass('default');
- }
+ this.setState({loaded});
}
}
componentDidMount() {
- $('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
- this.setState({viewed: true});
- this.loadImage(this.state.imgId);
- }.bind(this));
-
- $('#' + this.props.modalId).on('hidden.bs.modal', function onModalHide() {
- if (this.refs.video) {
- var video = React.findDOMNode(this.refs.video);
- video.pause();
- video.currentTime = 0;
- }
- }.bind(this));
-
- $(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) {
- if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) {
- $('.image_modal').modal('hide');
- }
- }.bind(this));
-
- $(React.findDOMNode(this.refs.imageWrap)).hover(
- function onModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
- }.bind(this), function offModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
- }.bind(this)
- );
-
- if (this.refs.previewArrowLeft) {
- $(React.findDOMNode(this.refs.previewArrowLeft)).hover(
- function onModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
- }.bind(this), function offModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
- }.bind(this)
- );
- }
-
- if (this.refs.previewArrowRight) {
- $(React.findDOMNode(this.refs.previewArrowRight)).hover(
- function onModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show');
- }.bind(this), function offModalHover() {
- $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show');
- }.bind(this)
- );
- }
-
$(window).on('keyup', this.handleKeyPress);
// keep track of whether or not this component is mounted so we can safely set the state asynchronously
@@ -189,6 +164,12 @@ export default class ViewImageModal extends React.Component {
// only images have proper previews, so just use a placeholder icon for non-images
return Utils.getPreviewImagePathForFileType(fileType);
}
+ onMouseEnterImage() {
+ this.setState({showFooter: true});
+ }
+ onMouseLeaveImage() {
+ this.setState({showFooter: false});
+ }
render() {
if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) {
return <div/>;
@@ -299,23 +280,6 @@ export default class ViewImageModal extends React.Component {
bgClass = 'black-bg';
}
- var publicLink = '';
- if (global.window.config.EnablePublicLink === 'true') {
- publicLink = (
- <div>
- <a
- href='#'
- className='public-link text'
- data-title='Public Image'
- onClick={this.getPublicLink}
- >
- Get Public Link
- </a>
- <span className='text'> | </span>
- </div>
- );
- }
-
var leftArrow = '';
var rightArrow = '';
if (this.props.filenames.length > 1) {
@@ -342,65 +306,61 @@ export default class ViewImageModal extends React.Component {
);
}
+ let closeButtonClass = 'modal-close';
+ if (this.state.showFooter) {
+ closeButtonClass += ' modal-close--show';
+ }
+
return (
- <div
- className='modal fade image_modal'
- ref='modal'
- id={this.props.modalId}
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onModalDismissed}
+ className='image_modal'
+ dialogClassName='modal-image'
>
- <div className='modal-dialog modal-image'>
- <div className='modal-content image-content'>
+ <Modal.Body
+ modalClassName='image-body'
+ onClick={this.props.onModalDismissed}
+ >
+ <div
+ className={'image-wrapper ' + bgClass}
+ style={{maxHeight: this.state.imgHeight}}
+ onMouseEnter={this.onMouseEnterImage}
+ onMouseLeave={this.onMouseLeaveImage}
+ onClick={(e) => e.stopPropagation()}
+ >
<div
- ref='imageBody'
- className='modal-body image-body'
- >
- <div
- ref='imageWrap'
- className={'image-wrapper default ' + bgClass}
- >
- <div
- className='modal-close'
- data-dismiss='modal'
- />
- {content}
- <div
- ref='imageFooter'
- className='modal-button-bar'
- >
- <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
- <div className='image-links'>
- {publicLink}
- <a
- href={fileUrl}
- download={name}
- className='text'
- >
- Download
- </a>
- </div>
- </div>
- </div>
- {leftArrow}
- {rightArrow}
- </div>
+ className={closeButtonClass}
+ onClick={this.props.onModalDismissed}
+ />
+ {content}
+ <ViewImagePopoverBar
+ show={this.state.showFooter}
+ fileId={this.state.imgId}
+ totalFiles={this.props.filenames.length}
+ filename={name}
+ fileURL={fileUrl}
+ onGetPublicLinkPressed={this.getPublicLink}
+ />
</div>
- </div>
- </div>
+ {leftArrow}
+ {rightArrow}
+ </Modal.Body>
+ </Modal>
);
}
}
ViewImageModal.defaultProps = {
+ show: false,
filenames: [],
- modalId: '',
channelId: '',
userId: '',
startId: 0
};
ViewImageModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onModalDismissed: React.PropTypes.func.isRequired,
filenames: React.PropTypes.array,
modalId: React.PropTypes.string,
channelId: React.PropTypes.string,
diff --git a/web/react/components/view_image_popover_bar.jsx b/web/react/components/view_image_popover_bar.jsx
new file mode 100644
index 000000000..68817d751
--- /dev/null
+++ b/web/react/components/view_image_popover_bar.jsx
@@ -0,0 +1,66 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class ViewImagePopoverBar extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ var publicLink = '';
+ if (global.window.config.EnablePublicLink === 'true') {
+ publicLink = (
+ <div>
+ <a
+ href='#'
+ className='public-link text'
+ data-title='Public Image'
+ onClick={this.getPublicLink}
+ >
+ {'Get Public Link'}
+ </a>
+ <span className='text'>{' | '}</span>
+ </div>
+ );
+ }
+
+ var footerClass = 'modal-button-bar';
+ if (this.props.show) {
+ footerClass += ' footer--show';
+ }
+
+ return (
+ <div
+ ref='imageFooter'
+ className={footerClass}
+ >
+ <span className='pull-left text'>{'File ' + (this.props.fileId + 1) + ' of ' + this.props.totalFiles}</span>
+ <div className='image-links'>
+ {publicLink}
+ <a
+ href={this.props.fileURL}
+ download={this.props.filename}
+ className='text'
+ >
+ {'Download'}
+ </a>
+ </div>
+ </div>
+ );
+ }
+}
+ViewImagePopoverBar.defaultProps = {
+ show: false,
+ imgId: 0,
+ totalFiles: 0,
+ filename: '',
+ fileURL: ''
+};
+
+ViewImagePopoverBar.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ fileId: React.PropTypes.number.isRequired,
+ totalFiles: React.PropTypes.number.isRequired,
+ filename: React.PropTypes.string.isRequired,
+ fileURL: React.PropTypes.string.isRequired,
+ onGetPublicLinkPressed: React.PropTypes.func.isRequired
+};
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 63924bff2..4effa7307 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1042,23 +1042,6 @@ export function getMyTeam(success, error) {
});
}
-export function updateValetFeature(data, success, error) {
- $.ajax({
- url: '/api/v1/teams/update_valet_feature',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify(data),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('updateValetFeature', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_update_valet_feature');
-}
-
export function registerOAuthApp(app, success, error) {
$.ajax({
url: '/api/v1/oauth/register',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 90af9beda..8c9e1ee85 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -182,7 +182,7 @@ module.exports = {
},
{
id: 'sidebarText',
- uiName: 'Sidebar text color'
+ uiName: 'Sidebar Text'
},
{
id: 'sidebarHeaderBg',
@@ -190,51 +190,51 @@ module.exports = {
},
{
id: 'sidebarHeaderTextColor',
- uiName: 'Sidebar Header text color'
+ uiName: 'Sidebar Header Text'
},
{
id: 'sidebarUnreadText',
- uiName: 'Sidebar unread text color'
+ uiName: 'Sidebar Unread Text'
},
{
id: 'sidebarTextHoverBg',
- uiName: 'Sidebar text hover BG'
+ uiName: 'Sidebar Text Hover BG'
},
{
id: 'sidebarTextHoverColor',
- uiName: 'Sidebar text hover color'
+ uiName: 'Sidebar Text Hover Color'
},
{
id: 'sidebarTextActiveBg',
- uiName: 'Sidebar text active BG'
+ uiName: 'Sidebar Text Active BG'
},
{
id: 'sidebarTextActiveColor',
- uiName: 'Sidebar text active color'
+ uiName: 'Sidebar Text Active Color'
},
{
id: 'onlineIndicator',
- uiName: 'Online indicator'
+ uiName: 'Online Indicator'
},
{
id: 'mentionBj',
- uiName: 'Mention jewel BG'
+ uiName: 'Mention Jewel BG'
},
{
id: 'mentionColor',
- uiName: 'Mention jewel text color'
+ uiName: 'Mention Jewel Text'
},
{
id: 'centerChannelBg',
- uiName: 'Center channel BG'
+ uiName: 'Center Channel BG'
},
{
id: 'centerChannelColor',
- uiName: 'Center channel text color'
+ uiName: 'Center Channel Text'
},
{
id: 'linkColor',
- uiName: 'Link color'
+ uiName: 'Link Color'
},
{
id: 'buttonBg',
@@ -243,7 +243,7 @@ module.exports = {
{
id: 'buttonColor',
- uiName: 'Button Color'
+ uiName: 'Button Text'
}
]
};
diff --git a/web/react/utils/emoticons.jsx b/web/react/utils/emoticons.jsx
index 7210201ff..a7c837199 100644
--- a/web/react/utils/emoticons.jsx
+++ b/web/react/utils/emoticons.jsx
@@ -5,15 +5,14 @@ const emoticonPatterns = {
smile: /:-?\)/g, // :)
open_mouth: /:o/gi, // :o
scream: /:-o/gi, // :-o
- smirk: /[:;]-?]/g, // :]
- grinning: /[:;]-?d/gi, // :D
+ smirk: /:-?]/g, // :]
+ grinning: /:-?d/gi, // :D
stuck_out_tongue_closed_eyes: /x-d/gi, // x-d
- stuck_out_tongue_winking_eye: /[:;]-?p/gi, // ;p
+ stuck_out_tongue_winking_eye: /:-?p/gi, // :p
rage: /:-?[\[@]/g, // :@
frowning: /:-?\(/g, // :(
sob: /:['’]-?\(|:&#x27;\(/g, // :`(
kissing_heart: /:-?\*/g, // :*
- wink: /;-?\)/g, // ;)
pensive: /:-?\//g, // :/
confounded: /:-?s/gi, // :s
flushed: /:-?\|/g, // :|