diff options
-rw-r--r-- | api/context.go | 10 | ||||
-rw-r--r-- | api/post.go | 11 | ||||
-rw-r--r-- | api/post_test.go | 14 | ||||
-rw-r--r-- | api/user.go | 2 | ||||
-rw-r--r-- | model/user.go | 1 | ||||
-rw-r--r-- | store/sql_user_store.go | 8 | ||||
-rw-r--r-- | store/store.go | 2 | ||||
-rw-r--r-- | web/react/components/channel_header.jsx | 33 | ||||
-rw-r--r-- | web/react/components/navbar.jsx | 159 | ||||
-rw-r--r-- | web/react/components/post_info.jsx | 3 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 3 | ||||
-rw-r--r-- | web/react/components/sidebar_header.jsx | 48 | ||||
-rw-r--r-- | web/react/components/user_settings.jsx | 9 | ||||
-rw-r--r-- | web/react/stores/channel_store.jsx | 53 | ||||
-rw-r--r-- | web/react/stores/user_store.jsx | 2 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 8 | ||||
-rw-r--r-- | web/sass-files/sass/partials/_navbar.scss | 1 | ||||
-rw-r--r-- | web/templates/head.html | 3 |
18 files changed, 194 insertions, 176 deletions
diff --git a/api/context.go b/api/context.go index bea0fbeff..054e42e2e 100644 --- a/api/context.go +++ b/api/context.go @@ -265,6 +265,16 @@ func (c *Context) IsSystemAdmin() bool { return false } +func (c *Context) IsTeamAdmin(userId string) bool { + if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil { + c.Err = uresult.Err + return false + } else { + user := uresult.Data.(*model.User) + return strings.Contains(c.Session.Roles, model.ROLE_ADMIN) && user.TeamId == c.Session.TeamId + } +} + func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { sessionCache.Remove(c.Session.Id) diff --git a/api/post.go b/api/post.go index 02f997166..efca2f570 100644 --- a/api/post.go +++ b/api/post.go @@ -634,16 +634,17 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) pchan := Srv.Store.Post().Get(postId) - if !c.HasPermissionsToChannel(cchan, "deletePost") { - return - } - if result := <-pchan; result.Err != nil { c.Err = result.Err return } else { + post := result.Data.(*model.PostList).Posts[postId] + if !c.HasPermissionsToChannel(cchan, "deletePost") && !c.IsTeamAdmin(post.UserId){ + return + } + if post == nil { c.SetInvalidParam("deletePost", "postId") return @@ -655,7 +656,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if post.UserId != c.Session.UserId { + if post.UserId != c.Session.UserId && !strings.Contains(c.Session.Roles,model.ROLE_ADMIN) { c.Err = model.NewAppError("deletePost", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden return diff --git a/api/post_test.go b/api/post_test.go index 970307759..5009ff54d 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -483,6 +483,10 @@ func TestDeletePosts(t *testing.T) { team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + userAdmin := &model.User{TeamId: team.Id, Email: team.Email, FullName: "Corey Hulen", Password: "pwd"} + userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id)) + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) @@ -521,8 +525,16 @@ func TestDeletePosts(t *testing.T) { r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList) if len(r2.Posts) != 4 { - t.Fatal("should have returned 5 items") + t.Fatal("should have returned 4 items") } + + time.Sleep(10 * time.Millisecond) + post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} + post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) + + Client.LoginByEmail(team.Domain, userAdmin.Email, "pwd") + + Client.Must(Client.DeletePost(channel1.Id, post4.Id)) } func TestEmailMention(t *testing.T) { diff --git a/api/user.go b/api/user.go index 0c63868b3..3c0062f8c 100644 --- a/api/user.go +++ b/api/user.go @@ -729,7 +729,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { return } - Srv.Store.User().UpdateUpdateAt(c.Session.UserId) + Srv.Store.User().UpdateLastPictureUpdate(c.Session.UserId) c.LogAudit("") } diff --git a/model/user.go b/model/user.go index b94ceb899..c5a4d846d 100644 --- a/model/user.go +++ b/model/user.go @@ -45,6 +45,7 @@ type User struct { Props StringMap `json:"props"` NotifyProps StringMap `json:"notify_props"` LastPasswordUpdate int64 `json:"last_password_update"` + LastPictureUpdate int64 `json:"last_picture_update"` } // IsValid validates the user and returns an error if it isn't configured diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 77470946c..665e4d697 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -37,6 +37,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore { } func (s SqlUserStore) UpgradeSchemaIfNeeded() { + s.CreateColumnIfNotExists("Users","LastPictureUpdate", "LastPasswordUpdate", "bigint(20)", "0") } func (us SqlUserStore) CreateIndexesIfNotExists() { @@ -120,6 +121,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha user.AuthData = oldUser.AuthData user.Password = oldUser.Password user.LastPasswordUpdate = oldUser.LastPasswordUpdate + user.LastPictureUpdate = oldUser.LastPictureUpdate user.TeamId = oldUser.TeamId user.LastActivityAt = oldUser.LastActivityAt user.LastPingAt = oldUser.LastPingAt @@ -150,13 +152,15 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha return storeChannel } -func (us SqlUserStore) UpdateUpdateAt(userId string) StoreChannel { +func (us SqlUserStore) UpdateLastPictureUpdate(userId string) StoreChannel { storeChannel := make(StoreChannel) go func() { result := StoreResult{} - if _, err := us.GetMaster().Exec("UPDATE Users SET UpdateAt = ? WHERE Id = ?", model.GetMillis(), userId); err != nil { + curTime := model.GetMillis() + + if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = ?, UpdateAt = ? WHERE Id = ?", curTime, curTime, 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/store/store.go b/store/store.go index 0ed045788..9faa6a9d7 100644 --- a/store/store.go +++ b/store/store.go @@ -77,7 +77,7 @@ type PostStore interface { type UserStore interface { Save(user *model.User) StoreChannel Update(user *model.User, allowRoleUpdate bool) StoreChannel - UpdateUpdateAt(userId string) StoreChannel + UpdateLastPictureUpdate(userId string) StoreChannel UpdateLastPingAt(userId string, time int64) StoreChannel UpdateLastActivityAt(userId string, time int64) StoreChannel UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 836454b46..2e430489f 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -142,10 +142,10 @@ module.exports = React.createClass({ return null; } - var description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true}); - var popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>); - var channelTitle = this.state.channel.display_name; - var channelName = this.state.channel.name; + var channel = this.state.channel; + var description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true}); + var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); + var channelTitle = channel.display_name; var currentId = UserStore.getCurrentId(); var isAdmin = this.state.memberChannel.roles.indexOf("admin") > -1 || this.state.memberTeam.roles.indexOf("admin") > -1; var isDirect = (this.state.channel.type === 'D'); @@ -169,23 +169,26 @@ module.exports = React.createClass({ <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span> </a> <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={this.state.channel.id} href="#">View Info</a></li> - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> - { isAdmin ? + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={channel.id} href="#">View Info</a></li> + { !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> + : null + } + { isAdmin && !ChannelStore.isDefault(channel) ? <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li> : null } - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={channelTitle} data-channelid={this.state.channel.id}>Set Channel Description...</a></li> - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channelTitle} data-channelid={this.state.channel.id}>Notification Preferences</a></li> - { isAdmin && channelName != Constants.DEFAULT_CHANNEL ? - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channelTitle} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li> + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li> + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li> + { isAdmin && !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li> : null } - { isAdmin && channelName != Constants.DEFAULT_CHANNEL ? - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channelTitle} data-channelid={this.state.channel.id}>Delete Channel...</a></li> + { isAdmin && !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li> : null } - { channelName != Constants.DEFAULT_CHANNEL ? + { !ChannelStore.isDefault(channel) ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li> : null } @@ -197,7 +200,7 @@ module.exports = React.createClass({ <a href="#"><strong className="heading">{channelTitle}</strong></a> } </th> - <th><PopoverListMembers members={this.state.users} channelId={this.state.channel.id} /></th> + <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th> <th className="search-bar__container"><NavbarSearchBox /></th> <th> <div className="dropdown channel-header__links"> diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 35f7d9044..78cf7d8b8 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -2,21 +2,20 @@ // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -var Sidebar = require('./sidebar.jsx'); var UserStore = require('../stores/user_store.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); -var Constants = require('../utils/constants.jsx'); + var UserProfile = require('./user_profile.jsx'); var MessageWrapper = require('./message_wrapper.jsx'); + +var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); function getCountsStateFromStores() { - var count = 0; var channels = ChannelStore.getAll(); var members = ChannelStore.getAllMembers(); @@ -34,7 +33,7 @@ function getCountsStateFromStores() { } }); - return { count: count } + return { count: count }; } var NotifyCounts = React.createClass({ @@ -54,11 +53,10 @@ var NotifyCounts = React.createClass({ return getCountsStateFromStores(); }, render: function() { - if (this.state.count == 0) { - return (<span></span>); - } - else { - return (<span className="badge badge-notify">{ this.state.count }</span>); + if (this.state.count) { + return <span className="badge badge-notify">{ this.state.count }</span>; + } else { + return null; } } }); @@ -66,25 +64,25 @@ var NotifyCounts = React.createClass({ var NavbarLoginForm = React.createClass({ handleSubmit: function(e) { e.preventDefault(); - var state = { } + var state = { }; var domain = this.refs.domain.getDOMNode().value.trim(); if (!domain) { - state.server_error = "A domain is required" + state.server_error = "A domain is required"; this.setState(state); return; } var email = this.refs.email.getDOMNode().value.trim(); if (!email) { - state.server_error = "An email is required" + state.server_error = "An email is required"; this.setState(state); return; } var password = this.refs.password.getDOMNode().value.trim(); if (!password) { - state.server_error = "A password is required" + state.server_error = "A password is required"; this.setState(state); return; } @@ -105,7 +103,7 @@ var NavbarLoginForm = React.createClass({ window.location.href = '/channels/town-square'; } - }.bind(this), + }, function(err) { if (err.message == "Login failed because email address has not been verified") { window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email); @@ -159,13 +157,14 @@ function getStateFromStores() { } module.exports = React.createClass({ + displayName: 'Navbar', + componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); ChannelStore.addExtraInfoChangeListener(this._onChange); - var self = this; - $('.inner__wrap').click(self.hideSidebars); + $('.inner__wrap').click(this.hideSidebars); - $('body').on('click.infopopover', function(e){ + $('body').on('click.infopopover', function(e) { if ($(e.target).attr('data-toggle') !== 'popover' && $(e.target).parents('.popover.in').length === 0) { $('.info-popover').popover('hide'); @@ -181,13 +180,13 @@ module.exports = React.createClass({ }, handleLeave: function(e) { client.leaveChannel(this.state.channel.id, - function(data) { + function() { AsyncClient.getChannels(true); window.location.href = '/channels/town-square'; - }.bind(this), + }, function(err) { AsyncClient.dispatchError(err, "handleLeave"); - }.bind(this) + } ); }, hideSidebars: function(e) { @@ -204,7 +203,7 @@ module.exports = React.createClass({ }); if (e.target.className != 'navbar-toggle' && e.target.className != 'icon-bar') { - $('.inner__wrap').removeClass('move--right').removeClass('move--left').removeClass('move--left-small'); + $('.inner__wrap').removeClass('move--right move--left move--left-small'); $('.sidebar--left').removeClass('move--right'); $('.sidebar--right').removeClass('move--left'); $('.sidebar--menu').removeClass('move--left'); @@ -229,24 +228,22 @@ module.exports = React.createClass({ render: function() { var currentId = UserStore.getCurrentId(); - var channelName = ""; var popoverContent = ""; var channelTitle = this.props.teamName; var isAdmin = false; var isDirect = false; var description = "" + var channel = this.state.channel; - if (this.state.channel) { - var channel = this.state.channel; - description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true}); - popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>); - channelName = this.state.channel.name; + if (channel) { + description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true}); + popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); isAdmin = this.state.member.roles.indexOf("admin") > -1; if (channel.type === 'O') { - channelTitle = this.state.channel.display_name; + channelTitle = channel.display_name; } else if (channel.type === 'P') { - channelTitle = this.state.channel.display_name; + channelTitle = channel.display_name; } else if (channel.type === 'D') { isDirect = true; if (this.state.users.length > 1) { @@ -258,12 +255,11 @@ module.exports = React.createClass({ } } - if(this.state.channel.description.length == 0){ - popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>); + if (channel.description.length == 0) { + popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>); } } - var loginForm = currentId == null ? <NavbarLoginForm /> : null; var navbar_collapse_button = currentId != null ? null : <button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1"> <span className="sr-only">Toggle sidebar</span> @@ -292,60 +288,61 @@ module.exports = React.createClass({ { navbar_collapse_button } { sidebar_collapse_button } { right_sidebar_collapse_button } - { !isDirect && this.state.channel ? - <div className="navbar-brand"> - <div className="dropdown"> - <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div> - <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> - <strong className="heading">{channelTitle} </strong> - <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span> - </a> - <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> - { isAdmin ? - <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li> - : "" - } - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Set Channel Description...</a></li> - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Notification Preferences</a></li> - { isAdmin && channelName != Constants.DEFAULT_CHANNEL ? - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={this.state.channel.display_name} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li> - : "" - } - { isAdmin && channelName != Constants.DEFAULT_CHANNEL ? - <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Delete Channel...</a></li> - : "" - } - { channelName != Constants.DEFAULT_CHANNEL ? - <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li> - : "" - } - </ul> + { !isDirect && channel ? + <div className="navbar-brand"> + <div className="dropdown"> + <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div> + <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> + <span className="heading">{channelTitle} </span> + <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span> + </a> + <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> + { !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li> + : null + } + { isAdmin && !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li> + : null + } + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li> + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li> + { isAdmin && !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li> + : null + } + { isAdmin && !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li> + : null + } + { !ChannelStore.isDefault(channel) ? + <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li> + : null + } + </ul> + </div> </div> - </div> - : "" } - { isDirect && this.state.channel ? + : null + } + { isDirect && channel ? <div className="navbar-brand"> - <strong> - <a href="#"><strong className="heading">{channelTitle}</strong></a> - </strong> + <a href="#" className="heading">{ channelTitle }</a> </div> - : "" } - { !this.state.channel ? + : null } + { !channel ? <div className="navbar-brand"> - <strong> - <a href="/"><strong className="heading">{ channelTitle }</strong></a> - </strong> + <a href="/" className="heading">{ channelTitle }</a> </div> - : "" } - </div> - <div className="collapse navbar-collapse" id="navbar-collapse-1"> - { loginForm } + : null } </div> + { !currentId ? + <div className="collapse navbar-collapse" id="navbar-collapse-1"> + <NavbarLoginForm /> + </div> + : null + } </div> </nav> ); -} + } }); - - diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index cf01747f0..48efa95ba 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -11,6 +11,7 @@ module.exports = React.createClass({ render: function() { var post = this.props.post; var isOwner = UserStore.getCurrentId() == post.user_id; + var isAdmin = UserStore.getCurrentUser().roles.indexOf("admin") > -1 var type = "Post" if (post.root_id.length > 0) { @@ -36,7 +37,7 @@ module.exports = React.createClass({ <ul className="dropdown-menu" role="menu"> { isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Edit</a></li> : "" } - { isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li> + { isOwner || isAdmin ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li> : "" } { this.props.allowReply === "true" ? <li role="presentation"><a className="reply-link theme" href="#" onClick={this.props.handleCommentClick}>Reply</a></li> : "" } diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 98491976f..573799a19 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -6,7 +6,6 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var UserProfile = require( './user_profile.jsx' ); var AsyncClient = require('../utils/async_client.jsx'); -var CreatePost = require('./create_post.jsx'); var Post = require('./post.jsx'); var LoadingScreen = require('./loading_screen.jsx'); var SocketStore = require('../stores/socket_store.jsx'); @@ -341,7 +340,7 @@ module.exports = React.createClass({ } } - if (channel.name === Constants.DEFAULT_CHANNEL) { + if (ChannelStore.isDefault(channel)) { more_messages = ( <div className="channel-intro"> <h4 className="channel-intro-title">Welcome</h4> diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 83ad4470e..45c9ca629 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -37,17 +37,13 @@ var NavbarDropdown = React.createClass({ var invite_link = ""; var manage_link = ""; var rename_link = ""; - var currentUser = UserStore.getCurrentUser() + var currentUser = UserStore.getCurrentUser(); var isAdmin = false; if (currentUser != null) { isAdmin = currentUser.roles.indexOf("admin") > -1; - invite_link = ( - <li> - <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a> - </li> - ); + invite_link = ( <li> <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a> </li>); if (this.props.teamType == "O") { team_link = ( @@ -59,16 +55,8 @@ var NavbarDropdown = React.createClass({ } if (isAdmin) { - manage_link = ( - <li> - <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a> - </li> - ); - rename_link = ( - <li> - <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a> - </li> - ); + manage_link = ( <li> <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a> </li>); + rename_link = ( <li> <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a> </li>); } var teams = []; @@ -95,7 +83,7 @@ var NavbarDropdown = React.createClass({ </a> <ul className="dropdown-menu" role="menu"> <li><a href="#" data-toggle="modal" data-target="#user_settings1">Account Settings</a></li> - { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : "" } + { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : null } { invite_link } { team_link } { manage_link } @@ -113,28 +101,30 @@ var NavbarDropdown = React.createClass({ }); module.exports = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - }, - getInitialState: function() { - return { }; + displayName: 'SidebarHeader', + + getDefaultProps: function() { + return { + teamName: config.SiteName + }; }, + render: function() { - var teamName = this.props.teamName ? this.props.teamName : config.SiteName; - var me = UserStore.getCurrentUser() + var me = UserStore.getCurrentUser(); + + if (!me) { + return null; + } return ( <div className="team__header theme"> <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} /> <div className="header__info"> - <div className="user__name">@{me.username}</div> - <a className="team__name" href="/channels/town-square">{ teamName }</a> + <div className="user__name">{'@' + me.username}</div> + <a className="team__name" href="/channels/town-square">{this.props.teamName}</a> </div> <NavbarDropdown teamType={this.props.teamType} /> </div> ); } }); - - - diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 17afadce2..38e4b1aea 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -820,6 +820,7 @@ var GeneralTab = React.createClass({ client.uploadProfileImage(formData, function(data) { this.submitActive = false; + AsyncClient.getMe(); window.location.reload(); }.bind(this), function(err) { @@ -989,7 +990,7 @@ var GeneralTab = React.createClass({ <SettingPicture title="Profile Picture" submit={this.submitPicture} - src={"/api/v1/users/" + user.id + "/image"} + src={"/api/v1/users/" + user.id + "/image?time=" + user.last_picture_update} server_error={server_error} client_error={client_error} updateSection={function(e){self.updateSection("");e.preventDefault();}} @@ -1000,10 +1001,14 @@ var GeneralTab = React.createClass({ ); } else { + var minMessage = "Click Edit to upload an image."; + if (user.last_picture_update) { + minMessage = "Image last updated " + utils.displayDate(user.last_picture_update) + } pictureSection = ( <SettingItemMin title="Profile Picture" - describe="Picture inside." + describe={minMessage} updateSection={function(){self.updateSection("picture");}} /> ); diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 4a27e5f17..a97f13391 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -44,40 +44,24 @@ var ChannelStore = assign({}, EventEmitter.prototype, { removeExtraInfoChangeListener: function(callback) { this.removeListener(EXTRA_INFO_EVENT, callback); }, - get: function(id) { - var current = null; - var c = this._getChannels(); - - c.some(function(channel) { - if (channel.id == id) { - current = channel; - return true; + findFirstBy: function(field, value) { + var channels = this._getChannels(); + for (var i = 0; i < channels.length; i++) { + if (channels[i][field] == value) { + return channels[i]; } - return false; - }); + } - return current; + return null; + }, + get: function(id) { + return this.findFirstBy('id', id); }, getMember: function(id) { - var current = null; return this.getAllMembers()[id]; }, getByName: function(name) { - var current = null; - var c = this._getChannels(); - - c.some(function(channel) { - if (channel.name == name) { - current = channel; - return true; - } - - return false; - - }); - - return current; - + return this.findFirstBy('name', name); }, getAll: function() { return this._getChannels(); @@ -120,7 +104,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { getCurrent: function() { var currentId = this.getCurrentId(); - if (currentId != null) + if (currentId) return this.get(currentId); else return null; @@ -128,7 +112,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { getCurrentMember: function() { var currentId = ChannelStore.getCurrentId(); - if (currentId != null) + if (currentId) return this.getAllMembers()[currentId]; else return null; @@ -143,7 +127,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { var currentId = ChannelStore.getCurrentId(); var extra = null; - if (currentId != null) + if (currentId) extra = this._getExtraInfos()[currentId]; if (extra == null) @@ -154,7 +138,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { getExtraInfo: function(channel_id) { var extra = null; - if (channel_id != null) + if (channel_id) extra = this._getExtraInfos()[channel_id]; if (extra == null) @@ -192,7 +176,10 @@ var ChannelStore = assign({}, EventEmitter.prototype, { }, _getExtraInfos: function() { return BrowserStore.getItem("extra_infos", {}); - } + }, + isDefault: function(channel) { + return channel.name == Constants.DEFAULT_CHANNEL; + } }); ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { @@ -231,4 +218,4 @@ ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { } }); -module.exports = ChannelStore; +module.exports = ChannelStore;
\ No newline at end of file diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index 93ddfec70..dd207ca80 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -177,7 +177,7 @@ var UserStore = assign({}, EventEmitter.prototype, { }, getCurrentMentionKeys: function() { var user = this.getCurrentUser(); - if (user.notify_props && user.notify_props.mention_keys) { + if (user && user.notify_props && user.notify_props.mention_keys) { var keys = user.notify_props.mention_keys.split(','); if (user.full_name.length > 0 && user.notify_props.first_name === "true") { diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 6cae7fe89..7186251e7 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -198,7 +198,13 @@ module.exports.getTimestamp = function() { } var testUrlMatch = function(text) { - var urlMatcher = new Autolinker.matchParser.MatchParser; + var urlMatcher = new Autolinker.matchParser.MatchParser({ + urls: true, + emails: false, + twitter: false, + phone: false, + hashtag: false, + }); var result = []; var replaceFn = function(match) { var linkData = {}; diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss index 62864afb7..6d8f11ce3 100644 --- a/web/sass-files/sass/partials/_navbar.scss +++ b/web/sass-files/sass/partials/_navbar.scss @@ -43,6 +43,7 @@ font-size: 16px; .heading { margin-right: 3px; + font-weight: 600; color: #fff; } .header-dropdown__icon { diff --git a/web/templates/head.html b/web/templates/head.html index ead648571..5b423d487 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -31,7 +31,6 @@ <link rel="stylesheet" href="/static/css/styles.css"> <script src="/static/js/perfect-scrollbar-0.6.3.jquery.js"></script> - <script src="/static/js/bundle.js"></script> <script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['annotationchart']}]}"></script> @@ -102,5 +101,7 @@ } </script> <!-- Snowplow stops plowing --> + + <script src="/static/js/bundle.js"></script> </head> {{end}} |