diff options
Diffstat (limited to 'web')
22 files changed, 423 insertions, 237 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index b6182cfa5..90a776791 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -67,7 +67,7 @@ var PopoverListMembers = React.createClass({ return ( <div id='member_popover' data-toggle='popover' data-content={popoverHtml} data-original-title='Members' > - <div id='member_tooltip' data-toggle='tooltip' title='View Channel Members'> + <div id='member_tooltip' data-placement='left' data-toggle='tooltip' title='View Channel Members'> {count} <span className='glyphicon glyphicon-user' aria-hidden='true'></span> </div> </div> @@ -175,6 +175,11 @@ module.exports = React.createClass({ } } + var channelTerm = 'Channel'; + if (channel.type === 'P') { + channelTerm = 'Group'; + } + return ( <table className='channel-header alt'> <tr> @@ -196,18 +201,18 @@ module.exports = React.createClass({ <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li> : null } - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li> + <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set {channelTerm} Description...</a></li> <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li> {isAdmin && !ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li> + <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename {channelTerm}...</a></li> : null } {isAdmin && !ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li> + <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete {channelTerm}...</a></li> : null } {!ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</a></li> + <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave {channelTerm}</a></li> : null } </ul> diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index a0f68df98..5f1bb6d0e 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -20,6 +20,12 @@ module.exports = React.createClass({ PostStore.addMentionDataChangeListener(this.onListenerChange); var self = this; + $('.post-right__scroll').scroll(function(){ + if($('.mentions--top').length){ + $('#reply_mention_tab .mentions--top').css({ bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top }); + } + }); + $('body').on('keydown.mentionlist', '#' + this.props.id, function(e) { if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index 6cf195795..ffcbfd32d 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -53,7 +53,7 @@ module.exports = React.createClass({ channel.team_id = cu.team_id; channel.description = this.refs.channel_desc.getDOMNode().value.trim(); - channel.type = this.state.channel_type; + channel.type = this.state.channelType; var self = this; client.createChannel(channel, @@ -107,6 +107,11 @@ module.exports = React.createClass({ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; } + var channelTerm = 'Channel'; + if (this.state.channelType === 'P') { + channelTerm = 'Group'; + } + return ( <div className='modal fade' id='new_channel' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'> <div className='modal-dialog'> @@ -116,7 +121,7 @@ module.exports = React.createClass({ <span aria-hidden='true'>×</span> <span className='sr-only'>Cancel</span> </button> - <h4 className='modal-title'>New Channel</h4> + <h4 className='modal-title'>New {channelTerm}</h4> </div> <form role='form'> <div className='modal-body'> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 3f59d5843..bb1b1704c 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -37,15 +37,23 @@ module.exports = React.createClass({ componentDidMount: function() { var user = UserStore.getCurrentUser(); if (user.props && user.props.theme) { - utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;'); utils.changeCss('div.theme', 'background-color:'+user.props.theme+';'); utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';'); - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';'); utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme+';'); utils.changeCss('.mention', 'background: ' + user.props.theme+';'); utils.changeCss('.mention-link', 'color: ' + user.props.theme+';'); utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme+';}'); } + if (user.props.theme != '#000000' && user.props.theme != '#585858') { + utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';'); + utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;'); + } else if (user.props.theme == '#000000') { + utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) +';'); + $('.team__header').addClass('theme--black'); + } else if (user.props.theme == '#585858') { + utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) +';'); + $('.team__header').addClass('theme--gray'); + } PostStore.addChangeListener(this._onChange); ChannelStore.addChangeListener(this._onChange); @@ -311,7 +319,6 @@ module.exports = React.createClass({ } else if (channel.type === 'D') { var teammate = utils.getDirectTeammate(channel.id) - if (teammate) { var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username; more_messages = ( @@ -399,7 +406,7 @@ module.exports = React.createClass({ var postCtls = []; if (posts) { - var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date(); + var previousPostDay = new Date(0); var currentPostDay; for (var i = order.length-1; i >= 0; i--) { diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx index fa4c8bb62..e97b67706 100644 --- a/web/react/components/setting_picture.jsx +++ b/web/react/components/setting_picture.jsx @@ -20,8 +20,14 @@ module.exports = React.createClass({ } }, render: function() { - var client_error = this.props.client_error ? <div className='form-group has-error'><label className='control-label'>{ this.props.client_error }</label></div> : null; - var server_error = this.props.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.props.server_error }</label></div> : null; + var clientError = null; + if (this.props.client_error) { + clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>; + } + var serverError = null; + if (this.props.server_error) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.props.server_error}</label></div>; + } var img = null; if (this.props.picture) { @@ -30,8 +36,20 @@ module.exports = React.createClass({ img = (<img ref='image' className='profile-img' src={this.props.src}/>); } - var self = this; + var confirmButton; + if (this.props.loadingPicture) { + confirmButton = <img className='spinner' src='/static/images/load.gif'/>; + } else { + var confirmButtonClass = 'btn btn-sm'; + if (this.props.submitActive) { + confirmButtonClass += ' btn-primary'; + } else { + confirmButtonClass += ' btn-inactive disabled'; + } + confirmButton = <a className={confirmButtonClass} onClick={this.props.submit}>Save</a>; + } + var self = this; return ( <ul className='section-max'> <li className='col-xs-12 section-title'>{this.props.title}</li> @@ -41,10 +59,10 @@ module.exports = React.createClass({ {img} </li> <li className='setting-list-item'> - {server_error} - {client_error} + {serverError} + {clientError} <span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span> - <a className={this.props.submitActive ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-inactive disabled'} onClick={this.props.submit}>Save</a> + {confirmButton} <a className='btn btn-sm theme' href='#' onClick={self.props.updateSection}>Cancel</a> </li> </ul> diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index e01ddcd05..ba8f9bacd 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -1,15 +1,15 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); function getStateFromStores() { - return { teams: UserStore.getTeams() }; + return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; } var NavbarDropdown = React.createClass({ @@ -19,20 +19,24 @@ var NavbarDropdown = React.createClass({ }, blockToggle: false, componentDidMount: function() { - UserStore.addTeamsChangeListener(this._onChange); + UserStore.addTeamsChangeListener(this.onListenerChange); + TeamStore.addChangeListener(this.onListenerChange); var self = this; - $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function(e) { + $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function() { self.blockToggle = true; - setTimeout(function(){self.blockToggle = false;}, 100); + setTimeout(function() { + self.blockToggle = false; + }, 100); }); }, componentWillUnmount: function() { - UserStore.removeTeamsChangeListener(this._onChange); + UserStore.removeTeamsChangeListener(this.onListenerChange); + TeamStore.removeChangeListener(this.onListenerChange); $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown'); }, - _onChange: function() { + onListenerChange: function() { if (this.isMounted()) { var newState = getStateFromStores(); if (!utils.areStatesEqual(newState, this.state)) { @@ -44,62 +48,65 @@ var NavbarDropdown = React.createClass({ return getStateFromStores(); }, render: function() { - var team_link = ""; - var invite_link = ""; - var manage_link = ""; - var rename_link = ""; + var teamLink = ''; + var inviteLink = ''; + var manageLink = ''; + var renameLink = ''; var currentUser = UserStore.getCurrentUser(); var isAdmin = false; + var teamSettings = null; if (currentUser != null) { - isAdmin = currentUser.roles.indexOf("admin") > -1; + isAdmin = currentUser.roles.indexOf('admin') > -1; - invite_link = ( <li> <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a> </li>); + inviteLink = (<li> <a href='#' data-toggle='modal' data-target='#invite_member'>Invite New Member</a> </li>); - if (this.props.teamType == "O") { - team_link = ( + if (this.props.teamType === 'O') { + teamLink = ( <li> - <a href="#" data-toggle="modal" data-target="#get_link" data-title="Team Invite" data-value={location.origin+"/signup_user_complete/?id="+currentUser.team_id}>Get Team Invite Link</a> + <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={location.origin + '/signup_user_complete/?id=' + currentUser.team_id}>Get Team Invite Link</a> </li> ); } } if (isAdmin) { - manage_link = ( <li> <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a> </li>); - rename_link = ( <li> <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a> </li>); + manageLink = (<li> <a href='#' data-toggle='modal' data-target='#team_members'>Manage Team</a> </li>); + renameLink = (<li> <a href='#' data-toggle='modal' data-target='#rename_team_link'>Rename</a> </li>); + teamSettings = (<li> <a href='#' data-toggle='modal' data-target='#team_settings'>Team Settings</a> </li>); } var teams = []; - teams.push(<li className="divider" key="div"></li>); - if (this.state.teams.length > 1) { - for (var i = 0; i < this.state.teams.length; i++) { - var teamName = this.state.teams[i]; - - teams.push(<li key={ teamName }><a href={utils.getWindowLocationOrigin() + "/" + teamName }>Switch to { teamName }</a></li>); - } + teams.push(<li className='divider' key='div'></li>); + if (this.state.teams.length > 1 && this.state.currentTeam) { + var curTeamName = this.state.currentTeam.name; + this.state.teams.forEach(function(teamName) { + if (teamName !== curTeamName) { + teams.push(<li key={teamName}><a href={utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>); + } + }); } - teams.push(<li><a href={utils.getWindowLocationOrigin() + "/signup_team" }>Create a New Team</a></li>); + teams.push(<li><a href={utils.getWindowLocationOrigin() + '/signup_team'}>Create a New Team</a></li>); return ( - <ul className="nav navbar-nav navbar-right"> - <li ref="dropdown" className="dropdown"> - <a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"> - <span className="dropdown__icon" dangerouslySetInnerHTML={{__html: Constants.MENU_ICON }} /> + <ul className='nav navbar-nav navbar-right'> + <li ref='dropdown' className='dropdown'> + <a href='#' className='dropdown-toggle' data-toggle='dropdown' role='button' aria-expanded='false'> + <span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a> - <ul className="dropdown-menu" role="menu"> - <li><a href="#" data-toggle="modal" data-target="#user_settings1">Account Settings</a></li> - { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : null } - { invite_link } - { team_link } - { manage_link } - { rename_link } - <li><a href="#" onClick={this.handleLogoutClick}>Logout</a></li> - { teams } - <li className="divider"></li> - <li><a target="_blank" href={config.HelpLink}>Help</a></li> - <li><a target="_blank" href={config.ReportProblemLink}>Report a Problem</a></li> + <ul className='dropdown-menu' role='menu'> + <li><a href='#' data-toggle='modal' data-target='#user_settings1'>Account Settings</a></li> + {teamSettings} + {inviteLink} + {teamLink} + {manageLink} + {renameLink} + <li><a href='#' onClick={this.handleLogoutClick}>Logout</a></li> + {teams} + <li className='divider'></li> + <li><a target='_blank' href={config.HelpLink}>Help</a></li> + <li><a target='_blank' href={config.ReportProblemLink}>Report a Problem</a></li> </ul> </li> </ul> @@ -109,14 +116,13 @@ var NavbarDropdown = React.createClass({ module.exports = React.createClass({ displayName: 'SidebarHeader', - getDefaultProps: function() { return { teamDisplayName: config.SiteName }; }, - toggleDropdown: function(e) { + toggleDropdown: function() { if (this.refs.dropdown.blockToggle) { this.refs.dropdown.blockToggle = false; return; @@ -126,25 +132,26 @@ module.exports = React.createClass({ render: function() { var me = UserStore.getCurrentUser(); + var profilePicture = null; if (!me) { return null; } + if (me.last_picture_update) { + profilePicture = (<img className='user__picture' src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} />); + } + return ( - <div className="team__header theme"> - <a href="#" onClick={this.toggleDropdown}> - { me.last_picture_update ? - <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} /> - : - null - } - <div className="header__info"> - <div className="user__name">{ '@' + me.username}</div> - <div className="team__name">{ this.props.teamDisplayName }</div> + <div className='team__header theme'> + <a href='#' onClick={this.toggleDropdown}> + {profilePicture} + <div className='header__info'> + <div className='user__name'>{'@' + me.username}</div> + <div className='team__name'>{this.props.teamDisplayName }</div> </div> </a> - <NavbarDropdown ref="dropdown" teamType={this.props.teamType} /> + <NavbarDropdown ref='dropdown' teamType={this.props.teamType} /> </div> ); } diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 447a405bd..3f35a5912 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -248,6 +248,8 @@ TeamURLPage = React.createClass({ }, render: function() { + $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} ); + client.track('signup', 'signup_team_03_url'); var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null; @@ -260,8 +262,8 @@ TeamURLPage = React.createClass({ <div className={ name_error ? "form-group has-error" : "form-group" }> <div className="row"> <div className="col-sm-11"> - <div className="input-group"> - <span className="input-group-addon">{ utils.getWindowLocationOrigin() + "/" }</span> + <div className="input-group input-group--limit"> + <span data-toggle="tooltip" title={ utils.getWindowLocationOrigin() + "/" } className="input-group-addon">{ utils.getWindowLocationOrigin() + "/" }</span> <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/> </div> </div> diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 03808e821..b21553d8a 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -1,7 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); @@ -9,36 +8,41 @@ var BrowserStore = require('../stores/browser_store.jsx'); module.exports = React.createClass({ handleSubmit: function(e) { - e.preventDefault(); + e.preventDefault(); this.state.user.username = this.refs.name.getDOMNode().value.trim(); if (!this.state.user.username) { - this.setState({name_error: "This field is required", email_error: "", password_error: "", server_error: ""}); + this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; } - var username_error = utils.isValidUsername(this.state.user.username); - if (username_error === "Cannot use a reserved word as a username.") { - this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""}); + var usernameError = utils.isValidUsername(this.state.user.username); + if (usernameError === 'Cannot use a reserved word as a username.') { + this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); return; - } else if (username_error) { - this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.", email_error: "", password_error: "", server_error: ""}); + } 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 \'_\'.', + emailError: '', + passwordError: '', + serverError: '' + }); return; } this.state.user.email = this.refs.email.getDOMNode().value.trim(); if (!this.state.user.email) { - this.setState({name_error: "", email_error: "This field is required", password_error: ""}); + this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); return; } this.state.user.password = this.refs.password.getDOMNode().value.trim(); if (!this.state.user.password || this.state.user.password .length < 5) { - this.setState({name_error: "", email_error: "", password_error: "Please enter at least 5 characters", server_error: ""}); + this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; } - this.setState({name_error: "", email_error: "", password_error: "", server_error: ""}); + this.setState({nameError: '', emailError: '', passwordError: '', serverError: ''}); this.state.user.allow_marketing = true; @@ -50,108 +54,154 @@ module.exports = React.createClass({ function(data) { UserStore.setLastEmail(this.state.user.email); UserStore.setCurrentUser(data); - if (this.props.hash > 0) - { - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"})); + if (this.props.hash > 0) { + BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); } window.location.href = '/'; }.bind(this), function(err) { - if (err.message == "Login failed because email address has not been verified") { - window.location.href = "/verify_email?email="+ encodeURIComponent(this.state.user.email) + "&teamname=" + encodeURIComponent(this.props.teamName); + if (err.message === 'Login failed because email address has not been verified') { + window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { - this.state.server_error = err.message; - this.setState(this.state); + this.setState({serverError: err.message}); } }.bind(this) ); }.bind(this), function(err) { - this.state.server_error = err.message; - this.setState(this.state); + this.setState({serverError: err.message}); }.bind(this) ); }, getInitialState: function() { - var props = BrowserStore.getGlobalItem(this.props.hash); - - if (!props) { - props = {}; - props.wizard = "welcome"; - props.user = {}; - props.user.team_id = this.props.teamId; - props.user.email = this.props.email; - props.hash = this.props.hash; - props.data = this.props.data; - props.original_email = this.props.email; + var state = BrowserStore.getGlobalItem(this.props.hash); + + if (!state) { + state = {}; + state.wizard = 'welcome'; + state.user = {}; + state.user.team_id = this.props.teamId; + state.user.email = this.props.email; + state.hash = this.props.hash; + state.data = this.props.data; + state.original_email = this.props.email; } - return props; + return state; }, render: function() { - 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>); + if (this.state.wizard === 'finished') { + return <div>You've already completed the signup process for this invitation or this invitation has expired.</div>; + } + + // set up error labels + var emailError = null; + var emailDivStyle = 'form-group'; + if (this.state.emailError) { + emailError = <label className='control-label'>{this.state.emailError}</label>; + emailDivStyle += ' has-error'; + } + + var nameError = null; + var nameDivStyle = 'form-group'; + if (this.state.nameError) { + nameError = <label className='control-label'>{this.state.nameError}</label>; + nameDivStyle += ' has-error'; + } + + var passwordError = null; + var passwordDivStyle = 'form-group'; + if (this.state.passwordError) { + passwordError = <label className='control-label'>{this.state.passwordError}</label>; + passwordDivStyle += ' has-error'; + } + + var serverError = null; + if (this.state.serverError) { + serverError = ( + <div className={'form-group has-error'}> + <label className='control-label'>{this.state.serverError}</label> + </div> + ); } - var email_error = this.state.email_error ? <label className='control-label'>{ this.state.email_error }</label> : null; - var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null; - var password_error = this.state.password_error ? <label className='control-label'>{ this.state.password_error }</label> : null; - var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null; + // 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 {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>; + } - var yourEmailIs = this.state.user.email == "" ? "" : <span>Your email address is { this.state.user.email }. </span> + var emailContainerStyle = "margin--extra"; + if (this.state.original_email) { + emailContainerStyle = "hidden"; + } var email = ( - <div className={ this.state.original_email == "" ? "margin--extra" : "hidden"} > + <div className={emailContainerStyle}> <h5><strong>What's your email address?</strong></h5> - <div className={ email_error ? "form-group has-error" : "form-group" }> - <input type="email" ref="email" className="form-control" defaultValue={ this.state.user.email } placeholder="" maxLength="128" autoFocus={true} /> - { email_error } - </div> + <div className={emailDivStyle}> + <input type='email' ref='email' className='form-control' defaultValue={this.state.user.email} placeholder='' maxLength='128' autoFocus={true} /> + {emailError} </div> + </div> ); - var auth_services = JSON.parse(this.props.authServices); + // add options to log in using another service + var authServices = JSON.parse(this.props.authServices); + + var signupMessage = null; + if (authServices.indexOf('gitlab') >= 0) { + signupMessage = ( + <div> + <a className='btn btn-custom-login gitlab' href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}> + <span className='icon' /> + <span>with GitLab</span> + </a> + <div className='or__container'> + <span>or</span> + </div> + </div> + ); + } - var signup_message; - if (auth_services.indexOf("gitlab") >= 0) { - signup_message = <div><a className="btn btn-custom-login gitlab" href={"/"+this.props.teamName+"/signup/gitlab"+window.location.search}><span className="icon" />{"with GitLab"}</a> - <div className="or__container"><span>or</span></div></div>; + var termsDisclaimer = null; + if (config.ShowTermsDuringSignup) { + termsDisclaimer = <p>By creating an account and using Mattermost you are agreeing to our <a href={config.TermsLink}>Terms of Service</a>. If you do not agree, you cannot use this service.</p>; } return ( <div> <form> - <img className="signup-team-logo" src="/static/images/logo.png" /> - <h5 className="margin--less">Welcome to:</h5> - <h2 className="signup-team__name">{ this.props.teamDisplayName }</h2> - <h2 className="signup-team__subdomain">on { config.SiteName }</h2> - <h4 className="color--light">Let's create your account</h4> - { signup_message } - <div className="inner__content"> - { email } - <p className={ this.state.original_email == "" ? "hidden" : ""}>{ yourEmailIs } You’ll use this address to sign in to {config.SiteName}.</p> - <div className="margin--extra"> - <h5><strong>Choose your username</strong></h5> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" /> - { name_error } - <p className="form__hint">Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'"</p> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h5 className='margin--less'>Welcome to:</h5> + <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2> + <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> + <h4 className='color--light'>Let's create your account</h4> + {signupMessage} + <div className='inner__content'> + {email} + {yourEmailIs} + <div className='margin--extra'> + <h5><strong>Choose your username</strong></h5> + <div className={nameDivStyle}> + <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' /> + {nameError} + <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p> + </div> + </div> + <div className='margin--extra'> + <h5><strong>Choose your password</strong></h5> + <div className={passwordDivStyle}> + <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + {passwordError} </div> - </div> - <div className="margin--extra"> - <h5><strong>Choose your password</strong></h5> - <div className={ password_error ? "form-group has-error" : "form-group" }> - <input type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> - { password_error } </div> </div> - </div> - <p className="margin--extra"><button type='submit' onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p> - { server_error } - <p>By creating an account and using Mattermost you are agreeing to our <a href={ config.TermsLink }>Terms of Service</a>. If you do not agree, you cannot use this service.</p> + <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p> + {serverError} + {termsDisclaimer} </form> </div> ); diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 65f025919..5c4d26a23 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -28,6 +28,7 @@ module.exports = React.createClass({ componentDidMount: function() { UserStore.addChangeListener(this._onChange); $("#profile_" + this.uniqueId).popover({placement : 'right', container: 'body', trigger: 'hover', html: true, delay: { "show": 200, "hide": 100 }}); + $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} ); }, componentWillUnmount: function() { UserStore.removeChangeListener(this._onChange); @@ -57,7 +58,7 @@ module.exports = React.createClass({ if (!config.ShowEmail) { data_content += "<div class='text-nowrap'>Email not shared</div>"; } else { - data_content += "<div><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase'>" + this.state.profile.email + "</a></div>"; + data_content += "<div data-toggle='tooltip' title= '" + this.state.profile.email + "'><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase user-popover__email'>" + this.state.profile.email + "</a></div>"; } return ( diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 937bbd9d2..1a0c313d3 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -5,8 +5,6 @@ var UserStore = require('../stores/user_store.jsx'); var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); var SettingPicture = require('./setting_picture.jsx'); -var AccessHistoryModal = require('./access_history_modal.jsx'); -var ActivityLogModal = require('./activity_log_modal.jsx'); var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var utils = require('../utils/utils.jsx'); @@ -653,17 +651,17 @@ var GeneralTab = React.createClass({ var user = this.props.user; var username = this.state.username.trim(); - var username_error = utils.isValidUsername(username); - if (username_error === 'Cannot use a reserved word as a username.') { - this.setState({client_error: 'This username is reserved, please choose a new one.' }); + var usernameError = utils.isValidUsername(username); + if (usernameError === 'Cannot use a reserved word as a username.') { + this.setState({clientError: 'This username is reserved, please choose a new one.'}); return; - } else if (username_error) { - this.setState({client_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'." }); + } 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 '_'."}); return; } if (user.username === username) { - this.setState({client_error: 'You must submit a new username'}); + this.setState({clientError: 'You must submit a new username'}); return; } @@ -678,7 +676,7 @@ var GeneralTab = React.createClass({ var nickname = this.state.nickname.trim(); if (user.nickname === nickname) { - this.setState({client_error: 'You must submit a new nickname'}) + this.setState({clientError: 'You must submit a new nickname'}); return; } @@ -690,11 +688,11 @@ var GeneralTab = React.createClass({ e.preventDefault(); var user = UserStore.getCurrentUser(); - var firstName = this.state.first_name.trim(); - var lastName = this.state.last_name.trim(); + var firstName = this.state.firstName.trim(); + var lastName = this.state.lastName.trim(); if (user.first_name === firstName && user.last_name === lastName) { - this.setState({client_error: 'You must submit a new first or last name'}) + this.setState({clientError: 'You must submit a new first or last name'}); return; } @@ -714,7 +712,7 @@ var GeneralTab = React.createClass({ } if (email === '' || !utils.isEmail(email)) { - this.setState({ email_error: 'Please enter a valid email address' }); + this.setState({emailError: 'Please enter a valid email address'}); return; } @@ -729,11 +727,11 @@ var GeneralTab = React.createClass({ AsyncClient.getMe(); }.bind(this), function(err) { - state = this.getInitialState(); + var state = this.getInitialState(); if (err.message) { - state.server_error = err.message; + state.serverError = err.message; } else { - state.server_error = err; + state.serverError = err; } this.setState(state); }.bind(this) @@ -753,12 +751,13 @@ var GeneralTab = React.createClass({ var picture = this.state.picture; if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') { - this.setState({client_error: 'Only JPG or PNG images may be used for profile pictures'}); + this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'}); return; } var formData = new FormData(); formData.append('image', picture, picture.name); + this.setState({loadingPicture: true}); client.uploadProfileImage(formData, function() { @@ -767,8 +766,8 @@ var GeneralTab = React.createClass({ window.location.reload(); }.bind(this), function(err) { - state = this.getInitialState(); - state.server_error = err; + var state = this.getInitialState(); + state.serverError = err; this.setState(state); }.bind(this) ); @@ -777,10 +776,10 @@ var GeneralTab = React.createClass({ this.setState({username: e.target.value}); }, updateFirstName: function(e) { - this.setState({first_name: e.target.value}); + this.setState({firstName: e.target.value}); }, updateLastName: function(e) { - this.setState({last_name: e.target.value}); + this.setState({lastName: e.target.value}); }, updateNickname: function(e) { this.setState({nickname: e.target.value}); @@ -790,17 +789,16 @@ var GeneralTab = React.createClass({ }, updatePicture: function(e) { if (e.target.files && e.target.files[0]) { - this.setState({ picture: e.target.files[0] }); + this.setState({picture: e.target.files[0]}); this.submitActive = true; - this.setState({client_error: null}); - + this.setState({clientError: null}); } else { this.setState({picture: null}); } }, updateSection: function(section) { - this.setState({client_error:''}); + this.setState({clientError: ''}); this.submitActive = false; this.props.updateSection(section); }, @@ -809,7 +807,7 @@ var GeneralTab = React.createClass({ this.value = ''; }); - this.setState(assign({}, this.getInitialState(), {client_error: null, server_error: null, email_error: null})); + this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null})); this.props.updateSection(''); }, componentDidMount: function() { @@ -821,15 +819,24 @@ var GeneralTab = React.createClass({ getInitialState: function() { var user = this.props.user; - return { username: user.username, first_name: user.first_name, last_name: user.last_name, nickname: user.nickname, - email: user.email, picture: null }; + return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, + email: user.email, picture: null, loadingPicture: false}; }, render: function() { var user = this.props.user; - var client_error = this.state.client_error ? this.state.client_error : null; - var server_error = this.state.server_error ? this.state.server_error : null; - var email_error = this.state.email_error ? this.state.email_error : null; + var clientError = null; + if (this.state.clientError) { + clientError = this.state.clientError; + } + var serverError = null; + if (this.state.serverError) { + serverError = this.state.serverError; + } + var emailError = null; + if (this.state.emailError) { + emailError = this.state.emailError; + } var nameSection; var self = this; @@ -840,7 +847,7 @@ var GeneralTab = React.createClass({ <div className='form-group'> <label className='col-sm-5 control-label'>First Name</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.first_name}/> + <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/> </div> </div> ); @@ -849,7 +856,7 @@ var GeneralTab = React.createClass({ <div className='form-group'> <label className='col-sm-5 control-label'>Last Name</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.last_name}/> + <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/> </div> </div> ); @@ -859,8 +866,8 @@ var GeneralTab = React.createClass({ title='Full Name' inputs={inputs} submit={this.submitName} - server_error={server_error} - client_error={client_error} + server_error={serverError} + client_error={clientError} updateSection={function(e) { self.updateSection(''); e.preventDefault(); @@ -868,20 +875,20 @@ var GeneralTab = React.createClass({ /> ); } else { - var full_name = ''; + var fullName = ''; if (user.first_name && user.last_name) { - full_name = user.first_name + ' ' + user.last_name; + fullName = user.first_name + ' ' + user.last_name; } else if (user.first_name) { - full_name = user.first_name; + fullName = user.first_name; } else if (user.last_name) { - full_name = user.last_name; + fullName = user.last_name; } nameSection = ( <SettingItemMin title='Full Name' - describe={full_name} + describe={fullName} updateSection={function() { self.updateSection('name'); }} @@ -891,7 +898,6 @@ var GeneralTab = React.createClass({ var nicknameSection; if (this.props.activeSection === 'nickname') { - inputs.push( <div className='form-group'> <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label> @@ -906,8 +912,8 @@ var GeneralTab = React.createClass({ title='Nickname' inputs={inputs} submit={this.submitNickname} - server_error={server_error} - client_error={client_error} + server_error={serverError} + client_error={clientError} updateSection={function(e) { self.updateSection(''); e.preventDefault(); @@ -930,7 +936,7 @@ var GeneralTab = React.createClass({ if (this.props.activeSection === 'username') { inputs.push( <div className='form-group'> - <label className='col-sm-5 control-label'>{utils.isMobile() ? '': 'Username'}</label> + <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label> <div className='col-sm-7'> <input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/> </div> @@ -942,8 +948,8 @@ var GeneralTab = React.createClass({ title='Username' inputs={inputs} submit={this.submitUsername} - server_error={server_error} - client_error={client_error} + server_error={serverError} + client_error={clientError} updateSection={function(e) { self.updateSection(''); e.preventDefault(); @@ -977,8 +983,8 @@ var GeneralTab = React.createClass({ title='Email' inputs={inputs} submit={this.submitEmail} - server_error={server_error} - client_error={email_error} + server_error={serverError} + client_error={emailError} updateSection={function(e) { self.updateSection(''); e.preventDefault(); @@ -1004,8 +1010,8 @@ var GeneralTab = React.createClass({ title='Profile Picture' submit={this.submitPicture} src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update} - server_error={server_error} - client_error={client_error} + server_error={serverError} + client_error={clientError} updateSection={function(e) { self.updateSection(''); e.preventDefault(); @@ -1013,6 +1019,7 @@ var GeneralTab = React.createClass({ picture={this.state.picture} pictureChange={this.updatePicture} submitActive={this.submitActive} + loadingPicture={this.state.loadingPicture} /> ); } else { diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 2b0976afd..bed0ec556 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -82,7 +82,7 @@ module.exports = { "channel", ], MONTHS: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - MAX_DMS: 10, + MAX_DMS: 20, ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>", MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index 52659521d..78006ff18 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -49,6 +49,12 @@ div.theme { background-color: $primary-color; } +.tooltip { + .tooltip-inner { + word-break: break-word; + } +} + .nopadding { padding: 0; margin: 0; @@ -61,6 +67,10 @@ div.theme { } } +.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { + cursor: auto; +} + .form-group { &.form-group--small { margin-bottom: 10px; diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index ddc5e98bb..65775f01e 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -4,7 +4,8 @@ max-height: 110px; height: 110px; white-space: nowrap; - overflow: auto; + overflow-x: auto; + overflow-y: hidden; .preview-div { display: inline-block; width: 120px; @@ -28,9 +29,9 @@ } } .preview-img { - display: block; - height: auto; - max-width: 100%; + display: block; + height: auto; + max-width: 100%; } .remove-preview { position: absolute; @@ -114,8 +115,7 @@ height: 100px; float: left; margin: 5px 10px 5px 0; - @include display-flex; - display: -ms-flexbox; + display: table; border: 1px solid lightgrey; .post__load { height: 100%; @@ -130,17 +130,23 @@ background-color: #FFF; background-repeat: no-repeat; &.small { - background-position: center; + background-position: center; } &.normal { - background-position: top left; + background-position: top left; } } .post-image__thumbnail { + display: table-cell; + vertical-align: top; width: 50%; height: 100%; + cursor: zoom-in; + cursor: -webkit-zoom-in; } .post-image__details { + display: table-cell; + vertical-align: top; width: 50%; height: 100%; background: white; @@ -165,34 +171,34 @@ } .file-details__container { - @include display-flex; - display: -ms-flexbox; + @include display-flex; + display: -ms-flexbox; - .file-details { - width: 320px; - height: 270px; - padding: 14px; - text-align: left; - vertical-align: top; + .file-details { + width: 320px; + height: 270px; + padding: 14px; + text-align: left; + vertical-align: top; - .file-details__name { - font-size: 16px; - } - .file-details__info { - color: grey; - } + .file-details__name { + font-size: 16px; + } + .file-details__info { + color: grey; } - .file-details__preview { - width: 320px; - height: 270px; - border-right: 1px solid #ddd; - vertical-align: center; + } + .file-details__preview { + width: 320px; + height: 270px; + border-right: 1px solid #ddd; + vertical-align: center; // helper to center the image icon in the preview window .file-details__preview-helper { - height: 100%; - display: inline-block; - vertical-align: middle; + height: 100%; + display: inline-block; + vertical-align: middle; } + } } -} diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index fb37c43eb..da648a170 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -110,6 +110,20 @@ } } } + &.theme--black { + &:hover { + &:before { + background: rgba(white, 0.2); + } + } + } + &.theme--gray { + &:hover { + &:before { + background: rgba(white, 0.1); + } + } + } a { color: #fff; } diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index f359037c5..014f834ed 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -15,10 +15,13 @@ } .remove__member { float: right; - color: #E56565; + color: #999; font-size: 20px; line-height: 0; padding: 6px; + &:hover { + color: #E56565; + } } .modal-dialog { max-width: 95%; @@ -151,10 +154,9 @@ height: 100%; margin: 0 auto; .image-wrapper { - background: #FFF; position: relative; max-width: 90%; - min-height: 50px; + min-height: 100px; min-width: 320px; @include border-radius(3px); display: table; @@ -182,6 +184,7 @@ z-index: 9999; } > a { + background: #FFF; display: table-cell; vertical-align: middle; } diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss index 905907d84..2e78a8728 100644 --- a/web/sass-files/sass/partials/_navbar.scss +++ b/web/sass-files/sass/partials/_navbar.scss @@ -19,6 +19,7 @@ } } .navbar-toggle { + width: 43px; float: left; border-color: transparent; border-radius: 0; diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss index fa1b44841..5008331b4 100644 --- a/web/sass-files/sass/partials/_popover.scss +++ b/web/sass-files/sass/partials/_popover.scss @@ -6,4 +6,11 @@ .user-popover__image { margin: 0 0 10px; @include border-radius(128px); +} + +.user-popover__email { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + display: block; }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index e3f140413..47b2b6bd7 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -229,6 +229,16 @@ } } +@media screen and (max-height: 640px) { + .signup-team__container { + padding: 30px 0; + margin-bottom: 30px; + font-size: 0.9em; + .signup-team__name { + font-size: 2em; + } + } +} @media screen and (max-width: 768px) { .date-separator, .new-separator { &.hovered--after { diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index 4b6ee79a1..3a6f73316 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -54,6 +54,28 @@ margin-bottom: 1em; } + .input-group { + &.input-group--limit { + table-layout: fixed; + width: 100%; + .tooltip-inner { + word-wrap: break-word; + } + .form-control { + text-align: left; + display: table-cell; + width: 100%; + } + .input-group-addon { + text-align: left; + width: 50%; + display: table-cell; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + .inner__content { padding: 0 15px; margin: 30px 0 20px; diff --git a/web/static/config/config.js b/web/static/config/config.js index 0d564b77e..00cae7ab2 100644 --- a/web/static/config/config.js +++ b/web/static/config/config.js @@ -31,6 +31,9 @@ var config = { ReportProblemLink: "/static/help/configure_links.html", HomeLink: "", + // Toggle whether or not users are shown a message about agreeing to the Terms of Service during the signup process + ShowTermsDuringSignup: false, + ThemeColors: ["#2389d7", "#008a17", "#dc4fad", "#ac193d", "#0072c6", "#d24726", "#ff8f32", "#82ba00", "#03b3b2", "#008299", "#4617b4", "#8c0095", "#004b8b", "#004b8b", "#570000", "#380000", "#585858", "#000000"] }; diff --git a/web/templates/channel.html b/web/templates/channel.html index 6325069ee..da6fed97d 100644 --- a/web/templates/channel.html +++ b/web/templates/channel.html @@ -49,7 +49,9 @@ <div id="activity_log_modal"></div> <div id="removed_from_channel_modal"></div> <script> -window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}'); + window.setup_channel_page('{{ .Props.TeamDisplayName }}', '{{ .Props.TeamType }}', '{{ .Props.TeamId }}', '{{ .Props.ChannelName }}', '{{ .Props.ChannelId }}'); + $('body').tooltip( {selector: '[data-toggle=tooltip]'} ); + $('.modal-body').perfectScrollbar(); </script> </body> </html> diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html index b84b8e486..313ed9d5f 100644 --- a/web/templates/signup_team.html +++ b/web/templates/signup_team.html @@ -10,7 +10,7 @@ <div class="signup-team__container"> <img class="signup-team-logo" src="/static/images/logo.png" /> <h1>Mattermost</h1> - <h4 class="color--light">All team communication in one place, searchable and accesible anywhere</h4> + <h4 class="color--light">All team communication in one place, searchable and accessible anywhere</h4> <div id="signup-team"></div> </div> </div> |