summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/post.go66
-rw-r--r--api/post_test.go77
-rw-r--r--api/user.go19
-rw-r--r--api/user_test.go8
-rw-r--r--doc/integrations/Single-Sign-On/Gitlab.md2
-rw-r--r--model/client.go22
-rw-r--r--store/sql_post_store.go98
-rw-r--r--store/sql_post_store_test.go105
-rw-r--r--store/store.go2
-rw-r--r--web/react/components/admin_console/team_users.jsx8
-rw-r--r--web/react/components/channel_invite_modal.jsx2
-rw-r--r--web/react/components/channel_loader.jsx9
-rw-r--r--web/react/components/channel_members.jsx8
-rw-r--r--web/react/components/member_list.jsx6
-rw-r--r--web/react/components/member_list_item.jsx26
-rw-r--r--web/react/components/member_list_team.jsx8
-rw-r--r--web/react/components/more_direct_channels.jsx2
-rw-r--r--web/react/components/navbar_dropdown.jsx18
-rw-r--r--web/react/components/sidebar.jsx82
-rw-r--r--web/react/components/sidebar_header.jsx3
-rw-r--r--web/react/components/signup_team.jsx15
-rw-r--r--web/react/components/tutorial/tutorial_intro_screens.jsx6
-rw-r--r--web/react/stores/channel_store.jsx14
-rw-r--r--web/react/stores/user_store.jsx33
-rw-r--r--web/react/utils/async_client.jsx18
-rw-r--r--web/react/utils/client.jsx5
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/markdown.jsx3
-rw-r--r--web/react/utils/utils.jsx21
-rw-r--r--web/sass-files/sass/partials/_modal.scss22
-rw-r--r--web/sass-files/sass/partials/_responsive.scss2
-rw-r--r--web/sass-files/sass/partials/_signup.scss55
-rw-r--r--web/templates/channel.html7
33 files changed, 589 insertions, 184 deletions
diff --git a/api/post.go b/api/post.go
index c98bf2d71..31a7ab3b5 100644
--- a/api/post.go
+++ b/api/post.go
@@ -31,6 +31,8 @@ func InitPost(r *mux.Router) {
sr.Handle("/posts/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}", ApiUserRequired(getPost)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}/delete", ApiUserRequired(deletePost)).Methods("POST")
+ sr.Handle("/post/{post_id:[A-Za-z0-9]+}/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
+ sr.Handle("/post/{post_id:[A-Za-z0-9]+}/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
}
func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -812,6 +814,70 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) {
+ getPostsBeforeOrAfter(c, w, r, true)
+}
+func getPostsAfter(c *Context, w http.ResponseWriter, r *http.Request) {
+ getPostsBeforeOrAfter(c, w, r, false)
+}
+func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, before bool) {
+ params := mux.Vars(r)
+
+ id := params["id"]
+ if len(id) != 26 {
+ c.SetInvalidParam("getPostsBeforeOrAfter", "channelId")
+ return
+ }
+
+ postId := params["post_id"]
+ if len(postId) != 26 {
+ c.SetInvalidParam("getPostsBeforeOrAfter", "postId")
+ return
+ }
+
+ numPosts, err := strconv.Atoi(params["num_posts"])
+ if err != nil || numPosts <= 0 {
+ c.SetInvalidParam("getPostsBeforeOrAfter", "numPosts")
+ return
+ }
+
+ offset, err := strconv.Atoi(params["offset"])
+ if err != nil || offset < 0 {
+ c.SetInvalidParam("getPostsBeforeOrAfter", "offset")
+ return
+ }
+
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ // We can do better than this etag in this situation
+ etagChan := Srv.Store.Post().GetEtag(id)
+
+ if !c.HasPermissionsToChannel(cchan, "getPostsBeforeOrAfter") {
+ return
+ }
+
+ etag := (<-etagChan).Data.(string)
+ if HandleEtag(etag, w, r) {
+ return
+ }
+
+ var pchan store.StoreChannel
+ if before {
+ pchan = Srv.Store.Post().GetPostsBefore(id, postId, numPosts, offset)
+ } else {
+ pchan = Srv.Store.Post().GetPostsAfter(id, postId, numPosts, offset)
+ }
+
+ if result := <-pchan; result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ list := result.Data.(*model.PostList)
+
+ w.Header().Set(model.HEADER_ETAG_SERVER, etag)
+ w.Write([]byte(list.ToJson()))
+ }
+}
+
func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
terms := r.FormValue("terms")
diff --git a/api/post_test.go b/api/post_test.go
index e54e9ef0c..3452c9788 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -329,6 +329,83 @@ func TestGetPostsSince(t *testing.T) {
}
}
+func TestGetPostsBeforeAfter(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+
+ Client.LoginByEmail(team.Name, user1.Email, "pwd")
+
+ channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ time.Sleep(10 * time.Millisecond)
+ post0 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post0 = Client.Must(Client.CreatePost(post0)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post1a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post1.Id}
+ post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post2 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post3 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post3a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post3.Id}
+ post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post)
+
+ r1 := Client.Must(Client.GetPostsBefore(channel1.Id, post1a1.Id, 0, 10, "")).Data.(*model.PostList)
+
+ if r1.Order[0] != post1.Id {
+ t.Fatal("wrong order")
+ }
+
+ if r1.Order[1] != post0.Id {
+ t.Fatal("wrong order")
+ }
+
+ if len(r1.Posts) != 2 {
+ t.Fatal("wrong size")
+ }
+
+ r2 := Client.Must(Client.GetPostsAfter(channel1.Id, post3a1.Id, 0, 3, "")).Data.(*model.PostList)
+
+ if len(r2.Posts) != 0 {
+ t.Fatal("should have been empty")
+ }
+
+ post2.Message = "new message"
+ Client.Must(Client.UpdatePost(post2))
+
+ r3 := Client.Must(Client.GetPostsAfter(channel1.Id, post1a1.Id, 0, 2, "")).Data.(*model.PostList)
+
+ if r3.Order[0] != post3.Id {
+ t.Fatal("wrong order")
+ }
+
+ if r3.Order[1] != post2.Id {
+ t.Fatal("wrong order")
+ }
+
+ if len(r3.Order) != 2 {
+ t.Fatal("missing post update")
+ }
+}
+
func TestSearchPosts(t *testing.T) {
Setup()
diff --git a/api/user.go b/api/user.go
index 732c6b9a8..42d3a43e7 100644
--- a/api/user.go
+++ b/api/user.go
@@ -49,7 +49,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET")
- sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("GET")
+ sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST")
sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET")
@@ -1483,16 +1483,31 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
}
func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
+ userIds := model.ArrayFromJson(r.Body)
+ if len(userIds) == 0 {
+ c.SetInvalidParam("getStatuses", "userIds")
+ return
+ }
if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
-
profiles := result.Data.(map[string]*model.User)
statuses := map[string]string{}
for _, profile := range profiles {
+ found := false
+ for _, uid := range userIds {
+ if uid == profile.Id {
+ found = true
+ }
+ }
+
+ if !found {
+ continue
+ }
+
if profile.IsOffline() {
statuses[profile.Id] = model.USER_OFFLINE
} else if profile.IsAway() {
diff --git a/api/user_test.go b/api/user_test.go
index 0ad3541bc..f067182cb 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -1020,9 +1020,15 @@ func TestStatuses(t *testing.T) {
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ ruser2 := Client.Must(Client.CreateUser(&user2, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(ruser2.Id))
+
Client.LoginByEmail(team.Name, user.Email, user.Password)
- r1, err := Client.GetStatuses()
+ userIds := []string{ruser2.Id}
+
+ r1, err := Client.GetStatuses(userIds)
if err != nil {
t.Fatal(err)
}
diff --git a/doc/integrations/Single-Sign-On/Gitlab.md b/doc/integrations/Single-Sign-On/Gitlab.md
index 7939c47fb..1242fd13e 100644
--- a/doc/integrations/Single-Sign-On/Gitlab.md
+++ b/doc/integrations/Single-Sign-On/Gitlab.md
@@ -11,7 +11,7 @@ Follow these steps to configure Mattermost to use GitLab as a single-sign-on (SS
3. Submit the application and copy the given _Id_ and _Secret_ into the appropriate _SSOSettings_ fields in config/config.json
-4. Also in config/config.json, set _Allow_ to `true` for the _gitlab_ section, leave _Scope_ blank and use the following for the endpoints:
+4. Also in config/config.json, set _Enable_ to `true` for the _gitlab_ section, leave _Scope_ blank and use the following for the endpoints:
* _AuthEndpoint_: `https://<your-gitlab-url>/oauth/authorize` (example https://example.com/oauth/authorize)
* _TokenEndpoint_: `https://<your-gitlab-url>/oauth/token`
* _UserApiEndpoint_: `https://<your-gitlab-url>/api/v3/user`
diff --git a/model/client.go b/model/client.go
index 19183098e..ac85b0d1c 100644
--- a/model/client.go
+++ b/model/client.go
@@ -618,6 +618,24 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError
}
}
+func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/before/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/after/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
return nil, err
@@ -783,8 +801,8 @@ func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
}
}
-func (c *Client) GetStatuses() (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/status", "", ""); err != nil {
+func (c *Client) GetStatuses(data []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index de8c4f356..fdae20f60 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -332,6 +332,104 @@ func (s SqlPostStore) GetPostsSince(channelId string, time int64) StoreChannel {
return storeChannel
}
+func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel {
+ return s.getPostsAround(channelId, postId, numPosts, offset, true)
+}
+
+func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel {
+ return s.getPostsAround(channelId, postId, numPosts, offset, false)
+}
+
+func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var direction string
+ var sort string
+ if before {
+ direction = "<"
+ sort = "DESC"
+ } else {
+ direction = ">"
+ sort = "ASC"
+ }
+
+ var posts []*model.Post
+ var parents []*model.Post
+ _, err1 := s.GetReplica().Select(&posts,
+ `(SELECT
+ *
+ FROM
+ Posts
+ WHERE
+ (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
+ AND ChannelId = :ChannelId
+ AND DeleteAt = 0)
+ ORDER BY CreateAt `+sort+`
+ LIMIT :NumPosts
+ OFFSET :Offset)`,
+ map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
+ _, err2 := s.GetReplica().Select(&parents,
+ `(SELECT
+ *
+ FROM
+ Posts
+ WHERE
+ Id
+ IN
+ (SELECT * FROM (SELECT
+ RootId
+ FROM
+ Posts
+ WHERE
+ (CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
+ AND ChannelId = :ChannelId
+ AND DeleteAt = 0)
+ ORDER BY CreateAt `+sort+`
+ LIMIT :NumPosts
+ OFFSET :Offset)
+ temp_tab))
+ ORDER BY CreateAt DESC`,
+ map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
+
+ if err1 != nil {
+ result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the posts for the channel", "channelId="+channelId+err1.Error())
+ } else if err2 != nil {
+ result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the parent posts for the channel", "channelId="+channelId+err2.Error())
+ } else {
+
+ list := &model.PostList{Order: make([]string, 0, len(posts))}
+
+ // We need to flip the order if we selected backwards
+ if before {
+ for _, p := range posts {
+ list.AddPost(p)
+ list.AddOrder(p.Id)
+ }
+ } else {
+ l := len(posts)
+ for i := range posts {
+ list.AddPost(posts[l-i-1])
+ list.AddOrder(posts[l-i-1].Id)
+ }
+ }
+
+ for _, p := range parents {
+ list.AddPost(p)
+ }
+
+ result.Data = list
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)
diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go
index 0980b1a11..fe7195a54 100644
--- a/store/sql_post_store_test.go
+++ b/store/sql_post_store_test.go
@@ -383,6 +383,111 @@ func TestPostStoreGetPostsWtihDetails(t *testing.T) {
}
}
+func TestPostStoreGetPostsBeforeAfter(t *testing.T) {
+ Setup()
+ o0 := &model.Post{}
+ o0.ChannelId = model.NewId()
+ o0.UserId = model.NewId()
+ o0.Message = "a" + model.NewId() + "b"
+ o0 = (<-store.Post().Save(o0)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o1 := &model.Post{}
+ o1.ChannelId = model.NewId()
+ o1.UserId = model.NewId()
+ o1.Message = "a" + model.NewId() + "b"
+ o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o2 := &model.Post{}
+ o2.ChannelId = o1.ChannelId
+ o2.UserId = model.NewId()
+ o2.Message = "a" + model.NewId() + "b"
+ o2.ParentId = o1.Id
+ o2.RootId = o1.Id
+ o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o2a := &model.Post{}
+ o2a.ChannelId = o1.ChannelId
+ o2a.UserId = model.NewId()
+ o2a.Message = "a" + model.NewId() + "b"
+ o2a.ParentId = o1.Id
+ o2a.RootId = o1.Id
+ o2a = (<-store.Post().Save(o2a)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o3 := &model.Post{}
+ o3.ChannelId = o1.ChannelId
+ o3.UserId = model.NewId()
+ o3.Message = "a" + model.NewId() + "b"
+ o3.ParentId = o1.Id
+ o3.RootId = o1.Id
+ o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o4 := &model.Post{}
+ o4.ChannelId = o1.ChannelId
+ o4.UserId = model.NewId()
+ o4.Message = "a" + model.NewId() + "b"
+ o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
+
+ o5 := &model.Post{}
+ o5.ChannelId = o1.ChannelId
+ o5.UserId = model.NewId()
+ o5.Message = "a" + model.NewId() + "b"
+ o5.ParentId = o4.Id
+ o5.RootId = o4.Id
+ o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
+
+ r1 := (<-store.Post().GetPostsBefore(o1.ChannelId, o1.Id, 4, 0)).Data.(*model.PostList)
+
+ if len(r1.Posts) != 0 {
+ t.Fatal("Wrong size")
+ }
+
+ r2 := (<-store.Post().GetPostsAfter(o1.ChannelId, o1.Id, 4, 0)).Data.(*model.PostList)
+
+ if r2.Order[0] != o4.Id {
+ t.Fatal("invalid order")
+ }
+
+ if r2.Order[1] != o3.Id {
+ t.Fatal("invalid order")
+ }
+
+ if r2.Order[2] != o2a.Id {
+ t.Fatal("invalid order")
+ }
+
+ if r2.Order[3] != o2.Id {
+ t.Fatal("invalid order")
+ }
+
+ if len(r2.Posts) != 5 {
+ t.Fatal("wrong size")
+ }
+
+ r3 := (<-store.Post().GetPostsBefore(o3.ChannelId, o3.Id, 2, 0)).Data.(*model.PostList)
+
+ if r3.Order[0] != o2a.Id {
+ t.Fatal("invalid order")
+ }
+
+ if r3.Order[1] != o2.Id {
+ t.Fatal("invalid order")
+ }
+
+ if len(r3.Posts) != 3 {
+ t.Fatal("wrong size")
+ }
+
+ if r3.Posts[o1.Id].Message != o1.Message {
+ t.Fatal("Missing parent")
+ }
+}
+
func TestPostStoreGetPostsSince(t *testing.T) {
Setup()
o0 := &model.Post{}
diff --git a/store/store.go b/store/store.go
index 53a6e053b..ce4d90883 100644
--- a/store/store.go
+++ b/store/store.go
@@ -86,6 +86,8 @@ type PostStore interface {
Get(id string) StoreChannel
Delete(postId string, time int64) StoreChannel
GetPosts(channelId string, offset int, limit int) StoreChannel
+ GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel
+ GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel
GetPostsSince(channelId string, time int64) StoreChannel
GetEtag(channelId string) StoreChannel
Search(teamId string, userId string, params *model.SearchParams) StoreChannel
diff --git a/web/react/components/admin_console/team_users.jsx b/web/react/components/admin_console/team_users.jsx
index b44aba56e..7161139e6 100644
--- a/web/react/components/admin_console/team_users.jsx
+++ b/web/react/components/admin_console/team_users.jsx
@@ -147,9 +147,11 @@ export default class UserList extends React.Component {
className='form-horizontal'
role='form'
>
- <div className='member-list-holder'>
- {memberList}
- </div>
+ <table className='table more-table member-list-holder'>
+ <tbody>
+ {memberList}
+ </tbody>
+ </table>
</form>
<ResetPasswordModal
user={this.state.user}
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 7c7770095..e90d1a666 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -139,7 +139,7 @@ export default class ChannelInviteModal extends React.Component {
return (
<div
- className='modal fade'
+ className='modal fade more-modal'
id='channel_invite'
tabIndex='-1'
role='dialog'
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 55b4a55c0..4fc115a92 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -30,19 +30,14 @@ export default class ChannelLoader extends React.Component {
AsyncClient.getChannels(true, true);
AsyncClient.getChannelExtraInfo(true);
AsyncClient.findTeams();
- AsyncClient.getStatuses();
AsyncClient.getMyTeam();
+ setTimeout(() => AsyncClient.getStatuses(), 3000); // temporary until statuses are reworked a bit
/* Perform pending post clean-up */
PostStore.clearPendingPosts();
/* Set up interval functions */
- this.intervalId = setInterval(
- function pollStatuses() {
- AsyncClient.getStatuses();
- },
- 30000
- );
+ this.intervalId = setInterval(() => AsyncClient.getStatuses(), 30000);
/* Device tracking setup */
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
index 86cc2464d..d0ea7278b 100644
--- a/web/react/components/channel_members.jsx
+++ b/web/react/components/channel_members.jsx
@@ -150,7 +150,7 @@ export default class ChannelMembers extends React.Component {
return (
<div
- className='modal fade'
+ className='modal fade more-modal'
ref='modal'
id='channel_members'
tabIndex='-1'
@@ -181,11 +181,7 @@ export default class ChannelMembers extends React.Component {
ref='modalBody'
className='modal-body'
>
- <div className='col-sm-12'>
- <div className='team-member-list'>
- {memberList}
- </div>
- </div>
+ {memberList}
</div>
<div className='modal-footer'>
<button
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
index fe744760f..70eb0a500 100644
--- a/web/react/components/member_list.jsx
+++ b/web/react/components/member_list.jsx
@@ -21,7 +21,8 @@ export default class MemberList extends React.Component {
}
return (
- <div className='member-list-holder'>
+ <table className='table more-table member-list-holder'>
+ <tbody>
{members.map(function mymembers(member) {
return (
<MemberListItem
@@ -34,8 +35,9 @@ export default class MemberList extends React.Component {
/>
);
}, this)}
+ </tbody>
{message}
- </div>
+ </table>
);
}
}
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index 8ed94680e..8251d67bc 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -37,7 +37,7 @@ export default class MemberListItem extends React.Component {
invite = (
<a
onClick={this.handleInvite}
- className='btn btn-sm btn-primary member-invite'
+ className='btn btn-sm btn-primary'
>
<i className='glyphicon glyphicon-envelope'/>
{' Add'}
@@ -102,17 +102,19 @@ export default class MemberListItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={'/api/v1/users/' + member.id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
- height='36'
- width='36'
- />
- <span className='member-name'>{member.username}</span>
- <span className='member-email'>{member.email}</span>
- {invite}
- </div>
+ <tr>
+ <td className='direct-channel'>
+ <img
+ className='profile-img pull-left'
+ src={'/api/v1/users/' + member.id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
+ height='36'
+ width='36'
+ />
+ <div className='member-name'>{member.username}</div>
+ <div className='member-description'>{member.email}</div>
+ </td>
+ <td className='td--action lg'>{invite}</td>
+ </tr>
);
}
}
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index 5ca40a39d..cb2d0660b 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -15,9 +15,11 @@ export default class MemberListTeam extends React.Component {
}, this);
return (
- <div className='member-list-holder'>
- {memberList}
- </div>
+ <table className='table more-table member-list-holder'>
+ <tbody>
+ {memberList}
+ </tbody>
+ </table>
);
}
}
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index b0232fc08..40deb37f2 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -206,7 +206,7 @@ export default class MoreDirectChannels extends React.Component {
return (
<Modal
- className='modal-direct-channels'
+ dialogClassName='more-modal'
show={this.props.show}
onHide={this.handleHide}
>
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index f43bdffdf..029b9c137 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -11,23 +11,15 @@ var AboutBuildModal = require('./about_build_modal.jsx');
var Constants = require('../utils/constants.jsx');
function getStateFromStores() {
- let teams = [];
- let teamsObject = UserStore.getTeams();
- for (let teamId in teamsObject) {
+ const teams = [];
+ const teamsObject = UserStore.getTeams();
+ for (const teamId in teamsObject) {
if (teamsObject.hasOwnProperty(teamId)) {
teams.push(teamsObject[teamId]);
}
}
- teams.sort(function sortByDisplayName(teamA, teamB) {
- let teamADisplayName = teamA.display_name.toLowerCase();
- let teamBDisplayName = teamB.display_name.toLowerCase();
- if (teamADisplayName < teamBDisplayName) {
- return -1;
- } else if (teamADisplayName > teamBDisplayName) {
- return 1;
- }
- return 0;
- });
+
+ teams.sort(Utils.sortByDisplayName);
return {teams};
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c47919885..aab9919a4 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -100,74 +100,56 @@ export default class Sidebar extends React.Component {
}
getStateFromStores() {
const members = ChannelStore.getAllMembers();
- var teamMemberMap = UserStore.getActiveOnlyProfiles();
- var currentId = ChannelStore.getCurrentId();
- const currentUserId = UserStore.getCurrentId();
+ const currentChannelId = ChannelStore.getCurrentId();
- var teammates = [];
- for (var id in teamMemberMap) {
- if (id === currentUserId) {
- continue;
- }
- teammates.push(teamMemberMap[id]);
- }
+ const channels = Object.assign([], ChannelStore.getAll());
+ const publicChannels = channels.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
+ const privateChannels = channels.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
+ const directChannels = channels.filter((channel) => channel.type === Constants.DM_CHANNEL);
const preferences = PreferenceStore.getPreferences(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
var visibleDirectChannels = [];
- var hiddenDirectChannelCount = 0;
- for (var i = 0; i < teammates.length; i++) {
- const teammate = teammates[i];
-
- if (teammate.id === currentUserId) {
+ for (var i = 0; i < directChannels.length; i++) {
+ const dm = directChannels[i];
+ const teammate = Utils.getDirectTeammate(dm.id);
+ if (!teammate) {
continue;
}
- const channelName = Utils.getDirectChannelName(currentUserId, teammate.id);
+ const member = members[dm.id];
+ const msgCount = dm.total_msg_count - member.msg_count;
- let forceShow = false;
- let channel = ChannelStore.getByName(channelName);
+ // always show a channel if either it is the current one or if it is unread, but it is not currently being left
+ const forceShow = (currentChannelId === dm.id || msgCount > 0) && !this.isLeaving.get(dm.id);
+ const preferenceShow = preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'));
- if (channel) {
- const member = members[channel.id];
- const msgCount = channel.total_msg_count - member.msg_count;
+ if (preferenceShow || forceShow) {
+ dm.display_name = Utils.displayUsername(teammate.id);
+ dm.teammate_id = teammate.id;
+ dm.status = UserStore.getStatus(teammate.id);
- // always show a channel if either it is the current one or if it is unread, but it is not currently being left
- forceShow = (currentId === channel.id || msgCount > 0) && !this.isLeaving.get(channel.id);
- } else {
- channel = {};
- channel.fake = true;
- channel.name = channelName;
- channel.last_post_at = 0;
- channel.total_msg_count = 0;
- channel.type = 'D';
- }
+ visibleDirectChannels.push(dm);
- channel.display_name = Utils.displayUsername(teammate.id);
- channel.teammate_id = teammate.id;
- channel.status = UserStore.getStatus(teammate.id);
-
- if (preferences.some((preference) => (preference.name === teammate.id && preference.value !== 'false'))) {
- visibleDirectChannels.push(channel);
- } else if (forceShow) {
- // make sure that unread direct channels are visible
- const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
- AsyncClient.savePreferences([preference]);
-
- visibleDirectChannels.push(channel);
- } else {
- hiddenDirectChannelCount += 1;
+ if (forceShow && !preferenceShow) {
+ // make sure that unread direct channels are visible
+ const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammate.id, 'true');
+ AsyncClient.savePreferences([preference]);
+ }
}
}
+ const hiddenDirectChannelCount = UserStore.getActiveOnlyProfileList().length - visibleDirectChannels.length;
+
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
return {
- activeId: currentId,
- channels: ChannelStore.getAll(),
+ activeId: currentChannelId,
members,
+ publicChannels,
+ privateChannels,
visibleDirectChannels,
hiddenDirectChannelCount,
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER
@@ -534,11 +516,9 @@ export default class Sidebar extends React.Component {
this.lastUnreadChannel = null;
// create elements for all 3 types of channels
- const publicChannels = this.state.channels.filter((channel) => channel.type === 'O');
- const publicChannelItems = publicChannels.map(this.createChannelElement);
+ const publicChannelItems = this.state.publicChannels.map(this.createChannelElement);
- const privateChannels = this.state.channels.filter((channel) => channel.type === 'P');
- const privateChannelItems = privateChannels.map(this.createChannelElement);
+ const privateChannelItems = this.state.privateChannels.map(this.createChannelElement);
const directMessageItems = this.state.visibleDirectChannels.map((channel, index, arr) => {
return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 3f777d93c..46730e1e6 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -62,6 +62,9 @@ export default class SidebarHeader extends React.Component {
<p>
{'Team administrators can also access their '}<strong>{'Team Settings'}</strong>{' from this menu.'}
</p>
+ <p>
+ {'System administrators will find a '}<strong>{'System Console'}</strong>{' option to administrate the entire system.'}
+ </p>
</div>
);
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 37760a2a2..516765a3f 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -46,7 +46,7 @@ export default class TeamSignUp extends React.Component {
} else {
teamListing = (
<div>
- <h3>{'Choose a Team'}</h3>
+ <h4>{'Choose a Team'}</h4>
<div className='signup-team-all'>
{
this.props.teams.map((team) => {
@@ -58,19 +58,18 @@ export default class TeamSignUp extends React.Component {
<a
href={'/' + team.name}
>
- <div className='signup-team-dir__group'>
- <span className='signup-team-dir__name'>{team.display_name}</span>
- <span
- className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
- aria-hidden='true'
- />
- </div>
+ <span className='signup-team-dir__name'>{team.display_name}</span>
+ <span
+ className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
+ aria-hidden='true'
+ />
</a>
</div>
);
})
}
</div>
+ <h4>{'Or Create a Team'}</h4>
</div>
);
}
diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx
index 8fc38b1e3..a99e9fe28 100644
--- a/web/react/components/tutorial/tutorial_intro_screens.jsx
+++ b/web/react/components/tutorial/tutorial_intro_screens.jsx
@@ -53,7 +53,7 @@ export default class TutorialIntroScreens extends React.Component {
<div>
<h3>{'Welcome to:'}</h3>
<h1>{'Mattermost'}</h1>
- <p>{'Your team communications all in one place, instantly searchable and available anywhere.'}</p>
+ <p>{'Your team communication all in one place, instantly searchable and available anywhere.'}</p>
<p>{'Keep your team connected to help them achieve what matters most.'}</p>
<div className='tutorial__circles'>
<div className='circle active'/>
@@ -68,7 +68,7 @@ export default class TutorialIntroScreens extends React.Component {
<div>
<h3>{'How Mattermost works:'}</h3>
<p>{'Communication happens in public discussion channels, private groups and direct messages.'}</p>
- <p>{'Everything is archived and searchable from any web-enabled laptop, tablet or phone.'}</p>
+ <p>{'Everything is archived and searchable from any web-enabled desktop, laptop or phone.'}</p>
<div className='tutorial__circles'>
<div className='circle'/>
<div className='circle active'/>
@@ -123,7 +123,7 @@ export default class TutorialIntroScreens extends React.Component {
</a>
{'.'}
</p>
- {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}
+ {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'}
<div className='tutorial__circles'>
<div className='circle'/>
<div className='circle'/>
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index d1f548d50..cc0d0d14b 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -4,6 +4,7 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
+var Utils;
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -183,16 +184,11 @@ class ChannelStoreClass extends EventEmitter {
channels.push(channel);
}
- channels.sort(function chanSort(a, b) {
- if (a.display_name.toLowerCase() < b.display_name.toLowerCase()) {
- return -1;
- }
- if (a.display_name.toLowerCase() > b.display_name.toLowerCase()) {
- return 1;
- }
- return 0;
- });
+ if (!Utils) {
+ Utils = require('../utils/utils.jsx'); //eslint-disable-line global-require
+ }
+ channels.sort(Utils.sortByDisplayName);
this.pStoreChannels(channels);
}
pStoreChannels(channels) {
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index ce80c5ec9..aedb3dc09 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -204,12 +204,13 @@ class UserStoreClass extends EventEmitter {
}
getActiveOnlyProfiles() {
- var active = {};
- var current = this.getProfiles();
+ const active = {};
+ const profiles = this.getProfiles();
+ const currentId = this.getCurrentId();
- for (var key in current) {
- if (current[key].delete_at === 0) {
- active[key] = current[key];
+ for (var key in profiles) {
+ if (profiles[key].delete_at === 0 && profiles[key].id !== currentId) {
+ active[key] = profiles[key];
}
}
@@ -219,9 +220,10 @@ class UserStoreClass extends EventEmitter {
getActiveOnlyProfileList() {
const profileMap = this.getActiveOnlyProfiles();
const profiles = [];
+ const currentId = this.getCurrentId();
for (const id in profileMap) {
- if (profileMap.hasOwnProperty(id)) {
+ if (profileMap.hasOwnProperty(id) && id !== currentId) {
profiles.push(profileMap[id]);
}
}
@@ -235,6 +237,14 @@ class UserStoreClass extends EventEmitter {
BrowserStore.setItem('profiles', ps);
}
+ saveProfiles(profiles) {
+ const currentId = this.getCurrentId();
+ if (currentId in profiles) {
+ delete profiles[currentId];
+ }
+ BrowserStore.setItem('profiles', profiles);
+ }
+
setSessions(sessions) {
BrowserStore.setItem('sessions', sessions);
}
@@ -320,15 +330,8 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) {
case ActionTypes.RECIEVED_PROFILES:
- for (var id in action.profiles) {
- // profiles can have incomplete data, so don't overwrite current user
- if (id === UserStore.getCurrentId()) {
- continue;
- }
- var profile = action.profiles[id];
- UserStore.saveProfile(profile);
- UserStore.emitChange(profile.id);
- }
+ UserStore.saveProfiles(action.profiles);
+ UserStore.emitChange();
break;
case ActionTypes.RECIEVED_ME:
UserStore.setCurrentUser(action.me);
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 75dd35e3f..205c7461c 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -588,13 +588,23 @@ export function getMe() {
}
export function getStatuses() {
- if (isCallInProgress('getStatuses')) {
+ const directChannels = ChannelStore.getAll().filter((channel) => channel.type === Constants.DM_CHANNEL);
+
+ const teammateIds = [];
+ for (var i = 0; i < directChannels.length; i++) {
+ const teammate = utils.getDirectTeammate(directChannels[i].id);
+ if (teammate) {
+ teammateIds.push(teammate.id);
+ }
+ }
+
+ if (isCallInProgress('getStatuses') || teammateIds.length === 0) {
return;
}
callTracker.getStatuses = utils.getTimestamp();
- client.getStatuses(
- function getStatusesSuccess(data, textStatus, xhr) {
+ client.getStatuses(teammateIds,
+ (data, textStatus, xhr) => {
callTracker.getStatuses = 0;
if (xhr.status === 304 || !data) {
@@ -606,7 +616,7 @@ export function getStatuses() {
statuses: data
});
},
- function getStatusesFailure(err) {
+ (err) => {
callTracker.getStatuses = 0;
dispatchError(err, 'getStatuses');
}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 7ce1346f9..003e24d33 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1069,12 +1069,13 @@ export function exportTeam(success, error) {
});
}
-export function getStatuses(success, error) {
+export function getStatuses(ids, success, error) {
$.ajax({
url: '/api/v1/users/status',
dataType: 'json',
contentType: 'application/json',
- type: 'GET',
+ type: 'POST',
+ data: JSON.stringify(ids),
success,
error: function onError(xhr, status, err) {
var e = handleError('getStatuses', xhr, status, err);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index fd64b1554..39be577df 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -127,6 +127,7 @@ module.exports = {
MAX_DMS: 20,
DM_CHANNEL: 'D',
OPEN_CHANNEL: 'O',
+ PRIVATE_CHANNEL: 'P',
INVITE_TEAM: 'I',
OPEN_TEAM: 'O',
MAX_POST_LEN: 4000,
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index f9416b2de..3ef09211f 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -208,7 +208,8 @@ export function format(text, options) {
const markdownOptions = {
renderer: new MattermostMarkdownRenderer(null, options),
sanitize: true,
- gfm: true
+ gfm: true,
+ tables: true
};
const tokens = marked.lexer(text, markdownOptions);
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index c82bd1065..e8d34dccd 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1104,3 +1104,24 @@ export function openDirectChannelToUser(user, successCb, errorCb) {
);
}
}
+
+// Use when sorting multiple channels or teams by their `display_name` field
+export function sortByDisplayName(a, b) {
+ let aDisplayName = '';
+ let bDisplayName = '';
+
+ if (a && a.display_name) {
+ aDisplayName = a.display_name.toLowerCase();
+ }
+ if (b && b.display_name) {
+ bDisplayName = b.display_name.toLowerCase();
+ }
+
+ if (aDisplayName < bDisplayName) {
+ return -1;
+ }
+ if (aDisplayName > bDisplayName) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 9314b4980..0333e0c65 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -149,6 +149,12 @@
@include opacity(0.8);
margin: 5px 0;
}
+ .profile-img {
+ -moz-border-radius: 50px;
+ -webkit-border-radius: 50px;
+ border-radius: 50px;
+ margin-right: 8px;
+ }
.more-name {
font-weight: 600;
font-size: 0.95em;
@@ -230,8 +236,8 @@
position: relative;
&:hover .file-playback-controls.stop {
- @include opacity(1);
- }
+ @include opacity(1);
+ }
}
img {
max-width: 100%;
@@ -347,7 +353,7 @@
}
}
-.modal-direct-channels {
+.more-modal {
.user-list {
margin-top: 10px;
@@ -362,11 +368,12 @@
}
.modal-body {
- padding: 20px 0 0;
+ padding: 10px 0 0;
@include clearfix;
}
.filter-row {
+ margin-top: 10px;
padding: 0 15px;
}
@@ -379,11 +386,4 @@
.more-purpose {
@include opacity(0.7);
}
-
- .profile-img {
- -moz-border-radius: 50px;
- -webkit-border-radius: 50px;
- border-radius: 50px;
- margin-right: 8px;
- }
}
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 8f25f58cd..339412b45 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -100,7 +100,7 @@
.date-separator, .new-separator {
&.hovered--comment {
&:before, &:after {
- background: none;
+ background: none !important;
}
}
}
diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss
index 84f9892f4..b9486e254 100644
--- a/web/sass-files/sass/partials/_signup.scss
+++ b/web/sass-files/sass/partials/_signup.scss
@@ -314,32 +314,39 @@
}
.signup-team-all {
- width: 280px;
- box-shadow: 3px 3px 1px #d5d5d5;
- margin: 0px 0px 50px 5px;
-}
-
-.signup-team-dir {
- background: #fafafa;
- border-bottom: 1px solid #d5d5d5;
-}
-
-.signup-team-dir__group {
- padding: 15px 10px 15px 10px;
-}
-
-.signup-team-dir__name {
- line-height: 1.3 !important;
- font-size: 1.5em !important;
- font-weight: 300 !important;
+ margin: 0 0 20px;
+ border: 1px solid #ddd;
+ @include border-radius(2px);
+ .signup-team-dir {
+ background: #fafafa;
+ border-top: 1px solid #d5d5d5;
+ &:first-child {
+ border: none;
+ }
+ a {
+ color: inherit;
+ display: block;
+ padding: 0 15px;
+ line-height: 3.5em;
+ height: 3.5em;
+ font-size: 1.1em;
+ }
+ }
+ .signup-team-dir__name {
+ white-space: nowrap;
+ float: left;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 90%;
+ }
+ .signup-team-dir__arrow {
+ float: right;
+ font-size: 0.9em;
+ color: #999;
+ line-height: 3.5em;
+ }
}
-.signup-team-dir__arrow {
- float: right;
- line-height: 1.3 !important;
- font-size: 1.5em !important;
- font-weight: 300 !important;
-}
.authorize-box {
margin: 100px auto;
diff --git a/web/templates/channel.html b/web/templates/channel.html
index 63fe38587..aabd01ecb 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -37,7 +37,12 @@
<script>
window.setup_channel_page({{ .Props }});
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
- $('.modal-body').css('max-height', $(window).height() - 200);
+ if($(window).height() > 1200){
+ $('.modal-body').css('max-height', 1000);
+ }
+ else {
+ $('.modal-body').css('max-height', $(window).height() - 200);
+ }
$('.modal-body').perfectScrollbar();
</script>
</body>