Этот коммит содержится в:
Asaad Mahmood
2015-07-29 19:29:41 +05:00
родитель ac8706b9f7 54265271fa
Коммит 5f2aa88a08
26 изменённых файлов: 599 добавлений и 178 удалений

Просмотреть файл

@@ -5,6 +5,7 @@ GOFLAGS ?= $(GOFLAGS:)
BUILD_NUMBER ?= $(BUILD_NUMBER:)
GO=$(GOPATH)/bin/godep go
ESLINT=web/react/node_modules/eslint/bin/eslint.js
ifeq ($(BUILD_NUMBER),)
BUILD_NUMBER := dev
@@ -62,6 +63,14 @@ install:
@cd web/react/ && npm install
check: install
@echo Running ESLint...
@$(ESLINT) web/react/components/*
@$(ESLINT) web/react/dispatcher/*
@$(ESLINT) web/react/pages/*
@$(ESLINT) web/react/stores/*
@$(ESLINT) web/react/utils/*
test: install
@mkdir -p logs
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1

167
STYLE-GUIDE.md Обычный файл
Просмотреть файл

@@ -0,0 +1,167 @@
# Mattermost Style Guide
1. [GO](#go)
2. [Javascript](#javascript)
3. [React-JSX](#react-jsx)
## Go
All go code must follow the golang official [Style Guide](https://golang.org/doc/effective_go.html)
In addition all code must be run though the official go formatter tool [gofmt](https://golang.org/cmd/gofmt/)
## Javascript
Part of the build process is running ESLint. ESLint is the final authority on all style issues. PRs will not be accepted unless there are no errors or warnings running ESLint. The ESLint configuration file can be found in: [web/react/.eslintrc](https://github.com/mattermost/platform/blob/master/web/react/.eslintrc.json)
Instructions on how to use ESLint with your favourite editor can be found here: [http://eslint.org/docs/user-guide/integrations](http://eslint.org/docs/user-guide/integrations)
The following is an abridged version of the [Airbnb Javascript Style Guide](https://github.com/airbnb/javascript/blob/master/README.md#airbnb-javascript-style-guide-), with modifications. Anything that is unclear here follow that guide. If there is a conflict, follow what is said below.
### Whitespace
- Indentation is four spaces
- Use a space before the leading brace
- Use one space between the comma and the next argument in a bracketed list. No other space.
- Use whitespace to make code more readable.
- Do not use more than one newline to separate code blocks.
- Do not use a newline as the first line of a function
```javascript
// Correct
function myFunction(parm1, parm2) {
stuff...;
morestuff;
}
// Incorrect
function myFunction ( parm1, parm2 ){
stuff...;
morestuff;
}
```
### Semicolons
- You must use them always
```javascript
// Correct
var x = 1;
// Incorrect
var x = 1
```
### Variables
- Declarations must always use var, let or const.
- Prefer let or const over var.
- camelCase for all variable names.
```javascript
// Correct
let myVariable = 4;
// OK
var myVariable = 4;
// Incorrect
myVariable = 4;
var my_variable = 4;
```
### Blocks
- Braces must be used on all blocks.
- Braces must start on the same line as the statement starting the block.
- Else and else if must be on the same line as the if block closing brace.
```javascript
// Correct
if (somthing) {
stuff...;
} else if (otherthing) {
stuff...;
}
// Incorrect
if (somthing)
{
stuff...;
}
else
{
stuff...;
}
// Incorrect
if (somthing) stuff...;
if (somthing)
stuff...;
```
### Strings
- Use template strings instead of concatenation.
```javascript
// Correct
function getStr(stuff) {
return "This is the ${stuff} string";
}
// Incorrect
function wrongGetStr(stuff) {
return "This is the " + stuff + " string";
}
```
## React-JSX
Part of the build process is running ESLint. ESLint is the final authority on all style issues. PRs will not be accepted unless there are no errors or warnings running ESLint. The ESLint configuration file can be found in: [web/react/.eslintrc](https://github.com/mattermost/platform/blob/master/web/react/.eslintrc.json)
Instructions on how to use ESLint with your favourite editor can be found here: [http://eslint.org/docs/user-guide/integrations](http://eslint.org/docs/user-guide/integrations)
This is an abridged version of the [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react#airbnb-reactjsx-style-guide). Anything that is unclear here follow that guide. If there is a conflict, follow what is said below.
### General
- Include only one React component per file.
- Use class \<name\> extends React.Component over React.createClass unless you need mixins
- CapitalCamelCase with .jsx extension for component filenames.
- Filenames should be the component name.
### Alignment
- Follow alignment styles shown below:
```xml
// Correct
<Tag
propertyOne="1"
propertyTwo="2"
>
<Child />
</Tag>
// Correct
<Tag propertyOne="1" />
```
### Naming
- Property names use camelCase.
- React component names use CapitalCamelCase.
- Do not use an understore for internal methods in a react component.
```xml
// Correct
<ReactComponent propertyOne="value" />
```

Просмотреть файл

@@ -35,25 +35,18 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
m := model.MapFromJson(r.Body)
email := strings.ToLower(strings.TrimSpace(m["email"]))
displayName := strings.TrimSpace(m["display_name"])
if len(email) == 0 {
c.SetInvalidParam("signupTeam", "email")
return
}
if len(displayName) == 0 {
c.SetInvalidParam("signupTeam", "display_name")
return
}
subjectPage := NewServerTemplatePage("signup_team_subject", c.GetSiteURL())
bodyPage := NewServerTemplatePage("signup_team_body", c.GetSiteURL())
bodyPage.Props["TourUrl"] = utils.Cfg.TeamSettings.TourLink
props := make(map[string]string)
props["email"] = email
props["display_name"] = displayName
props["time"] = fmt.Sprintf("%v", model.GetMillis())
data := model.MapToJson(props)

139
web/react/.eslintrc Обычный файл
Просмотреть файл

@@ -0,0 +1,139 @@
{
"ecmaFeatures": {
"jsx": true,
"blockBindings": true,
"modules": true
},
"plugins": [
"react"
],
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true
},
"globals": {
"React": false
},
"rules": {
"comma-dangle": [2, "never"],
"no-cond-assign": [2, "except-parens"],
"no-console": 1,
"no-constant-condition": 1,
"no-debugger": 1,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-empty": 1,
"no-ex-assign": 1,
"no-extra-semi": 2,
"no-func-assign": 1,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"no-unexpected-multiline": 2,
"block-scoped-var": 1,
"complexity": [1, 8],
"consistent-return": 2,
"curly": [2, "all"],
"dot-notation": 2,
"dot-location": [2, "object"],
"eqeqeq": [2, "smart"],
"guard-for-in": 1,
"no-alert": 1,
"no-caller": 2,
"no-div-regex": 1,
"no-else-return": 1,
"no-eval": 2,
"no-extend-native": 2,
"no-floating-decimal": 2,
"no-labels": 2,
"no-lone-blocks": 1,
"no-multi-spaces": [2, { "exceptions": { "Property": false } }],
"no-multi-str": 0,
"no-param-reassign": 2,
"no-process-env": 2,
"no-redeclare": 2,
"no-return-assign": [2, "always"],
"no-script-url": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-throw-literal": 2,
"no-unused-expressions": 2,
"no-void": 2,
"no-warning-comments": 0,
"no-with": 2,
"radix": 2,
"vars-on-top": 0,
"wrap-iife": [2, "outside"],
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}],
"no-undefined": 2,
"no-shadow": [2, {"hoist": "functions"}],
"no-unused-vars": [2, {"vars": "all", "args": "all"}],
"no-use-before-define": [2, "nofunc"],
// Style
"array-bracket-spacing": [2, "never"],
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
"camelcase": [2, {"properties": "always"}],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
"consistent-this": [2, "self"],
"func-names": 2,
"func-style": [2, "declaration"],
"indent": [2, 4, {"indentSwitchCase": false}],
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"linebreak-style": 2,
"new-cap": 2,
"new-parens": 2,
"no-lonely-if": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multiple-empty-lines": [2, {"max": 1}],
"no-spaced-func": 2,
"no-ternary": 2,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-underscore-dangle": 2,
"no-unneeded-ternary": 2,
"object-curly-spacing": [2, "never"],
"one-var": [2, "never"],
"operator-linebreak": [2, "after"],
"padded-blocks": [2, "never"],
"quote-props": [2, "as-needed"],
"quotes": [2, "single", "avoid-escape"],
"semi-spacing": [2, {"before": false, "after": true}],
"semi": [2, "always"],
"space-after-keywords": [2, "always"],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-return-throw-case": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"wrap-regex": 2,
// React Specific
"react/display-name": [2, { "acceptTranspilerName": true }],
"react/jsx-boolean-value": [2, "always"],
"react/jsx-curly-spacing": [2, "never"],
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-undef": 2,
"react/jsx-quotes": [2, "single", "avoid-escape"],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-danger": 0,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-multi-comp": 2,
"react/no-unknown-property": 2,
"react/prop-types": 2,
"react/sort-comp": 0,
"react/wrap-multilines": 2
}
}

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -24,7 +24,7 @@ module.exports = React.createClass({
if (filename.indexOf("/api/v1/files/get") != -1) {
filename = filename.split("/api/v1/files/get")[1];
}
filename = window.location.origin + "/api/v1/files/get" + filename;
filename = utils.getWindowLocationOrigin() + "/api/v1/files/get" + filename;
if (type === "image") {
previews.push(

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -32,7 +32,7 @@ module.exports = React.createClass({
if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
}
fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
if (type === "image") {
$('<img/>').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() {
@@ -112,7 +112,7 @@ module.exports = React.createClass({
if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
}
fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
if (type === "image") {
if (i < Constants.MAX_DISPLAY_FILES) {

Просмотреть файл

@@ -98,7 +98,7 @@ RootPost = React.createClass({
if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
}
fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
if (ftype === "image") {
var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
@@ -208,7 +208,7 @@ CommentPost = React.createClass({
if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
}
fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
if (type === "image") {
var url = fileInfo.path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');

Просмотреть файл

@@ -68,10 +68,10 @@ var NavbarDropdown = React.createClass({
for (var i = 0; i < this.state.teams.length; i++) {
var teamName = this.state.teams[i];
teams.push(<li key={ teamName }><a href={window.location.origin + "/" + teamName }>Switch to { teamName }</a></li>);
teams.push(<li key={ teamName }><a href={utils.getWindowLocationOrigin() + "/" + teamName }>Switch to { teamName }</a></li>);
}
}
teams.push(<li><a href={window.location.origin + "/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">

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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>&nbsp;
<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>
);
@@ -248,17 +261,23 @@ TeamURLPage = React.createClass({
<div className="row">
<div className="col-sm-11">
<div className="input-group">
<span className="input-group-addon">{ window.location.origin + "/" }</span>
<span 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>
</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>&nbsp;
<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>&nbsp;<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>&nbsp;
<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;
@@ -566,7 +592,7 @@ PasswordPage = React.createClass({
props.state.wizard = "finished";
props.updateParent(props.state, true);
window.location.href = window.location.origin + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email);
window.location.href = utils.getWindowLocationOrigin() + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email);
// client.loginByEmail(teamSignup.team.domain, teamSignup.team.email, teamSignup.user.password,
// function(data) {
@@ -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>&nbsp;
<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("");

Просмотреть файл

@@ -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 } Youll 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 } Youll 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>
);
}

Просмотреть файл

@@ -40,7 +40,7 @@ module.exports = React.createClass({
if (fileInfo.path.indexOf("/api/v1/files/get") !== -1) {
fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
}
fileInfo.path = window.location.origin + "/api/v1/files/get" + fileInfo.path;
fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
src = fileInfo['path'] + '_preview.jpg';
}
@@ -148,7 +148,7 @@ module.exports = React.createClass({
if (info.path.indexOf("/api/v1/files/get") !== -1) {
info.path = info.path.split("/api/v1/files/get")[1];
}
info.path = window.location.origin + "/api/v1/files/get" + info.path;
info.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + info.path;
preview_filename = info['path'] + '_preview.jpg';
}
@@ -166,7 +166,7 @@ module.exports = React.createClass({
if (download_link.indexOf("/api/v1/files/get") !== -1) {
download_link = download_link.split("/api/v1/files/get")[1];
}
download_link = window.location.origin + "/api/v1/files/get" + download_link;
download_link = utils.getWindowLocationOrigin() + "/api/v1/files/get" + download_link;
return (
<div className="modal fade image_modal" ref="modal" id={this.props.modalId} tabIndex="-1" role="dialog" aria-hidden="true">

Просмотреть файл

@@ -16,7 +16,9 @@
"jest-cli": "~0.1.17",
"reactify": "^0.15.2",
"uglify-js": "~2.4.15",
"watchify": "^2.1.1"
"watchify": "^2.1.1",
"eslint": "^0.24.1",
"eslint-plugin-react": "^3.0.0"
},
"scripts": {
"start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.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')
);
};

Просмотреть файл

@@ -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(

Просмотреть файл

@@ -11,6 +11,12 @@ var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
var utils;
function getWindowLocationOrigin() {
if (!utils) utils = require('../utils/utils.jsx');
return utils.getWindowLocationOrigin();
}
var TeamStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
@@ -58,7 +64,7 @@ var TeamStore = assign({}, EventEmitter.prototype, {
return null;
},
getCurrentTeamUrl: function() {
return window.location.origin + "/" + this.getCurrent().name;
return getWindowLocationOrigin() + "/" + this.getCurrent().name;
},
storeTeam: function(team) {
var teams = this._getTeams();

Просмотреть файл

@@ -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);

Просмотреть файл

@@ -22,8 +22,6 @@ module.exports.cleanUpUrlable = function(input) {
return cleaned;
};
module.exports.isTestDomain = function() {
if ((/^localhost/).test(window.location.hostname))
@@ -841,3 +839,12 @@ module.exports.getDisplayName = function(user) {
}
}
};
//IE10 does not set window.location.origin automatically so this must be called instead when using it
module.exports.getWindowLocationOrigin = function() {
var windowLocationOrigin = window.location.origin;
if (!windowLocationOrigin) {
windowLocationOrigin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
}
return windowLocationOrigin;
};

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -9,65 +9,108 @@
max-width: 380px;
margin: 0 auto;
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(30px);
}
h3 {
font-weight: 600;
margin: 0 0 1.3em 0;
font-size: 1.5em;
&.extra-margin {
margin-bottom: 2.5em;
}
}
h4 {
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;
padding-left: 1rem;
}
.signup-team__subdomain {
margin: 0.2em 0 1.2em;
font-size: 1.5em;
padding-left: 1rem;
font-weight: 300;
text-transform: uppercase;
}
.signup-team__subdomain {
font-size: 1.5em;
padding-left: 1em;
}
.form-control {
height: em(38px);
}
.or__container {
height: 1px;
background: #dddddd;
@@ -84,6 +127,12 @@
display: inline-block;
}
}
ul {
margin-bottom: 0;
padding-left: 18px;
}
.btn {
padding: em(7px) em(15px);
font-weight: 600;
@@ -121,8 +170,8 @@
}
.glyphicon {
&.glyphicon-ok, &.glyphicon-refresh {
margin-right: 0.3em;
left: -5px;
margin-right: 0.5em;
left: -2px;
font-size: 0.9em;
}
&.glyphicon-chevron-right {
@@ -137,6 +186,7 @@
}
}
}
.has-error {
.control-label {
background: #f2f2f2;
@@ -148,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;
@@ -163,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;
}

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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>

Просмотреть файл

@@ -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)