diff options
Diffstat (limited to 'web/react/components')
21 files changed, 278 insertions, 98 deletions
diff --git a/web/react/components/admin_console/reset_password_modal.jsx b/web/react/components/admin_console/reset_password_modal.jsx index 5ff7c3413..bf7d5f7e5 100644 --- a/web/react/components/admin_console/reset_password_modal.jsx +++ b/web/react/components/admin_console/reset_password_modal.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import * as Client from '../../utils/client.jsx'; +import Constants from '../../utils/constants.jsx'; var Modal = ReactBootstrap.Modal; export default class ResetPasswordModal extends React.Component { @@ -20,8 +21,8 @@ export default class ResetPasswordModal extends React.Component { e.preventDefault(); var password = ReactDOM.findDOMNode(this.refs.password).value; - if (!password || password.length < 5) { - this.setState({serverError: 'Please enter at least 5 characters.'}); + if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { + this.setState({serverError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.'}); return; } diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx index 18e125de3..72c7c3daa 100644 --- a/web/react/components/channel_info_modal.jsx +++ b/web/react/components/channel_info_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import * as Utils from '../utils/utils.jsx'; const Modal = ReactBootstrap.Modal; export default class ChannelInfoModal extends React.Component { @@ -10,10 +11,13 @@ export default class ChannelInfoModal extends React.Component { channel = { display_name: 'No Channel Found', name: 'No Channel Found', + purpose: 'No Channel Found', id: 'No Channel Found' }; } + const channelURL = Utils.getShortenedTeamURL() + channel.name; + return ( <Modal show={this.props.show} @@ -28,13 +32,17 @@ export default class ChannelInfoModal extends React.Component { <div className='col-sm-9'>{channel.display_name}</div> </div> <div className='row form-group'> - <div className='col-sm-3 info__label'>{'Channel Handle:'}</div> - <div className='col-sm-9'>{channel.name}</div> + <div className='col-sm-3 info__label'>{'Channel URL:'}</div> + <div className='col-sm-9'>{channelURL}</div> </div> <div className='row'> <div className='col-sm-3 info__label'>{'Channel ID:'}</div> <div className='col-sm-9'>{channel.id}</div> </div> + <div className='row'> + <div className='col-sm-3 info__label'>{'Channel Purpose:'}</div> + <div className='col-sm-9'>{channel.purpose}</div> + </div> </Modal.Body> <Modal.Footer> <button diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index cae94429c..aa7ab6a7b 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -129,7 +129,7 @@ export default class CreateComment extends React.Component { function handlePostError(err) { let state = {}; - if (err.message === 'Invalid RootId parameter') { + if (err.id === 'api.post.create_post.root_id.app_error') { PostStore.removePendingPost(post.channel_id, post.pending_post_id); if ($('#post_deleted').length > 0) { diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index d2f62334e..76f63d4b3 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -186,7 +186,7 @@ export default class CreatePost extends React.Component { (err) => { const state = {}; - if (err.message === 'Invalid RootId parameter') { + if (err.id === 'api.post.create_post.root_id.app_error') { if ($('#post_deleted').length > 0) { $('#post_deleted').modal('show'); } diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index fef253c52..7e6cc2942 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -151,7 +151,11 @@ export default class FileUpload extends React.Component { }); } - document.addEventListener('paste', function handlePaste(e) { + document.addEventListener('paste', (e) => { + if (!e.clipboardData) { + return; + } + var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0]; if (textarea !== e.target && !$.contains(textarea, e.target)) { diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 1d9b3e906..6887489a7 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -7,6 +7,8 @@ import LoginLdap from './login_ldap.jsx'; import * as Utils from '../utils/utils.jsx'; import Constants from '../utils/constants.jsx'; +var FormattedMessage = ReactIntl.FormattedMessage; + export default class Login extends React.Component { constructor(props) { super(props); @@ -86,7 +88,12 @@ export default class Login extends React.Component { if (emailSignup) { forgotPassword = ( <div className='form-group'> - <a href={'/' + teamName + '/reset_password'}>{'I forgot my password'}</a> + <a href={'/' + teamName + '/reset_password'}> + <FormattedMessage + id='login.forgot_password' + defaultMessage='I forgot my password' + /> + </a> </div> ); } @@ -141,7 +148,13 @@ export default class Login extends React.Component { {ldapLogin} {userSignUp} <div className='form-group margin--extra form-group--small'> - <span><a href='/find_team'>{'Find your other teams'}</a></span> + <span> + <a href='/find_team'> + <FormattedMessage + id='login.find_teams' + defaultMessage='Find your other teams' + /> + </a></span> </div> {forgotPassword} {teamSignUp} diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx index 812911569..8063db05a 100644 --- a/web/react/components/password_reset_form.jsx +++ b/web/react/components/password_reset_form.jsx @@ -1,7 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as client from '../utils/client.jsx'; +import * as Client from '../utils/client.jsx'; +import Constants from '../utils/constants.jsx'; export default class PasswordResetForm extends React.Component { constructor(props) { @@ -16,8 +17,8 @@ export default class PasswordResetForm extends React.Component { var state = {}; var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!password || password.length < 5) { - state.error = 'Please enter at least 5 characters.'; + if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { + state.error = 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters.'; this.setState(state); return; } @@ -31,7 +32,7 @@ export default class PasswordResetForm extends React.Component { data.data = this.props.data; data.name = this.props.teamName; - client.resetPassword(data, + Client.resetPassword(data, function resetSuccess() { this.setState({error: null, updateText: 'Your password has been updated successfully.'}); }.bind(this), @@ -59,7 +60,7 @@ export default class PasswordResetForm extends React.Component { return ( <div className='col-sm-12'> <div className='signup-team__container'> - <h3>Password Reset</h3> + <h3>{'Password Reset'}</h3> <form onSubmit={this.handlePasswordReset}> <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p> <div className={formClass}> @@ -77,7 +78,7 @@ export default class PasswordResetForm extends React.Component { type='submit' className='btn btn-primary' > - Change my password + {'Change my password'} </button> {updateText} </form> diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 7d8c7e265..856403af5 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -57,7 +57,10 @@ export default class PostsView extends React.Component { this.setState({displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false')}); } isAtBottom() { - return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); + // consider the view to be at the bottom if it's within this many pixels of the bottom + const atBottomMargin = 10; + + return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - atBottomMargin; } handleScroll() { // HACK FOR RHS -- REMOVE WHEN RHS DIES diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index 2edcd8b37..945b09e37 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -17,6 +17,8 @@ export default class RhsThread extends React.Component { constructor(props) { super(props); + this.mounted = false; + this.onChange = this.onChange.bind(this); this.onChangeAll = this.onChangeAll.bind(this); this.forceUpdateInfo = this.forceUpdateInfo.bind(this); @@ -50,8 +52,11 @@ export default class RhsThread extends React.Component { PostStore.addSelectedPostChangeListener(this.onChange); PostStore.addChangeListener(this.onChangeAll); PreferenceStore.addChangeListener(this.forceUpdateInfo); + this.resize(); window.addEventListener('resize', this.handleResize); + + this.mounted = true; } componentDidUpdate() { if ($('.post-right__scroll')[0]) { @@ -63,7 +68,10 @@ export default class RhsThread extends React.Component { PostStore.removeSelectedPostChangeListener(this.onChange); PostStore.removeChangeListener(this.onChangeAll); PreferenceStore.removeChangeListener(this.forceUpdateInfo); + window.removeEventListener('resize', this.handleResize); + + this.mounted = false; } forceUpdateInfo() { if (this.state.postList) { @@ -82,7 +90,7 @@ export default class RhsThread extends React.Component { } onChange() { var newState = this.getStateFromStores(); - if (!Utils.areObjectsEqual(newState, this.state)) { + if (this.mounted && !Utils.areObjectsEqual(newState, this.state)) { this.setState(newState); } } @@ -120,7 +128,7 @@ export default class RhsThread extends React.Component { } var newState = this.getStateFromStores(); - if (!Utils.areObjectsEqual(newState, this.state)) { + if (this.mounted && !Utils.areObjectsEqual(newState, this.state)) { this.setState(newState); } } diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index eaeb7bb91..c902731c9 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -506,9 +506,9 @@ export default class Sidebar extends React.Component { link.rel = 'shortcut icon'; link.id = 'favicon'; if (this.badgesActive) { - link.href = '/static/images/redfavicon.ico'; + link.href = '/static/images/favicon/redfavicon-16x16.png'; } else { - link.href = '/static/images/favicon.ico'; + link.href = '/static/images/favicon/favicon-16x16.png'; } var head = document.getElementsByTagName('head')[0]; var oldLink = document.getElementById('favicon'); diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index df11fe045..ace0d28ae 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -5,6 +5,7 @@ import * as Utils from '../utils/utils.jsx'; import * as client from '../utils/client.jsx'; import UserStore from '../stores/user_store.jsx'; import BrowserStore from '../stores/browser_store.jsx'; +import Constants from '../utils/constants.jsx'; export default class SignupUserComplete extends React.Component { constructor(props) { @@ -51,7 +52,7 @@ export default class SignupUserComplete extends React.Component { return; } else if (usernameError) { this.setState({ - nameError: 'Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.', + nameError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + ' lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.', emailError: '', passwordError: '', serverError: '' @@ -60,8 +61,8 @@ export default class SignupUserComplete extends React.Component { } const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!providedPassword || providedPassword.length < 5) { - this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); + if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) { + this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''}); return; } @@ -111,7 +112,7 @@ export default class SignupUserComplete extends React.Component { client.track('signup', 'signup_user_01_welcome'); if (this.state.wizard === 'finished') { - return <div>You've already completed the signup process for this invitation or this invitation has expired.</div>; + return <div>{"You've already completed the signup process for this invitation or this invitation has expired."}</div>; } // set up error labels @@ -123,9 +124,11 @@ export default class SignupUserComplete extends React.Component { } var nameError = null; + var nameHelpText = <span className='help-block'>{'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'"}</span>; var nameDivStyle = 'form-group'; if (this.state.nameError) { nameError = <label className='control-label'>{this.state.nameError}</label>; + nameHelpText = ''; nameDivStyle += ' has-error'; } @@ -148,7 +151,7 @@ export default class SignupUserComplete extends React.Component { // set up the email entry and hide it if an email was provided var yourEmailIs = ''; if (this.state.user.email) { - yourEmailIs = <span>Your email address is <strong>{this.state.user.email}</strong>. You'll use this address to sign in to {global.window.mm_config.SiteName}.</span>; + yourEmailIs = <span>{'Your email address is '}<strong>{this.state.user.email}</strong>{". You'll use this address to sign in to " + global.window.mm_config.SiteName + '.'}</span>; } var emailContainerStyle = 'margin--extra'; @@ -158,7 +161,7 @@ export default class SignupUserComplete extends React.Component { var email = ( <div className={emailContainerStyle}> - <h5><strong>What's your email address?</strong></h5> + <h5><strong>{"What's your email address?"}</strong></h5> <div className={emailDivStyle}> <input type='email' @@ -208,7 +211,7 @@ export default class SignupUserComplete extends React.Component { {email} {yourEmailIs} <div className='margin--extra'> - <h5><strong>Choose your username</strong></h5> + <h5><strong>{'Choose your username'}</strong></h5> <div className={nameDivStyle}> <input type='text' @@ -219,11 +222,11 @@ export default class SignupUserComplete extends React.Component { spellCheck='false' /> {nameError} - <span className='help-block'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</span> + {nameHelpText} </div> </div> <div className='margin--extra'> - <h5><strong>Choose your password</strong></h5> + <h5><strong>{'Choose your password'}</strong></h5> <div className={passwordDivStyle}> <input type='password' @@ -243,7 +246,7 @@ export default class SignupUserComplete extends React.Component { onClick={this.handleSubmit} className='btn-primary btn' > - Create Account + {'Create Account'} </button> </p> </div> @@ -255,7 +258,7 @@ export default class SignupUserComplete extends React.Component { <div> {signupMessage} <div className='or__container'> - <span>or</span> + <span>{'or'}</span> </div> </div> ); @@ -268,10 +271,10 @@ export default class SignupUserComplete extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h5 className='margin--less'>Welcome to:</h5> + <h5 className='margin--less'>{'Welcome to:'}</h5> <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2> - <h2 className='signup-team__subdomain'>on {global.window.mm_config.SiteName}</h2> - <h4 className='color--light'>Let's create your account</h4> + <h2 className='signup-team__subdomain'>{'on ' + global.window.mm_config.SiteName}</h2> + <h4 className='color--light'>{"Let's create your account"}</h4> {signupMessage} {emailSignup} {serverError} diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx index cc06a940e..b6fb3389f 100644 --- a/web/react/components/team_general_tab.jsx +++ b/web/react/components/team_general_tab.jsx @@ -66,7 +66,7 @@ export default class GeneralTab extends React.Component { handleTeamListingRadio(listing) { if (global.window.mm_config.EnableTeamListing !== 'true' && listing) { - this.setState({clientError: 'Team directory has been disabled. Please ask a system admin to enable it.'}); + this.setState({clientError: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'}); } else { this.setState({allow_team_listing: listing}); } diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index 378c7fe2c..7e11d38c3 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -4,6 +4,7 @@ import * as Client from '../utils/client.jsx'; import BrowserStore from '../stores/browser_store.jsx'; import UserStore from '../stores/user_store.jsx'; +import Constants from '../utils/constants.jsx'; export default class TeamSignupPasswordPage extends React.Component { constructor(props) { @@ -23,8 +24,8 @@ export default class TeamSignupPasswordPage extends React.Component { e.preventDefault(); var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!password || password.length < 5) { - this.setState({passwordError: 'Please enter at least 5 characters'}); + if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { + this.setState({passwordError: 'Please enter at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters'}); return; } @@ -92,15 +93,15 @@ export default class TeamSignupPasswordPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2 className='margin--less'>Your password</h2> - <h5 className='color--light'>Select a password that you'll use to login with your email address:</h5> + <h2 className='margin--less'>{'Your password'}</h2> + <h5 className='color--light'>{"Select a password that you'll use to login with your email address:"}</h5> <div className='inner__content margin--extra'> - <h5><strong>Email</strong></h5> + <h5><strong>{'Email'}</strong></h5> <div className='block--gray form-group'>{this.props.state.team.email}</div> <div className={passwordDivStyle}> <div className='row'> <div className='col-sm-11'> - <h5><strong>Choose your password</strong></h5> + <h5><strong>{'Choose your password'}</strong></h5> <input autoFocus={true} type='password' @@ -110,7 +111,7 @@ export default class TeamSignupPasswordPage extends React.Component { maxLength='128' spellCheck='false' /> - <span className='color--light help-block'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</span> + <span className='color--light help-block'>{'Passwords must contain ' + Constants.MIN_PASSWORD_LENGTH + ' to ' + Constants.MAX_PASSWORD_LENGTH + ' characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'}</span> </div> </div> {passwordError} @@ -125,7 +126,7 @@ export default class TeamSignupPasswordPage extends React.Component { data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating team...'} onClick={this.submitNext} > - Finish + {'Finish'} </button> </div> <p>By proceeding to create your account and use {global.window.mm_config.SiteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {global.window.mm_config.SiteName}.</p> @@ -134,7 +135,7 @@ export default class TeamSignupPasswordPage extends React.Component { href='#' onClick={this.submitBack} > - Back to previous step + {'Back to previous step'} </a> </div> </form> diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index de239f169..6ccab6656 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -3,6 +3,7 @@ import * as Utils from '../utils/utils.jsx'; import * as Client from '../utils/client.jsx'; +import Constants from '../utils/constants.jsx'; export default class TeamSignupUsernamePage extends React.Component { constructor(props) { @@ -33,7 +34,7 @@ export default class TeamSignupUsernamePage extends React.Component { this.setState({nameError: 'This username is reserved, please choose a new one.'}); return; } else if (usernameError) { - this.setState({nameError: 'Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''}); + this.setState({nameError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + ' characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''}); return; } @@ -45,9 +46,11 @@ export default class TeamSignupUsernamePage extends React.Component { Client.track('signup', 'signup_team_06_username'); var nameError = null; + var nameHelpText = <span className='color--light help-block'>{'Usernames must begin with a letter and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"}</span>; var nameDivClass = 'form-group'; if (this.state.nameError) { nameError = <label className='control-label'>{this.state.nameError}</label>; + nameHelpText = ''; nameDivClass += ' has-error'; } @@ -58,13 +61,13 @@ export default class TeamSignupUsernamePage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2 className='margin--less'>Your username</h2> + <h2 className='margin--less'>{'Your username'}</h2> <h5 className='color--light'>{'Select a memorable username that makes it easy for teammates to identify you:'}</h5> <div className='inner__content margin--extra'> <div className={nameDivClass}> <div className='row'> <div className='col-sm-11'> - <h5><strong>Choose your username</strong></h5> + <h5><strong>{'Choose your username'}</strong></h5> <input autoFocus={true} type='text' @@ -75,7 +78,7 @@ export default class TeamSignupUsernamePage extends React.Component { maxLength='128' spellCheck='false' /> - <span className='color--light help-block'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</span> + {nameHelpText} </div> </div> {nameError} @@ -86,7 +89,7 @@ export default class TeamSignupUsernamePage extends React.Component { className='btn btn-primary margin--extra' onClick={this.submitNext} > - Next + {'Next'} <i className='glyphicon glyphicon-chevron-right'></i> </button> <div className='margin--extra'> @@ -94,7 +97,7 @@ export default class TeamSignupUsernamePage extends React.Component { href='#' onClick={this.submitBack} > - Back to previous step + {'Back to previous step'} </a> </div> </form> diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index b29f304ab..62c0d5218 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -22,8 +22,6 @@ export default class Textbox extends React.Component { this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.resize = this.resize.bind(this); - this.handleFocus = this.handleFocus.bind(this); - this.handleBlur = this.handleBlur.bind(this); this.showPreview = this.showPreview.bind(this); this.state = { @@ -81,51 +79,43 @@ export default class Textbox extends React.Component { } resize() { - const e = this.refs.message.getTextbox(); - const w = ReactDOM.findDOMNode(this.refs.wrapper); + const textbox = this.refs.message.getTextbox(); + const $textbox = $(textbox); + const $wrapper = $(ReactDOM.findDOMNode(this.refs.wrapper)); - const prevHeight = $(e).height(); + const padding = parseInt($textbox.css('padding-bottom'), 10) + parseInt($textbox.css('padding-top'), 10); + const borders = parseInt($textbox.css('border-bottom-width'), 10) + parseInt($textbox.css('border-top-width'), 10); + const maxHeight = parseInt($textbox.css('max-height'), 10) - borders; - const lht = parseInt($(e).css('lineHeight'), 10); - const lines = e.scrollHeight / lht; - let mod = 15; + const prevHeight = $textbox.height(); - if (lines < 2.5 || this.props.messageText === '') { - mod = 30; - } + // set the height to auto and remove the scrollbar so we can get the actual size of the contents + $textbox.css('height', 'auto').css('overflow-y', 'hidden'); + + let height = textbox.scrollHeight - padding; + + if (height + padding > maxHeight) { + height = maxHeight - padding; - if (e.scrollHeight - mod < 167) { - $(e).css({height: 'auto', 'overflow-y': 'hidden'}).height(e.scrollHeight - mod); - $(w).css({height: 'auto'}).height(e.scrollHeight + 2); - $(w).closest('.post-body__cell').removeClass('scroll'); - if (this.state.preview) { - $(ReactDOM.findDOMNode(this.refs.preview)).css({height: 'auto', 'overflow-y': 'auto'}).height(e.scrollHeight - mod); - } + // turn scrollbar on and move over attachment icon to compensate for that + $textbox.css('overflow-y', 'scroll'); + $wrapper.closest('.post-body__cell').addClass('scroll'); } else { - $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167 - mod); - $(w).css({height: 'auto'}).height(163); - $(w).closest('.post-body__cell').addClass('scroll'); - if (this.state.preview) { - $(ReactDOM.findDOMNode(this.refs.preview)).css({height: 'auto', 'overflow-y': 'scroll'}).height(163); - } + $wrapper.closest('.post-body__cell').removeClass('scroll'); } - if (prevHeight !== $(e).height() && this.props.onHeightChange) { - this.props.onHeightChange(); - } - } + // set the textarea to be the proper height + $textbox.height(height); + + // set the wrapper height to match the height of the textbox including padding and borders + $wrapper.height(height + padding + borders); - handleFocus() { - const elm = this.refs.message.getTextbox(); - if (elm.title === elm.value) { - elm.value = ''; + if (this.state.preview) { + $(ReactDOM.findDOMNode(this.refs.preview)).height(height + borders); } - } - handleBlur() { - const elm = this.refs.message.getTextbox(); - if (elm.value === '') { - elm.value = elm.title; + if (height !== prevHeight && this.props.onHeightChange) { + this.props.onHeightChange(); } } @@ -178,9 +168,6 @@ export default class Textbox extends React.Component { onUserInput={this.props.onUserInput} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - onPaste={this.handlePaste} style={{visibility: this.state.preview ? 'hidden' : 'visible'}} listComponent={SuggestionList} providers={this.suggestionProviders} diff --git a/web/react/components/unread_channel_indicator.jsx b/web/react/components/unread_channel_indicator.jsx index 6ae06528b..c0c34584f 100644 --- a/web/react/components/unread_channel_indicator.jsx +++ b/web/react/components/unread_channel_indicator.jsx @@ -10,7 +10,7 @@ export default class UnreadChannelIndicator extends React.Component { render() { let displayValue = 'none'; if (this.props.show) { - displayValue = 'initial'; + displayValue = 'block'; } return ( <div diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx new file mode 100644 index 000000000..123165b76 --- /dev/null +++ b/web/react/components/user_settings/manage_languages.jsx @@ -0,0 +1,101 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Client from '../../utils/client.jsx'; +import * as Utils from '../../utils/utils.jsx'; + +export default class ManageLanguage extends React.Component { + constructor(props) { + super(props); + + this.setupInitialState = this.setupInitialState.bind(this); + this.setLanguage = this.setLanguage.bind(this); + this.changeLanguage = this.changeLanguage.bind(this); + this.submitUser = this.submitUser.bind(this); + this.state = this.setupInitialState(props); + } + setupInitialState(props) { + var user = props.user; + return { + languages: Utils.languages(), + locale: user.locale + }; + } + setLanguage(e) { + this.setState({locale: e.target.value}); + } + changeLanguage(e) { + e.preventDefault(); + + var user = this.props.user; + var locale = this.state.locale; + + user.locale = locale; + + this.submitUser(user); + } + submitUser(user) { + Client.updateUser(user, + () => { + window.location.reload(true); + }, + (err) => { + let serverError; + if (err.message) { + serverError = err.message; + } else { + serverError = err; + } + this.setState({serverError}); + } + ); + } + render() { + let serverError; + if (this.state.serverError) { + serverError = <label className='has-error'>{this.state.serverError}</label>; + } + + const options = []; + this.state.languages.forEach((lang) => { + options.push( + <option + key={lang.value} + value={lang.value} + > + {lang.name} + </option>); + }); + + return ( + <div key='changeLanguage'> + <br/> + <label className='control-label'>{'Change interface language'}</label> + <div className='padding-top'> + <select + ref='language' + className='form-control' + value={this.state.locale} + onChange={this.setLanguage} + > + {options} + </select> + {serverError} + <div className='padding-top'> + <a + className={'btn btn-sm btn-primary'} + href='#' + onClick={this.changeLanguage} + > + {'Set language'} + </a> + </div> + </div> + </div> + ); + } +} + +ManageLanguage.propTypes = { + user: React.PropTypes.object +};
\ No newline at end of file diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index 1ff0a2913..f2c2502fb 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -5,7 +5,9 @@ import {savePreferences} from '../../utils/client.jsx'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import Constants from '../../utils/constants.jsx'; +const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES; import PreferenceStore from '../../stores/preference_store.jsx'; +import ManageLanguages from './manage_languages.jsx'; import * as Utils from '../../utils/utils.jsx'; function getDisplayStateFromStores() { @@ -78,6 +80,7 @@ export default class UserSettingsDisplay extends React.Component { let clockSection; let nameFormatSection; let fontSection; + let languagesSection; if (this.props.activeSection === 'clock') { const clockFormat = [false, false]; @@ -292,6 +295,48 @@ export default class UserSettingsDisplay extends React.Component { ); } + if (Utils.isFeatureEnabled(PreReleaseFeatures.LOC_PREVIEW)) { + if (this.props.activeSection === 'languages') { + var inputs = []; + inputs.push( + <ManageLanguages + user={this.props.user} + key='languages-ui' + /> + ); + + languagesSection = ( + <SettingItemMax + title={'Language'} + width='medium' + inputs={inputs} + updateSection={(e) => { + this.updateSection(''); + e.preventDefault(); + }} + /> + ); + } else { + var locale = 'English'; + Utils.languages().forEach((l) => { + if (l.value === this.props.user.locale) { + locale = l.name; + } + }); + + languagesSection = ( + <SettingItemMin + title={'Language'} + width='medium' + describe={locale} + updateSection={() => { + this.updateSection('languages'); + }} + /> + ); + } + } + return ( <div> <div className='modal-header'> @@ -324,6 +369,7 @@ export default class UserSettingsDisplay extends React.Component { <div className='divider-dark'/> {nameFormatSection} <div className='divider-dark'/> + {languagesSection} </div> </div> ); diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 014038dd4..df7ae4a25 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -47,7 +47,7 @@ export default class UserSettingsGeneralTab extends React.Component { this.setState({clientError: 'This username is reserved, please choose a new one.'}); return; } else if (usernameError) { - this.setState({clientError: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."}); + this.setState({clientError: 'Username must begin with a letter, and contain between ' + Constants.MIN_USERNAME_LENGTH + ' to ' + Constants.MAX_USERNAME_LENGTH + " lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."}); return; } @@ -493,7 +493,7 @@ export default class UserSettingsGeneralTab extends React.Component { ); submit = this.submitEmail; - } else { + } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { inputs.push( <div key='oauthEmailInfo' @@ -531,7 +531,7 @@ export default class UserSettingsGeneralTab extends React.Component { } else { describe = UserStore.getCurrentUser().email; } - } else { + } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) { describe = 'Log in done through GitLab'; } diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx index d1266dd3f..5a21abd19 100644 --- a/web/react/components/user_settings/user_settings_security.jsx +++ b/web/react/components/user_settings/user_settings_security.jsx @@ -48,8 +48,8 @@ export default class SecurityTab extends React.Component { return; } - if (newPassword.length < 5) { - this.setState({passwordError: 'New passwords must be at least 5 characters', serverError: ''}); + if (newPassword.length < Constants.MIN_PASSWORD_LENGTH) { + this.setState({passwordError: 'New passwords must be at least ' + Constants.MIN_PASSWORD_LENGTH + ' characters', serverError: ''}); return; } @@ -337,7 +337,7 @@ export default class SecurityTab extends React.Component { className='security-links theme' dialogType={AccessHistoryModal} > - <i className='fa fa-clock-o'></i>View Access History + <i className='fa fa-clock-o'></i>{'View Access History'} </ToggleModalButton> <b> </b> <ToggleModalButton diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 31ec91248..d11f8a21c 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -211,7 +211,7 @@ export default class ViewImageModal extends React.Component { } const filename = this.props.filenames[this.state.imgId]; - const fileUrl = Utils.getFileUrl(filename); + const fileUrl = Utils.getFileUrl(filename, true); var content; if (this.state.loaded[this.state.imgId]) { @@ -377,6 +377,7 @@ function ImagePreview({filename, fileUrl, fileInfo, maxHeight}) { <a href={fileUrl} target='_blank' + download={true} > <img style={{maxHeight}} |