diff options
Diffstat (limited to 'web/react')
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'>×</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: /:['’]-?\(|:'\(/g, // :`( kissing_heart: /:-?\*/g, // :* - wink: /;-?\)/g, // ;) pensive: /:-?\//g, // :/ confounded: /:-?s/gi, // :s flushed: /:-?\|/g, // :| |