summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Godeps/Godeps.json2
-rw-r--r--Godeps/_workspace/src/github.com/nfnt/resize/converter.go6
-rw-r--r--Godeps/_workspace/src/github.com/nfnt/resize/resize.go4
-rw-r--r--Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go96
-rw-r--r--Makefile54
-rw-r--r--README.md6
-rw-r--r--api/channel.go5
-rw-r--r--api/context.go6
-rw-r--r--api/file.go6
-rw-r--r--api/team.go7
-rw-r--r--api/templates/email_change_body.html4
-rw-r--r--api/templates/find_teams_body.html4
-rw-r--r--api/templates/invite_body.html4
-rw-r--r--api/templates/password_change_body.html4
-rw-r--r--api/templates/post_body.html4
-rw-r--r--api/templates/reset_body.html4
-rw-r--r--api/templates/signup_team_body.html4
-rw-r--r--api/templates/verify_body.html4
-rw-r--r--api/templates/welcome_body.html4
-rw-r--r--model/message.go1
-rw-r--r--model/session.go4
-rw-r--r--model/utils.go2
-rw-r--r--store/sql_channel_store.go2
-rw-r--r--store/sql_post_store.go12
-rw-r--r--store/sql_store.go2
-rw-r--r--store/sql_user_store.go2
-rw-r--r--web/react/components/activity_log_modal.jsx3
-rw-r--r--web/react/components/create_post.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx2
-rw-r--r--web/react/components/login.jsx14
-rw-r--r--web/react/components/post_body.jsx2
-rw-r--r--web/react/components/removed_from_channel_modal.jsx64
-rw-r--r--web/react/components/sidebar.jsx14
-rw-r--r--web/react/components/signup_team.jsx20
-rw-r--r--web/react/components/signup_team_complete.jsx140
-rw-r--r--web/react/components/signup_user_complete.jsx52
-rw-r--r--web/react/components/textbox.jsx37
-rw-r--r--web/react/components/user_settings.jsx10
-rw-r--r--web/react/pages/channel.jsx6
-rw-r--r--web/react/pages/signup_team_complete.jsx4
-rw-r--r--web/react/pages/signup_user_complete.jsx2
-rw-r--r--web/react/utils/client.jsx4
-rw-r--r--web/react/utils/utils.jsx9
-rw-r--r--web/sass-files/sass/partials/_activity-log.scss3
-rw-r--r--web/sass-files/sass/partials/_responsive.scss6
-rw-r--r--web/sass-files/sass/partials/_signup.scss163
-rw-r--r--web/static/images/Bladekick-logodark.pngbin7754 -> 10380 bytes
-rw-r--r--web/static/images/Mattermost-logodark.pngbin7754 -> 10380 bytes
-rw-r--r--web/templates/channel.html1
-rw-r--r--web/templates/head.html7
-rw-r--r--web/templates/signup_team.html4
-rw-r--r--web/templates/signup_team_complete.html2
-rw-r--r--web/templates/signup_user_complete.html2
-rw-r--r--web/web.go1
54 files changed, 546 insertions, 281 deletions
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 8b30d0478..86ed9e0cf 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -100,7 +100,7 @@
},
{
"ImportPath": "github.com/nfnt/resize",
- "Rev": "f2d1b73023c55bdfb4fa2ed385d2bec95bce3692"
+ "Rev": "dc93e1b98c579d90ee2fa15c1fd6dac34f6e7899"
},
{
"ImportPath": "github.com/stretchr/objx",
diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/converter.go b/Godeps/_workspace/src/github.com/nfnt/resize/converter.go
index 84bd284ba..b3dd06b8d 100644
--- a/Godeps/_workspace/src/github.com/nfnt/resize/converter.go
+++ b/Godeps/_workspace/src/github.com/nfnt/resize/converter.go
@@ -131,11 +131,11 @@ func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16,
// reverse alpha-premultiplication.
if a != 0 {
- r *= 0xffff
+ r *= 0xff
r /= a
- g *= 0xffff
+ g *= 0xff
g /= a
- b *= 0xffff
+ b *= 0xff
b /= a
}
diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/resize.go b/Godeps/_workspace/src/github.com/nfnt/resize/resize.go
index 4d4ff6e3e..c1672432e 100644
--- a/Godeps/_workspace/src/github.com/nfnt/resize/resize.go
+++ b/Godeps/_workspace/src/github.com/nfnt/resize/resize.go
@@ -167,7 +167,7 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
- result := newYCC(image.Rect(0, 0, int(width), int(height)), input.SubsampleRatio)
+ result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
in := imageYCbCrToYCC(input)
@@ -409,7 +409,7 @@ func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image,
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
- result := newYCC(image.Rect(0, 0, int(width), int(height)), input.SubsampleRatio)
+ result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
in := imageYCbCrToYCC(input)
diff --git a/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go b/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go
index ee31ac494..6f6911316 100644
--- a/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go
+++ b/Godeps/_workspace/src/github.com/nfnt/resize/resize_test.go
@@ -46,7 +46,7 @@ func Test_CorrectResize(t *testing.T) {
}
}
-func Test_SameColor(t *testing.T) {
+func Test_SameColorWithRGBA(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
@@ -56,9 +56,99 @@ func Test_SameColor(t *testing.T) {
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
- color := img.At(x, y).(color.RGBA)
+ color := out.At(x, y).(color.NRGBA)
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
- t.Fail()
+ t.Errorf("%+v", color)
+ }
+ }
+ }
+}
+
+func Test_SameColorWithNRGBA(t *testing.T) {
+ img := image.NewNRGBA(image.Rect(0, 0, 20, 20))
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ img.SetNRGBA(x, y, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
+ }
+ }
+ out := Resize(10, 10, img, Lanczos3)
+ for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
+ for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
+ color := out.At(x, y).(color.NRGBA)
+ if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
+ t.Errorf("%+v", color)
+ }
+ }
+ }
+}
+
+func Test_SameColorWithRGBA64(t *testing.T) {
+ img := image.NewRGBA64(image.Rect(0, 0, 20, 20))
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ img.SetRGBA64(x, y, color.RGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
+ }
+ }
+ out := Resize(10, 10, img, Lanczos3)
+ for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
+ for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
+ color := out.At(x, y).(color.NRGBA64)
+ if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
+ t.Errorf("%+v", color)
+ }
+ }
+ }
+}
+
+func Test_SameColorWithNRGBA64(t *testing.T) {
+ img := image.NewNRGBA64(image.Rect(0, 0, 20, 20))
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ img.SetNRGBA64(x, y, color.NRGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
+ }
+ }
+ out := Resize(10, 10, img, Lanczos3)
+ for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
+ for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
+ color := out.At(x, y).(color.NRGBA64)
+ if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
+ t.Errorf("%+v", color)
+ }
+ }
+ }
+}
+
+func Test_SameColorWithGray(t *testing.T) {
+ img := image.NewGray(image.Rect(0, 0, 20, 20))
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ img.SetGray(x, y, color.Gray{0x80})
+ }
+ }
+ out := Resize(10, 10, img, Lanczos3)
+ for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
+ for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
+ color := out.At(x, y).(color.Gray)
+ if color.Y != 0x80 {
+ t.Errorf("%+v", color)
+ }
+ }
+ }
+}
+
+func Test_SameColorWithGray16(t *testing.T) {
+ img := image.NewGray16(image.Rect(0, 0, 20, 20))
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ img.SetGray16(x, y, color.Gray16{0x8000})
+ }
+ }
+ out := Resize(10, 10, img, Lanczos3)
+ for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
+ for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
+ color := out.At(x, y).(color.Gray16)
+ if color.Y != 0x8000 {
+ t.Errorf("%+v", color)
}
}
}
diff --git a/Makefile b/Makefile
index 8793ba98a..222d4ffe4 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,8 @@ GOPATH ?= $(GOPATH:)
GOFLAGS ?= $(GOFLAGS:)
BUILD_NUMBER ?= $(BUILD_NUMBER:)
+GO=$(GOPATH)/bin/godep go
+
ifeq ($(BUILD_NUMBER),)
BUILD_NUMBER := dev
endif
@@ -21,29 +23,27 @@ travis:
@echo building for travis
rm -Rf $(DIST_ROOT)
- @go clean $(GOFLAGS) -i ./...
-
+ @$(GO) clean $(GOFLAGS) -i ./...
+
@cd web/react/ && npm install
- @go build $(GOFLAGS) ./...
+ @$(GO) build $(GOFLAGS) ./...
@mkdir -p logs
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
build:
- @go build $(GOFLAGS) ./...
+ @$(GO) build $(GOFLAGS) ./...
install:
@go get $(GOFLAGS) github.com/tools/godep
@if [ $(shell docker ps -a | grep -ci mattermost-mysql) -eq 0 ]; then \
- echo restoring go libs using godep; \
- $(GOPATH)/bin/godep restore; \
echo starting mattermost-mysql; \
docker run --name mattermost-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mostest \
-e MYSQL_USER=mmuser -e MYSQL_PASSWORD=mostest -e MYSQL_DATABASE=mattermost_test -d mysql > /dev/null; \
@@ -64,37 +64,37 @@ install:
test: install
@mkdir -p logs
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
- @go test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
+ @$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
benchmark: install
@mkdir -p logs
- @go test $(GOFLAGS) -test.v -run=NO_TESTS -bench=$(BENCH) ./api || exit 1
+ @$(GO) test $(GOFLAGS) -test.v -run=NO_TESTS -bench=$(BENCH) ./api || exit 1
cover: install
rm -Rf $(DIST_RESULTS)
mkdir -p $(DIST_RESULTS)
- @go test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/api.cover.out github.com/mattermost/platform/api
- @go test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/model.cover.out github.com/mattermost/platform/model
- @go test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/store.cover.out github.com/mattermost/platform/store
- @go test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/utils.cover.out github.com/mattermost/platform/utils
- @go test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/web.cover.out github.com/mattermost/platform/web
+ @$(GO) test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/api.cover.out github.com/mattermost/platform/api
+ @$(GO) test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/model.cover.out github.com/mattermost/platform/model
+ @$(GO) test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/store.cover.out github.com/mattermost/platform/store
+ @$(GO) test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/utils.cover.out github.com/mattermost/platform/utils
+ @$(GO) test $(GOFLAGS) -coverprofile=$(DIST_RESULTS)/web.cover.out github.com/mattermost/platform/web
cd $(DIST_RESULTS) && \
echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \
awk '{if($$1 != last) {print $$0;last=$$1}}' >> coverage.out
- cd $(DIST_RESULTS) && go tool cover -html=coverage.out -o=coverage.html
+ cd $(DIST_RESULTS) && $(GO) tool cover -html=coverage.out -o=coverage.html
rm -f $(DIST_RESULTS)/*.cover.out
clean:
rm -Rf $(DIST_ROOT)
- @go clean $(GOFLAGS) -i ./...
+ @$(GO) clean $(GOFLAGS) -i ./...
@if [ $(shell docker ps -a | grep -ci mattermost-mysql) -eq 1 ]; then \
echo stopping mattermost-mysql; \
@@ -124,7 +124,7 @@ run: install
@cd web/react && npm start &
@echo starting go web server
- @go run $(GOFLAGS) mattermost.go -config=config.json &
+ @$(GO) run $(GOFLAGS) mattermost.go -config=config.json &
@echo starting compass watch
@cd web/sass-files && compass watch &
@@ -161,8 +161,8 @@ cleandb:
dist: install
- @go build $(GOFLAGS) -i -a ./...
- @go install $(GOFLAGS) -a ./...
+ @$(GO) build $(GOFLAGS) -i -a ./...
+ @$(GO) install $(GOFLAGS) -a ./...
mkdir -p $(DIST_PATH)/bin
cp $(GOPATH)/bin/platform $(DIST_PATH)/bin
diff --git a/README.md b/README.md
index 65a3e7c66..f0857e99d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-**Mattermost Preview**
+**Mattermost Alpha**
**Team Communication Service**
-**Version 0.5.0**
+**Development Build**
About Mattermost
@@ -22,7 +22,7 @@ Learn More
Installing the Mattermost
=========================
-You're installing "Mattermost Preview", a pre-released 0.5.0 version intended for an early look at what we're building. While SpinPunch runs this version internally, it's not recommended for production deployments since we can't guarantee API stability or backwards compatibility until our 1.0.0 version release.
+You're installing "Mattermost Alpha", a pre-released version intended for an early look at what we're building. While SpinPunch runs this version internally, it's not recommended for production deployments since we can't guarantee API stability or backwards compatibility until our production release.
That said, any issues at all, please let us know on the Mattermost forum at: http://forum.mattermost.org
diff --git a/api/channel.go b/api/channel.go
index 4d8dbad09..123fd8a35 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -710,6 +710,11 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_REMOVED)
+ message.Add("channel_id",id)
+ message.Add("remover", c.Session.UserId)
+ PublishAndForget(message)
+
c.LogAudit("name=" + channel.Name + " user_id=" + userId)
result := make(map[string]string)
diff --git a/api/context.go b/api/context.go
index ac9dffcbc..16da0a6eb 100644
--- a/api/context.go
+++ b/api/context.go
@@ -101,6 +101,12 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
w.Header().Set(model.HEADER_VERSION_ID, utils.Cfg.ServiceSettings.Version)
+ // Instruct the browser not to display us in an iframe for anti-clickjacking
+ if !h.isApi {
+ w.Header().Set("X-Frame-Options", "DENY")
+ w.Header().Set("Content-Security-Policy", "frame-ancestors none")
+ }
+
sessionId := ""
// attempt to parse the session token from the header
diff --git a/api/file.go b/api/file.go
index 82cee9d1e..3ef50fbbd 100644
--- a/api/file.go
+++ b/api/file.go
@@ -33,7 +33,7 @@ func InitFile(r *mux.Router) {
sr := r.PathPrefix("/files").Subrouter()
sr.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST")
- sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+\\.[A-Za-z0-9]{3,}}", ApiAppHandler(getFile)).Methods("GET")
+ sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFile)).Methods("GET")
sr.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("POST")
}
@@ -142,7 +142,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
go func() {
var thumbnail image.Image
if imgConfig.Width > int(utils.Cfg.ImageSettings.ThumbnailWidth) {
- thumbnail = resize.Resize(utils.Cfg.ImageSettings.ThumbnailWidth, utils.Cfg.ImageSettings.ThumbnailHeight, img, resize.NearestNeighbor)
+ thumbnail = resize.Resize(utils.Cfg.ImageSettings.ThumbnailWidth, utils.Cfg.ImageSettings.ThumbnailHeight, img, resize.Lanczos3)
} else {
thumbnail = img
}
@@ -164,7 +164,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
go func() {
var preview image.Image
if imgConfig.Width > int(utils.Cfg.ImageSettings.PreviewWidth) {
- preview = resize.Resize(utils.Cfg.ImageSettings.PreviewWidth, utils.Cfg.ImageSettings.PreviewHeight, img, resize.NearestNeighbor)
+ preview = resize.Resize(utils.Cfg.ImageSettings.PreviewWidth, utils.Cfg.ImageSettings.PreviewHeight, img, resize.Lanczos3)
} else {
preview = img
}
diff --git a/api/team.go b/api/team.go
index 1145e6e81..c9fe42ecc 100644
--- a/api/team.go
+++ b/api/team.go
@@ -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)
diff --git a/api/templates/email_change_body.html b/api/templates/email_change_body.html
index f8f3845e7..439fffd5b 100644
--- a/api/templates/email_change_body.html
+++ b/api/templates/email_change_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -32,7 +32,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html
index 6eaaf56e0..a73ed0ad4 100644
--- a/api/templates/find_teams_body.html
+++ b/api/templates/find_teams_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -40,7 +40,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html
index 46189fae5..ad0658e3d 100644
--- a/api/templates/invite_body.html
+++ b/api/templates/invite_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -35,7 +35,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/password_change_body.html b/api/templates/password_change_body.html
index 515c0a7d9..1d4a6e1c8 100644
--- a/api/templates/password_change_body.html
+++ b/api/templates/password_change_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -32,7 +32,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/post_body.html b/api/templates/post_body.html
index c0f4375d8..0aa913db5 100644
--- a/api/templates/post_body.html
+++ b/api/templates/post_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -35,7 +35,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html
index af9f6b4e8..4c2fec1e7 100644
--- a/api/templates/reset_body.html
+++ b/api/templates/reset_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -35,7 +35,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/signup_team_body.html b/api/templates/signup_team_body.html
index 5a5ae4d47..5e60a042b 100644
--- a/api/templates/signup_team_body.html
+++ b/api/templates/signup_team_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -38,7 +38,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/verify_body.html b/api/templates/verify_body.html
index 67ded9c20..1a68c16f5 100644
--- a/api/templates/verify_body.html
+++ b/api/templates/verify_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -35,7 +35,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/api/templates/welcome_body.html b/api/templates/welcome_body.html
index 7107bc2e0..cc4d95fb1 100644
--- a/api/templates/welcome_body.html
+++ b/api/templates/welcome_body.html
@@ -8,7 +8,7 @@
<td style="border: 1px solid #ddd;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
- <td style="padding: 20px 30px 10px; text-align:left;">
+ <td style="padding: 20px 20px 10px; text-align:left;">
<img src="{{.TeamURL}}/static/images/{{.SiteName}}-logodark.png" width="130px" style="opacity: 0.5" alt="">
</td>
</tr>
@@ -32,7 +32,7 @@
</td>
</tr>
<tr>
- <td style="text-align: center;color: #AAA; font-size: 13px; padding-bottom: 10px;">
+ <td style="text-align: center;color: #AAA; font-size: 11px; padding-bottom: 10px;">
<p style="margin: 25px 0;">
<img width="65" src="{{.TeamURL}}/static/images/circles.png" alt="">
</p>
diff --git a/model/message.go b/model/message.go
index 52ee69e8f..ec4817b2a 100644
--- a/model/message.go
+++ b/model/message.go
@@ -16,6 +16,7 @@ const (
ACTION_VIEWED = "viewed"
ACTION_NEW_USER = "new_user"
ACTION_USER_ADDED = "user_added"
+ ACTION_USER_REMOVED = "user_removed"
)
type Message struct {
diff --git a/model/session.go b/model/session.go
index 9fd3b9ec3..c812f83e2 100644
--- a/model/session.go
+++ b/model/session.go
@@ -10,9 +10,9 @@ import (
const (
SESSION_TOKEN = "MMSID"
- SESSION_TIME_WEB_IN_DAYS = 365
+ SESSION_TIME_WEB_IN_DAYS = 30
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
- SESSION_TIME_MOBILE_IN_DAYS = 365
+ SESSION_TIME_MOBILE_IN_DAYS = 30
SESSION_TIME_MOBILE_IN_SECS = 60 * 60 * 24 * SESSION_TIME_MOBILE_IN_DAYS
SESSION_CACHE_IN_SECS = 60 * 10
SESSION_CACHE_SIZE = 10000
diff --git a/model/utils.go b/model/utils.go
index 093a54e38..c7f991da2 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -319,6 +319,6 @@ func ClearMentionTags(post string) string {
}
var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&amp;]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`)
-var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+\.[A-Za-z0-9]{3,})`)
+var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+(?:\.[A-Za-z0-9]{3,})?)`)
var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true}
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index 8961d5d97..f64558a92 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -120,7 +120,7 @@ func (s SqlChannelStore) Update(channel *model.Channel) StoreChannel {
if count, err := s.GetMaster().Update(channel); err != nil {
if IsUniqueConstraintError(err.Error(), "Name", "channels_name_teamid_key") {
dupChannel := model.Channel{}
- s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId=? AND Name=? AND DeleteAt > 0", channel.TeamId, channel.Name)
+ s.GetReplica().SelectOne(&dupChannel, "SELECT * FROM Channels WHERE TeamId = :TeamId AND Name= :Name AND DeleteAt > 0", map[string]interface{}{"TeamId": channel.TeamId, "Name": channel.Name})
if dupChannel.DeleteAt > 0 {
result.Err = model.NewAppError("SqlChannelStore.Update", "A channel with that name was previously created", "id="+channel.Id+", "+err.Error())
} else {
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 56c174e4c..ede69d125 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -38,7 +38,7 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
// These execs are for upgrading currently created databases to full utf8mb4 compliance
- // Will be removed as seen fit for upgrading
+ // Will be removed as seen fit for upgrading
s.GetMaster().Exec("ALTER TABLE Posts charset=utf8mb4")
s.GetMaster().Exec("ALTER TABLE Posts MODIFY COLUMN Message varchar(4000) CHARACTER SET utf8mb4")
}
@@ -80,14 +80,14 @@ func (s SqlPostStore) Save(post *model.Post) StoreChannel {
time := model.GetMillis()
if post.Type != model.POST_JOIN_LEAVE {
- s.GetMaster().Exec("UPDATE Channels SET LastPostAt = ?, TotalMsgCount = TotalMsgCount + 1 WHERE Id = ?", time, post.ChannelId)
+ s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt, TotalMsgCount = TotalMsgCount + 1 WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId})
} else {
// don't update TotalMsgCount for unimportant messages so that the channel isn't marked as unread
- s.GetMaster().Exec("UPDATE Channels SET LastPostAt = ? WHERE Id = ?", time, post.ChannelId)
+ s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": post.ChannelId})
}
if len(post.RootId) > 0 {
- s.GetMaster().Exec("UPDATE Posts SET UpdateAt = ? WHERE Id = ?", time, post.RootId)
+ s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": post.RootId})
}
result.Data = post
@@ -126,10 +126,10 @@ func (s SqlPostStore) Update(oldPost *model.Post, newMessage string, newHashtags
result.Err = model.NewAppError("SqlPostStore.Update", "We couldn't update the Post", "id="+editPost.Id+", "+err.Error())
} else {
time := model.GetMillis()
- s.GetMaster().Exec("UPDATE Channels SET LastPostAt = ? WHERE Id = ?", time, editPost.ChannelId)
+ s.GetMaster().Exec("UPDATE Channels SET LastPostAt = :LastPostAt WHERE Id = :ChannelId", map[string]interface{}{"LastPostAt": time, "ChannelId": editPost.ChannelId})
if len(editPost.RootId) > 0 {
- s.GetMaster().Exec("UPDATE Posts SET UpdateAt = ? WHERE Id = ?", time, editPost.RootId)
+ s.GetMaster().Exec("UPDATE Posts SET UpdateAt = :UpdateAt WHERE Id = :RootId", map[string]interface{}{"UpdateAt": time, "RootId": editPost.RootId})
}
// mark the old post as deleted
diff --git a/store/sql_store.go b/store/sql_store.go
index 606b2cbc1..0d4f76a72 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -224,7 +224,7 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st
func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) {
if utils.Cfg.SqlSettings.DriverName == "postgres" {
- _, err := ss.GetMaster().SelectStr("SELECT to_regclass($1)", indexName)
+ _, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
// It should fail if the index does not exist
if err == nil {
return
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 0228fa308..cd63e95b8 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -184,7 +184,7 @@ func (us SqlUserStore) UpdateLastPictureUpdate(userId string) StoreChannel {
curTime := model.GetMillis()
- if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = ?, UpdateAt = ? WHERE Id = ?", curTime, curTime, userId); err != nil {
+ if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = :Time, UpdateAt = :Time WHERE Id = :UserId", map[string]interface{}{"Time": curTime, "UserId": userId}); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdateUpdateAt", "We couldn't update the update_at", "user_id="+userId)
} else {
result.Data = userId
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">&times;</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">&times;</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>&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>
);
@@ -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>&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;
@@ -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("");
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
index 8f1268fdd..c16978ba8 100644
--- a/web/static/images/Bladekick-logodark.png
+++ b/web/static/images/Bladekick-logodark.png
Binary files differ
diff --git a/web/static/images/Mattermost-logodark.png b/web/static/images/Mattermost-logodark.png
index 8f1268fdd..c16978ba8 100644
--- a/web/static/images/Mattermost-logodark.png
+++ b/web/static/images/Mattermost-logodark.png
Binary files differ
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)