diff options
Diffstat (limited to 'web')
28 files changed, 372 insertions, 202 deletions
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 7cce807a9..90f139e8b 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -102,8 +102,9 @@ module.exports = React.createClass({ <div className="modal-content"> <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" id="myModalLabel">Active Devices</h4> + <h4 className="modal-title" id="myModalLabel">Active Sessions</h4> </div> + <p className="session-help-text">Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the "Logout" button below to end a session.</p> <div ref="modalBody" className="modal-body"> <form role="form"> { activityList } diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 87895588e..327520210 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -32,7 +32,7 @@ module.exports = React.createClass({ post.message = this.state.messageText; // if this is a reply, trim off any carets from the beginning of a message - if (this.state.rootId && post.message.startsWith("^")) { + if (this.state.rootId && post.message[0] === "^") { post.message = post.message.replace(/^\^+\s*/g, ""); } diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 11970bc2b..f0cb809af 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -98,7 +98,7 @@ module.exports = React.createClass({ : "" } </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button> </div> </div> diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 05918650b..fe0a47777 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -103,13 +103,9 @@ module.exports = React.createClass({ return ( <div className="signup-team__container"> - <div> - <span className="signup-team__name">{ teamDisplayName }</span> - <br/> - <span className="signup-team__subdomain">/{ teamName }/</span> - <br/> - <br/> - </div> + <h5 className="margin--less">Sign in to:</h5> + <h2 className="signup-team__name">{ teamDisplayName }</h2> + <h2 className="signup-team__subdomain">on { config.SiteName }</h2> <form onSubmit={this.handleSubmit}> <div className={server_error ? 'form-group has-error' : 'form-group'}> { server_error } @@ -124,13 +120,13 @@ module.exports = React.createClass({ <button type="submit" className="btn btn-primary">Sign in</button> </div> { login_message } - <div className="form-group form-group--small"> + <div className="form-group margin--extra form-group--small"> <span><a href="/find_team">{"Find other " + strings.TeamPlural}</a></span> </div> <div className="form-group"> <a href={"/" + teamName + "/reset_password"}>I forgot my password</a> </div> - <div className="external-link"> + <div className="margin--extra"> <span>{"Want to create your own " + strings.Team + "?"} <a href="/" className="signup-team-login">Sign up now</a></span> </div> </form> diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index a71fa9e8f..641ffeef2 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -126,7 +126,7 @@ module.exports = React.createClass({ } else if (i < Constants.MAX_DISPLAY_FILES) { postFiles.push( <div className="post-image__column custom-file" key={fileInfo.name+i}> - <a href={fileInfo.path+"."+fileInfo.ext} download={fileInfo.name+"."+fileInfo.ext}> + <a href={fileInfo.path + (fileInfo.ext ? "." + fileInfo.ext : "")} download={fileInfo.name + (fileInfo.ext ? "." + fileInfo.ext : "")}> <div className={"file-icon "+utils.getIconClassName(type)}/> </a> </div> diff --git a/web/react/components/removed_from_channel_modal.jsx b/web/react/components/removed_from_channel_modal.jsx new file mode 100644 index 000000000..a8889a92a --- /dev/null +++ b/web/react/components/removed_from_channel_modal.jsx @@ -0,0 +1,64 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var ChannelStore = require('../stores/channel_store.jsx'); +var UserStore = require('../stores/user_store.jsx'); +var BrowserStore = require('../stores/browser_store.jsx') +var utils = require('../utils/utils.jsx'); + +module.exports = React.createClass({ + handleShow: function() { + var newState = {}; + if(BrowserStore.getItem("channel-removed-state")) { + newState = BrowserStore.getItem("channel-removed-state"); + BrowserStore.removeItem("channel-removed-state"); + } + + this.setState(newState); + }, + handleClose: function() { + var townSquare = ChannelStore.getByName("town-square"); + utils.switchChannel(townSquare); + + this.setState({channelName: "", remover: ""}) + }, + componentDidMount: function() { + $(this.getDOMNode()).on('show.bs.modal',this.handleShow); + $(this.getDOMNode()).on('hidden.bs.modal',this.handleClose); + }, + componentWillUnmount: function() { + $(this.getDOMNode()).off('show.bs.modal',this.handleShow); + $(this.getDOMNode()).off('hidden.bs.modal',this.handleClose); + }, + getInitialState: function() { + return {channelName: "", remover: ""} + }, + render: function() { + var currentUser = UserStore.getCurrentUser(); + var channelName = this.state.channelName ? this.state.channelName : "the channel" + var remover = this.state.remover ? this.state.remover : "Someone" + + if (currentUser != null) { + return ( + <div className="modal fade" ref="modal" id="removed_from_channel" tabIndex="-1" role="dialog" aria-hidden="true"> + <div className="modal-dialog"> + <div className="modal-content"> + <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">Removed from {channelName}</h4> + </div> + <div className="modal-body"> + <p>{remover} removed you from {channelName}</p> + </div> + <div className="modal-footer"> + <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button> + </div> + </div> + </div> + </div> + ); + } else { + return <div/>; + } + } +});
\ No newline at end of file diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 3cf67e410..5b8d6c542 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -7,6 +7,7 @@ var AsyncClient = require('../utils/async_client.jsx'); var SocketStore = require('../stores/socket_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); +var BrowserStore = require('../stores/browser_store.jsx') var utils = require('../utils/utils.jsx'); var SidebarHeader = require('./sidebar_header.jsx'); var SearchBox = require('./search_bar.jsx'); @@ -197,6 +198,19 @@ module.exports = React.createClass({ if (UserStore.getCurrentId() === msg.user_id) { AsyncClient.getChannels(true); } + } else if(msg.action === "user_removed") { + if(msg.user_id === UserStore.getCurrentId()) { + AsyncClient.getChannels(true); + + if(msg.props.channel_id === ChannelStore.getCurrentId() && $('#removed_from_channel').length > 0) { + var sentState = {}; + sentState.channelName = ChannelStore.getCurrent().display_name; + sentState.remover = UserStore.getProfile(msg.props.remover).username; + + BrowserStore.setItem('channel-removed-state',sentState); + $('#removed_from_channel').modal('show'); + } + } } }, updateTitle: function() { diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index 362f79163..edd48e0b9 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -20,21 +20,12 @@ module.exports = React.createClass({ state.email_error = ""; } - team.display_name = this.refs.name.getDOMNode().value.trim(); - if (!team.display_name) { - state.name_error = "This field is required"; - state.inValid = true; - } - else { - state.name_error = ""; - } - if (state.inValid) { this.setState(state); return; } - client.signupTeam(team.email, team.display_name, + client.signupTeam(team.email, function(data) { if (data["follow_link"]) { window.location.href = data["follow_link"]; @@ -55,7 +46,6 @@ module.exports = React.createClass({ render: function() { 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 server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null; return ( @@ -64,15 +54,11 @@ module.exports = React.createClass({ <input autoFocus={true} type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" /> { email_error } </div> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <input type="text" ref="name" className="form-control" placeholder={utils.toTitleCase(strings.Company) + " Name"} maxLength="64" /> - { name_error } - </div> { server_error } <div className="form-group"> - <button className="btn btn-md btn-primary" type="submit">Sign up for Free</button> + <button className="btn btn-md btn-primary" type="submit">Sign up</button> </div> - <div className="form-group form-group--small"> + <div className="form-group margin--extra-2x"> <span><a href="/find_team">{"Find my " + strings.Team}</a></span> </div> </form> diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 11882c57f..21f9edef1 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -42,11 +42,15 @@ WelcomePage = React.createClass({ state.email_error = ""; } - client.signupTeam(email, this.props.state.team.name, + client.signupTeam(email, function(data) { - this.props.state.wizard = "finished"; - this.props.updateParent(this.props.state); - window.location.href = "/signup_team_confirm/?email=" + encodeURI(email); + if (data["follow_link"]) { + window.location.href = data["follow_link"]; + } else { + this.props.state.wizard = "finished"; + this.props.updateParent(this.props.state); + window.location.href = "/signup_team_confirm/?email=" + encodeURIComponent(team.email); + } }.bind(this), function(err) { this.state.server_error = err.message; @@ -80,19 +84,25 @@ WelcomePage = React.createClass({ <div> <p> <img className="signup-team-logo" src="/static/images/logo.png" /> - <h2>Welcome!</h2> - <h3>{"Let's set up your " + strings.Team + " on " + config.SiteName + "."}</h3> + <h3 className="sub-heading">Welcome to:</h3> + <h1 className="margin--top-none">{config.SiteName}</h1> </p> + <p className="margin--less">Let's setup your new team</p> <p> Please confirm your email address:<br /> - <span className="black">{ this.props.state.team.email }</span><br /> + <div className="inner__content"> + <div className="block--gray">{ this.props.state.team.email }</div> + </div> + </p> + <p className="margin--extra color--light"> + Your account will administer the new team site. <br /> + You can add other administrators later. </p> <div className="form-group"> <button className="btn-primary btn form-group" type="submit" onClick={this.submitNext}><i className="glyphicon glyphicon-ok"></i>Yes, this address is correct</button> { storage_error } </div> <hr /> - <p>If this is not correct, you can switch to a different email. We'll send you a new invite right away.</p> <div className={ this.state.use_diff ? "" : "hidden" }> <div className={ email_error ? "form-group has-error" : "form-group" }> <div className="row"> @@ -105,7 +115,7 @@ WelcomePage = React.createClass({ { server_error } <button className="btn btn-md btn-primary" type="button" onClick={this.handleDiffSubmit} type="submit">Use this instead</button> </div> - <button type="button" onClick={this.handleDiffEmail} className={ this.state.use_diff ? "btn-default btn hidden" : "btn-default btn" }>Use a different address</button> + <a href="#" onClick={this.handleDiffEmail} className={ this.state.use_diff ? "hidden" : "" }>Use a different email</a> </div> ); } @@ -128,6 +138,7 @@ TeamDisplayNamePage = React.createClass({ this.props.state.wizard = "team_url"; this.props.state.team.display_name = display_name; + this.props.state.team.name = utils.cleanUpUrlable(display_name); this.props.updateParent(this.props.state); }, getInitialState: function() { @@ -158,9 +169,11 @@ TeamDisplayNamePage = React.createClass({ </div> { name_error } </div> - <p>{"Your " + strings.Team + " name shows in menus and headings. It may include the name of your " + strings.Company + ", but it's not required."}</p> - <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> - <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> + <div>{"Name your " + strings.Team + " in any language. Your " + strings.Team + " name shows in menus and headings."}</div> + <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> + <div className="margin--extra"> + <a href="#" onClick={this.submitBack}>Back to previous step</a> + </div> </form> </div> ); @@ -255,10 +268,16 @@ TeamURLPage = React.createClass({ </div> { name_error } </div> - <p className="black">{"Pick something short and memorable for your " + strings.Team + "'s web address."}</p> - <p>{"Your " + strings.Team + " URL can only contain lowercase letters, numbers and dashes. Also, it needs to start with a letter and cannot end in a dash."}</p> - <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> - <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> + <p>{"Choose the web address of your new " + strings.Team + ":"}</p> + <ul className="color--light"> + <li>Short and memorable is best</li> + <li>Use lower case letters, numbers and dashes</li> + <li>Must start with a letter and can't end in a dash</li> + </ul> + <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> + <div className="margin--extra"> + <a href="#" onClick={this.submitBack}>Back to previous step</a> + </div> </form> </div> ); @@ -461,14 +480,16 @@ SendInivtesPage = React.createClass({ return ( <div> <form> - <img className="signup-team-logo" src="/static/images/logo.png" /> - <h2>Send Invitations</h2> - { emails } - <div className="form-group"><button type="button" className="btn-default btn" onClick={this.submitAddInvite}>Add Invitation</button></div> - <div className="form btn-default-group"><button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div> - </form> - <p>{"If you'd prefer, you can send invitations after you finish setting up the "+ strings.Team + "."}</p> - <div><a href="#" onClick={this.submitSkip}>Skip this step</a></div> + <img className="signup-team-logo" src="/static/images/logo.png" /> + <h2>{"Invite " + utils.toTitleCase(strings.Team) + " Members"}</h2> + { emails } + <div className="form-group text-right"><a href="#" onClick={this.submitAddInvite}>Add Invitation</a></div> + <div className="form-group"><button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div> + </form> + <p className="color--light">{"if you prefer, you can invite " + strings.Team + " members later"}<br /> and <a href="#" onClick={this.submitSkip}>skip this step</a> for now.</p> + <div className="margin--extra"> + <a href="#" onClick={this.submitBack}>Back to previous step</a> + </div> </div> ); } @@ -512,19 +533,24 @@ UsernamePage = React.createClass({ <div> <form> <img className="signup-team-logo" src="/static/images/logo.png" /> - <h2>Choose a username</h2> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-9"> - <input autoFocus={true} type="text" ref="name" className="form-control" placeholder="" defaultValue={this.props.state.user.username} maxLength="128" /> + <h2 className="margin--less">Your username</h2> + <h5 className="color--light">{"Select a memorable username that makes it easy for " + strings.Team + "mates to identify you:"}</h5> + <div className="inner__content margin--extra"> + <div className={ name_error ? "form-group has-error" : "form-group" }> + <div className="row"> + <div className="col-sm-11"> + <h5><strong>Choose your username</strong></h5> + <input autoFocus={true} type="text" ref="name" className="form-control" placeholder="" defaultValue={this.props.state.user.username} maxLength="128" /> + <div className="color--light form__hint">Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div> + </div> + </div> + { name_error } </div> </div> - { name_error } + <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> + <div className="margin--extra"> + <a href="#" onClick={this.submitBack}>Back to previous step</a> </div> - <p>{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}</p> - <p>It can be made of lowercase letters and numbers.</p> - <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> - <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> </form> </div> ); @@ -542,15 +568,15 @@ PasswordPage = React.createClass({ var password = this.refs.password.getDOMNode().value.trim(); if (!password || password.length < 5) { - this.setState({name_error: "Please enter at least 5 characters"}); + this.setState({password_error: "Please enter at least 5 characters"}); return; } - this.setState({name_error: ""}); + this.setState({password_error: null, server_error: null}); $('#finish-button').button('loading'); var teamSignup = JSON.parse(JSON.stringify(this.props.state)); teamSignup.user.password = password; - teamSignup.user.allow_marketing = this.refs.email_service.getDOMNode().checked; + teamSignup.user.allow_marketing = true; delete teamSignup.wizard; var ctl = this; @@ -582,7 +608,7 @@ PasswordPage = React.createClass({ }, 5000); }.bind(this), function(err) { - this.setState({name_error: err.message}); + this.setState({server_error: err.message}); $('#sign-up-button').button('reset'); }.bind(this) ); @@ -594,30 +620,37 @@ PasswordPage = React.createClass({ client.track('signup', 'signup_team_07_password'); - 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 ? <label className="control-label">{ this.state.server_error }</label> : null; return ( <div> <form> <img className="signup-team-logo" src="/static/images/logo.png" /> - <h2>Choose a password</h2> - <p>You'll use your email address ({this.props.state.team.email}) and password to log into {config.SiteName}.</p> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-9"> - <input autoFocus={true} type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> + <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> + <div className="block--gray form-group">{this.props.state.team.email}</div> + <div className={ password_error ? "form-group has-error" : "form-group" }> + <div className="row"> + <div className="col-sm-11"> + <h5><strong>Choose your password</strong></h5> + <input autoFocus={true} type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> + <div className="color--light form__hint">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.</div> + </div> + </div> + { password_error } + { server_error } </div> - </div> - { name_error } - </div> - <div className="form-group checkbox"> - <label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service.</label> </div> <div className="form-group"> - <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> - <button type="submit" className="btn-primary btn" id="finish-button" data-loading-text={"<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button> + <button type="submit" className="btn btn-primary margin--extra" id="finish-button" data-loading-text={"<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button> </div> <p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p> + <div className="margin--extra"> + <a href="#" onClick={this.submitBack}>Back to previous step</a> + </div> </form> </div> ); @@ -640,9 +673,6 @@ module.exports = React.createClass({ props.wizard = "welcome"; props.team = {}; props.team.email = this.props.email; - props.team.display_name = this.props.name; - props.team.company_name = this.props.name; - props.team.name = utils.cleanUpUrlable(this.props.name); props.team.allowed_domains = ""; props.invites = []; props.invites.push(""); diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index bbf1f670c..670aab943 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -40,7 +40,7 @@ module.exports = React.createClass({ this.setState({name_error: "", email_error: "", password_error: "", server_error: ""}); - this.state.user.allow_marketing = this.refs.email_service.getDOMNode().checked; + this.state.user.allow_marketing = true; client.createUser(this.state.user, this.state.data, this.state.hash, function(data) { @@ -104,8 +104,8 @@ module.exports = React.createClass({ var yourEmailIs = this.state.user.email == "" ? "" : <span>Your email address is { this.state.user.email }. </span> var email = ( - <div className={ this.state.original_email == "" ? "" : "hidden"} > - <label className="control-label">Email</label> + <div className={ this.state.original_email == "" ? "margin--extra" : "hidden"} > + <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" /> { email_error } @@ -124,29 +124,33 @@ module.exports = React.createClass({ return ( <div> <img className="signup-team-logo" src="/static/images/logo.png" /> - <h3 className="text-center extra-margin">Signup to { config.SiteName }</h3> - <div className="form-group form-group--small"> - <span></span> - </div> + <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 } - <label className="control-label">Username</label> - <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">Your username can be made of lowercase letters and numbers.</p> - <p className="form__hint">{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others"}</p> - </div> - { email } - <label className="control-label">Password</label> - <div className={ password_error ? "form-group has-error" : "form-group" }> - <input type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> - { password_error } - </div> - <p className={ this.state.original_email == "" ? "hidden" : ""}>{ yourEmailIs } You’ll use this address to sign in to {config.SiteName}.</p> - <div className="checkbox"><label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service. </label></div> - <p><button onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p> + <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> + </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 onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p> { server_error } - <p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p> + <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> </div> ); } diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index ad50b7920..bbd1f84b6 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -36,7 +36,6 @@ module.exports = React.createClass({ this.resize(); this.processMentions(); - this.updateTextdiv(); }, componentWillUnmount: function() { PostStore.removeAddMentionListener(this._onChange); @@ -87,7 +86,6 @@ module.exports = React.createClass({ this.processMentions(); this.doProcessMentions = false; } - this.updateTextdiv(); this.resize(); }, componentWillReceiveProps: function(nextProps) { @@ -117,17 +115,6 @@ module.exports = React.createClass({ }); }, 1); }, - updateTextdiv: function() { - var html = utils.insertHtmlEntities(this.refs.message.getDOMNode().value); - for (var k in this.mentions) { - var m = this.mentions[k]; - var re = new RegExp('( |^)@' + m + '( |$|\n)', 'm'); - html = html.replace(re, '$1<span class="mention">@'+m+'</span>$2'); - } - var re2 = new RegExp('(^$)(?![.\n])', 'gm'); - html = html.replace(re2, '<br/><br/>'); - $(this.refs.textdiv.getDOMNode()).html(html); - }, handleChange: function() { this.props.onUserInput(this.refs.message.getDOMNode().value); this.resize(); @@ -181,7 +168,7 @@ module.exports = React.createClass({ } }, processMentions: function() { - /* First, find all the possible mentions, highlight them in the HTML and add + /* First, find all the possible mentions and add them all to a list of mentions */ var text = utils.insertHtmlEntities(this.refs.message.getDOMNode().value); @@ -192,9 +179,7 @@ module.exports = React.createClass({ var matches = text.match(re1); if (!matches) { - $(this.refs.textdiv.getDOMNode()).text(text); this.updateMentionTab(null, []); - this.mentions = []; return; } @@ -207,7 +192,7 @@ module.exports = React.createClass({ } /* Figure out what the user is currently typing. If it's a mention then we don't - want to highlight it and add it to the mention list yet, so we remove it if + want to add it to the mention list yet, so we remove it if there is only one occurence of that mention so far. */ var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); @@ -225,14 +210,13 @@ module.exports = React.createClass({ typingMention = text.substring(atIndex+1, caret); } - var re3 = new RegExp('@' + typingMention + '( |$|\n)', 'g'); + var re2 = new RegExp('@' + typingMention + '( |$|\n)', 'g'); - if ((text.match(re3) || []).length === 1 && mentions.indexOf(typingMention) !== -1) { + if ((text.match(re2) || []).length === 1 && mentions.indexOf(typingMention) !== -1) { mentions.splice(mentions.indexOf(typingMention), 1); } this.updateMentionTab(null, mentions); - this.mentions = mentions; }, checkForNewMention: function(text) { var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); @@ -287,15 +271,9 @@ module.exports = React.createClass({ elm.value = cmd; this.handleChange(); }, - scroll: function() { - var e = this.refs.message.getDOMNode(); - var d = this.refs.textdiv.getDOMNode(); - $(d).scrollTop($(e).scrollTop()); - }, resize: function() { var e = this.refs.message.getDOMNode(); var w = this.refs.wrapper.getDOMNode(); - var d = this.refs.textdiv.getDOMNode(); var lht = parseInt($(e).css('lineHeight'),10); var lines = e.scrollHeight / lht; @@ -303,15 +281,11 @@ module.exports = React.createClass({ if (e.scrollHeight - mod < 167) { $(e).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight - mod); - $(d).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight - mod); $(w).css({'height':'auto'}).height(e.scrollHeight+2); } else { $(e).css({'height':'auto','overflow-y':'scroll'}).height(167); - $(d).css({'height':'auto','overflow-y':'scroll'}).height(167); $(w).css({'height':'auto'}).height(167); } - - $(d).scrollTop($(e).scrollTop()); }, handleFocus: function() { var elm = this.refs.message.getDOMNode(); @@ -332,8 +306,7 @@ module.exports = React.createClass({ return ( <div ref="wrapper" className="textarea-wrapper"> <CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} /> - <div className="form-control textarea-div" ref="textdiv"/> - <textarea id={this.props.id} ref="message" className={"form-control custom-textarea " + this.state.connection} spellCheck="true" autoComplete="off" autoCorrect="off" rows="1" placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onScroll={this.scroll} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> + <textarea id={this.props.id} ref="message" className={"form-control custom-textarea " + this.state.connection} spellCheck="true" autoComplete="off" autoCorrect="off" rows="1" placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> </div> ); } diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index b940a3c6f..e1ae6da52 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -617,7 +617,7 @@ var SecurityTab = React.createClass({ <br></br> <a data-toggle="modal" className="security-links theme" data-target="#access-history" href="#" onClick={this.handleHistoryOpen}><i className="fa fa-clock-o"></i>View Access History</a> <b> </b> - <a data-toggle="modal" className="security-links theme" data-target="#activity-log" href="#" onClick={this.handleDevicesOpen}><i className="fa fa-globe"></i>View and Logout of Active Devices</a> + <a data-toggle="modal" className="security-links theme" data-target="#activity-log" href="#" onClick={this.handleDevicesOpen}><i className="fa fa-globe"></i>View and Logout of Active Sessions</a> </div> </div> ); @@ -721,13 +721,15 @@ var GeneralTab = React.createClass({ if(!this.submitActive) return; - if(this.state.picture.type !== "image/jpeg") { - this.setState({client_error: "Only JPG images may be used for profile pictures"}); + 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"}); return; } formData = new FormData(); - formData.append('image', this.state.picture, this.state.picture.name); + formData.append('image', picture, picture.name); client.uploadProfileImage(formData, function(data) { diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index cc78df120..90d90b29f 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -34,6 +34,7 @@ var MentionList = require('../components/mention_list.jsx'); var ChannelInfoModal = require('../components/channel_info_modal.jsx'); var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); +var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx') var Constants = require('../utils/constants.jsx'); @@ -217,4 +218,9 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann document.getElementById('activity_log_modal') ); + React.render( + <RemovedFromChannelModal />, + document.getElementById('removed_from_channel_modal') + ); + }; diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx index 346f2ab5a..71806c2ea 100644 --- a/web/react/pages/signup_team_complete.jsx +++ b/web/react/pages/signup_team_complete.jsx @@ -3,9 +3,9 @@ var SignupTeamComplete =require('../components/signup_team_complete.jsx'); -global.window.setup_signup_team_complete_page = function(email, name, data, hash) { +global.window.setup_signup_team_complete_page = function(email, data, hash) { React.render( - <SignupTeamComplete name={name} email={email} hash={hash} data={data}/>, + <SignupTeamComplete email={email} hash={hash} data={data}/>, document.getElementById('signup-team-complete') ); }; diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx index 60c3a609a..8f9be1f94 100644 --- a/web/react/pages/signup_user_complete.jsx +++ b/web/react/pages/signup_user_complete.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var SignupUserComplete =require('../components/signup_user_complete.jsx'); +var SignupUserComplete = require('../components/signup_user_complete.jsx'); global.window.setup_signup_user_complete_page = function(email, name, ui_name, id, data, hash, auth_services) { React.render( diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 1c31dc5ed..b8eda0075 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -341,13 +341,13 @@ module.exports.updateTeamDisplayName = function(data, success, error) { module.exports.track('api', 'api_teams_update_name'); }; -module.exports.signupTeam = function(email, display_name, success, error) { +module.exports.signupTeam = function(email, success, error) { $.ajax({ url: "/api/v1/teams/signup", dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify({email: email, display_name: display_name}), + data: JSON.stringify({email: email}), success: success, error: function(xhr, status, err) { e = handleError("singupTeam", xhr, status, err); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index cd27afe61..8a4d92b85 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -544,10 +544,13 @@ module.exports.getIconClassName = function(fileType) { module.exports.splitFileLocation = function(fileLocation) { var fileSplit = fileLocation.split('.'); - if (fileSplit.length < 2) return {}; - var ext = fileSplit[fileSplit.length-1]; - fileSplit.splice(fileSplit.length-1,1) + var ext = ""; + if (fileSplit.length > 1) { + ext = fileSplit[fileSplit.length - 1]; + fileSplit.splice(fileSplit.length - 1, 1); + } + var filePath = fileSplit.join('.'); var filename = filePath.split('/')[filePath.split('/').length-1]; diff --git a/web/sass-files/sass/partials/_activity-log.scss b/web/sass-files/sass/partials/_activity-log.scss index 36eb48750..3f0c3090d 100644 --- a/web/sass-files/sass/partials/_activity-log.scss +++ b/web/sass-files/sass/partials/_activity-log.scss @@ -28,4 +28,7 @@ .report__info { color: #999; } +} +.session-help-text { + padding: 20px 20px 5px 20px; }
\ 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 2d78cf242..719934638 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -426,13 +426,15 @@ } body { &.white { - .row.content { + .inner__wrap { + >.row.content { margin-bottom: -185px; } + } } } .footer, .footer-pane, .footer-push { - height: auto; + height: 187px; } .footer-pane { .footer-link { diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index 826394a10..1a0c55f39 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -8,67 +8,109 @@ padding: 100px 0px 50px 0px; max-width: 380px; margin: 0 auto; - font-size: 1.1em; position: relative; + &.padding--less { + padding-top: 50px; + } + h1, h2, h3, h4, h5, h6, p { line-height: 1.3; } + + h1 { + font-weight: 600; + } + h2 { font-weight: 600; - margin-bottom: 0.5em; + margin-bottom: 0.8em; letter-spacing: -0.5px; - font-size: em(28px); + font-size: em(30px); } + h3 { font-weight: 600; margin: 0 0 1.3em 0; - font-size: 1.4em; - &.extra-margin { - margin-bottom: 2.5em; - } + font-size: 1.5em; } + h4 { - font-size: em(18px); + font-size: em(20px); font-weight: 600; margin-bottom: 1em; - &.text--light { - font-weight: 300; - color: #999; - } } + + h5 { + font-size: em(16px); + } + + hr { + margin: 2em 0; + } + p { color: #555; line-height: 1.5; margin-bottom: 1em; - .black, &.black { - color: #000; - } } + + .inner__content { + padding: 0 15px; + margin: 30px 0 20px; + } + + .block--gray { + background: #f2f2f2; + display: inline-block; + padding: 0.85em 1.2em; + font-weight: 600; + @include border-radius(3px); + } + form { margin-bottom: 0.8em; } + .form__hint { font-size: 0.95em; color: #999; margin: 10px 0; } - .external-link { - position: absolute; - bottom: 0; - left: 0; + + .signup-team-confirm__container { + padding: 100px 0px 100px 0px; + } + + .signup-team-logo { + display: none; + width: 210px; + margin: 0 0 2em 0; + } + + .signup-team-login { + padding-bottom: 10px; + font-weight: 700; } + .signup-team__name { + margin: 0.5em 0 0; font-size: 2.2em; font-weight: 600; - text-transform: uppercase; + padding-left: 1rem; } + .signup-team__subdomain { + margin: 0.2em 0 1.2em; font-size: 1.5em; - padding-left: 1em; + padding-left: 1rem; + font-weight: 300; + text-transform: uppercase; } + .form-control { height: em(38px); } + .or__container { height: 1px; background: #dddddd; @@ -78,16 +120,23 @@ width: 33px; top: -10px; position: relative; + font-size: em(16px); line-height: 20px; font-weight: 600; background: #fff; display: inline-block; } } + + ul { + margin-bottom: 0; + padding-left: 18px; + } + .btn { padding: em(7px) em(15px); font-weight: 600; - font-size: em(13px); + margin-right: 5px; &.btn-custom-login { display: block; min-width: 200px; @@ -121,24 +170,23 @@ } .glyphicon { &.glyphicon-ok, &.glyphicon-refresh { - margin-right: 0.3em; - left: -5px; + margin-right: 0.5em; + left: -2px; font-size: 0.9em; } &.glyphicon-chevron-right { - margin-left: 0.3em; font-size: 0.8em; right: -2px; top: 0px; } &.glyphicon-chevron-left { - margin-right: 0.3em; font-size: 0.8em; left: -2px; top: 0px; } } } + .has-error { .control-label { background: #f2f2f2; @@ -150,13 +198,14 @@ color: #999; width: 100%; &:before { - @extend .fa; - content: "\f071"; - margin-right: 4px; - color: #aaa; + @extend .fa; + content: "\f071"; + margin-right: 4px; + color: #aaa; } } } + .reset-form { @include border-radius(3px); position: relative; @@ -165,19 +214,49 @@ color: inherit; } } -} -.signup-team-confirm__container { - padding: 100px 0px 100px 0px; -} + // Modifier Styles + h1, h2, h3, h4, h5, h6 { + &.margin--top-none { + margin-top: 0; + } + &.margin--bottom-none { + margin-bottom: 0; + } + &.margin--less { + margin-bottom: 0.3em; + } + &.sub-heading { + font-weight: 400; + margin-bottom: 0; + } + &.color--light { + font-weight: 300; + } + } -.signup-team-logo { - display: none; - width: 210px; - margin: 0 0 2em 0; -} + p { + &.margin--extra { + margin-bottom: 1.5em; + } + &.margin--less { + margin-bottom: 0.3em; + } + .black, &.black { + color: #000; + } + } + + .color--light { + color: #777; + } + + .margin--extra { + margin-top: 3em; + } + + .margin--extra-2x { + margin-top: 6em; + } -.signup-team-login { - padding-bottom: 10px; - font-weight: 700; } diff --git a/web/static/images/Bladekick-logodark.png b/web/static/images/Bladekick-logodark.png Binary files differindex 8f1268fdd..c16978ba8 100644 --- a/web/static/images/Bladekick-logodark.png +++ b/web/static/images/Bladekick-logodark.png diff --git a/web/static/images/Mattermost-logodark.png b/web/static/images/Mattermost-logodark.png Binary files differindex 8f1268fdd..c16978ba8 100644 --- a/web/static/images/Mattermost-logodark.png +++ b/web/static/images/Mattermost-logodark.png diff --git a/web/templates/channel.html b/web/templates/channel.html index 8e856032d..6325069ee 100644 --- a/web/templates/channel.html +++ b/web/templates/channel.html @@ -47,6 +47,7 @@ <div id="channel_info_modal"></div> <div id="access_history_modal"></div> <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 }}'); </script> diff --git a/web/templates/head.html b/web/templates/head.html index d14340998..7a7d4fe8e 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -36,6 +36,13 @@ <script type="text/javascript" src="https://cloudfront.loggly.com/js/loggly.tracker.js" async></script> <script id="config" type="text/javascript" src="/static/config/config.js"></script> + <style id="antiClickjack">body{display:none !important;}</style> + <script type="text/javascript"> + if (self === top) { + var blocker = document.getElementById("antiClickjack"); + blocker.parentNode.removeChild(blocker); + } + </script> <script> if (config == null) { config = {}; diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html index b86590589..b84b8e486 100644 --- a/web/templates/signup_team.html +++ b/web/templates/signup_team.html @@ -9,8 +9,8 @@ <div class="col-sm-12"> <div class="signup-team__container"> <img class="signup-team-logo" src="/static/images/logo.png" /> - <h2>All team communication in one place, searchable and accessible anywhere</h2> - <h4 class="text--light">{{ .SiteName }} is free for an unlimited time, for unlimited users </h4 class="text--light"> + <h1>Mattermost</h1> + <h4 class="color--light">All team communication in one place, searchable and accesible anywhere</h4> <div id="signup-team"></div> </div> </div> diff --git a/web/templates/signup_team_complete.html b/web/templates/signup_team_complete.html index 674e54ee2..041889435 100644 --- a/web/templates/signup_team_complete.html +++ b/web/templates/signup_team_complete.html @@ -19,7 +19,7 @@ </div> </div> <script> -window.setup_signup_team_complete_page('{{.Props.Email}}', '{{.Props.DisplayName}}', '{{.Props.Data}}', '{{.Props.Hash}}'); +window.setup_signup_team_complete_page('{{.Props.Email}}', '{{.Props.Data}}', '{{.Props.Hash}}'); </script> </body> </html> diff --git a/web/templates/signup_user_complete.html b/web/templates/signup_user_complete.html index 176ca77b1..e9f6bafcf 100644 --- a/web/templates/signup_user_complete.html +++ b/web/templates/signup_user_complete.html @@ -7,7 +7,7 @@ <div class="inner__wrap"> <div class="row content"> <div class="col-sm-12"> - <div class="signup-team__container"> + <div class="signup-team__container padding--less"> <div id="signup-user-complete"></div> </div> </div> diff --git a/web/web.go b/web/web.go index 1d59ef946..68e2a5226 100644 --- a/web/web.go +++ b/web/web.go @@ -219,7 +219,6 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request) page := NewHtmlTemplatePage("signup_team_complete", "Complete Team Sign Up") page.Props["Email"] = props["email"] - page.Props["DisplayName"] = props["display_name"] page.Props["Data"] = data page.Props["Hash"] = hash page.Render(c, w) |