diff options
41 files changed, 414 insertions, 258 deletions
diff --git a/.gitignore b/.gitignore index ebd5e4342..fc9076e69 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ node_modules dist npm-debug.log -bundle*.js +web/static/js/bundle*.js +web/static/js/libs*.js model/version.go model/version.go.bak @@ -43,6 +43,7 @@ travis: @$(GO) clean $(GOFLAGS) -i ./... @cd web/react/ && npm install + cd web/react/ && npm run build-libs @echo Checking for style guide compliance cd web/react && $(ESLINT) --quiet components/* dispatcher/* pages/* stores/* utils/* @@ -83,10 +84,11 @@ travis: mkdir -p web/static/js cd web/react && npm run build - cd web/sass-files && compass compile + cd web/sass-files && compass compile -e production --force - mkdir -p $(DIST_PATH)/web - cp -RL web/static $(DIST_PATH)/web + mkdir -p $(DIST_PATH)/web/static/js + cp -L web/static/js/*.min.js $(DIST_PATH)/web/static/js/ + cp -RL web/static/js/jquery-dragster $(DIST_PATH)/web/static/js/ cp -RL web/templates $(DIST_PATH)/web mkdir -p $(DIST_PATH)/api @@ -97,6 +99,7 @@ travis: cp README.md $(DIST_PATH) mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js + mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js @sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html @@ -104,6 +107,7 @@ travis: @sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html + @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html rm $(DIST_PATH)/web/templates/*.bak mv doc/README.md doc/index.md @@ -129,6 +133,7 @@ install: fi @cd web/react/ && npm install + @cd web/react/ && npm run build-libs check: install @echo Running ESLint... @@ -185,6 +190,7 @@ clean: rm -rf web/react/node_modules rm -f web/static/js/bundle*.js + rm -f web/static/js/libs*.js rm -f web/static/css/styles.css rm -rf data/* @@ -257,7 +263,7 @@ dist: install mkdir -p web/static/js cd web/react && npm run build - cd web/sass-files && compass compile + cd web/sass-files && compass compile -e production --force mkdir -p $(DIST_PATH)/web cp -RL web/static $(DIST_PATH)/web @@ -271,6 +277,7 @@ dist: install cp README.md $(DIST_PATH) mv $(DIST_PATH)/web/static/js/bundle.min.js $(DIST_PATH)/web/static/js/bundle-$(BUILD_NUMBER).min.js + mv $(DIST_PATH)/web/static/js/libs.min.js $(DIST_PATH)/web/static/js/libs-$(BUILD_NUMBER).min.js @sed -i'.bak' 's|react-with-addons-0.13.3.js|react-with-addons-0.13.3.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|jquery-1.11.1.js|jquery-1.11.1.min.js|g' $(DIST_PATH)/web/templates/head.html @@ -278,6 +285,7 @@ dist: install @sed -i'.bak' 's|react-bootstrap-0.25.1.js|react-bootstrap-0.25.1.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|perfect-scrollbar.js|perfect-scrollbar.min.js|g' $(DIST_PATH)/web/templates/head.html @sed -i'.bak' 's|bundle.js|bundle-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html + @sed -i'.bak' 's|libs.min.js|libs-$(BUILD_NUMBER).min.js|g' $(DIST_PATH)/web/templates/head.html rm $(DIST_PATH)/web/templates/*.bak tar -C dist -czf $(DIST_PATH).tar.gz mattermost diff --git a/api/channel.go b/api/channel.go index 5e13fa18a..0d22d7c00 100644 --- a/api/channel.go +++ b/api/channel.go @@ -472,6 +472,8 @@ func leaveChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } + UpdateChannelAccessCacheAndForget(c.Session.TeamId, c.Session.UserId, channel.Id) + post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf( `%v has left the channel.`, user.Username), Type: model.POST_JOIN_LEAVE} @@ -706,20 +708,21 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { return } - post := &model.Post{ChannelId: id, Message: fmt.Sprintf( - `%v added to the channel by %v`, - nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE} - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error("Failed to post add message %v", err) - c.Err = model.NewAppError("addChannelMember", "Failed to add member to channel", "") - return - } - c.LogAudit("name=" + channel.Name + " user_id=" + userId) - message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_ADDED) + go func() { + post := &model.Post{ChannelId: id, Message: fmt.Sprintf( + `%v added to the channel by %v`, + nUser.Username, oUser.Username), Type: model.POST_JOIN_LEAVE} + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error("Failed to post add member to channel message, err=%v", err) + } - PublishAndForget(message) + UpdateChannelAccessCache(c.Session.TeamId, userId, channel.Id) + message := model.NewMessage(c.Session.TeamId, channel.Id, userId, model.ACTION_USER_ADDED) + + PublishAndForget(message) + }() <-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id) w.Write([]byte(cm.ToJson())) @@ -773,13 +776,17 @@ 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) + go func() { + UpdateChannelAccessCache(c.Session.TeamId, userId, id) + + message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_REMOVED) + message.Add("channel_id", id) + message.Add("remover", c.Session.UserId) + PublishAndForget(message) + }() + result := make(map[string]string) result["channel_id"] = channel.Id result["removed_user_id"] = userId diff --git a/api/user.go b/api/user.go index 2d7dd9ab1..78f8768a4 100644 --- a/api/user.go +++ b/api/user.go @@ -991,7 +991,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() { - c.Err = model.NewAppError("updateRoles", "The system_admin role can only be set by another system admin", "") + c.Err = model.NewAppError("updateRoles", "The system admin role can only be set by another system admin", "") c.Err.StatusCode = http.StatusForbidden return } @@ -1014,6 +1014,12 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { return } + if user.IsInRole(model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() { + c.Err = model.NewAppError("updateRoles", "The system admin role can only by modified by another system admin", "") + c.Err.StatusCode = http.StatusForbidden + return + } + ruser := UpdateRoles(c, user, new_roles) if c.Err != nil { return diff --git a/api/web_hub.go b/api/web_hub.go index 44d405283..15528c612 100644 --- a/api/web_hub.go +++ b/api/web_hub.go @@ -30,11 +30,15 @@ func PublishAndForget(message *model.Message) { }() } +func UpdateChannelAccessCache(teamId, userId, channelId string) { + if nh, ok := hub.teamHubs[teamId]; ok { + nh.UpdateChannelAccessCache(userId, channelId) + } +} + func UpdateChannelAccessCacheAndForget(teamId, userId, channelId string) { go func() { - if nh, ok := hub.teamHubs[teamId]; ok { - nh.UpdateChannelAccessCache(userId, channelId) - } + UpdateChannelAccessCache(teamId, userId, channelId) }() } diff --git a/config/config.json b/config/config.json index b14175372..88da33215 100644 --- a/config/config.json +++ b/config/config.json @@ -89,4 +89,4 @@ "TokenEndpoint": "", "UserApiEndpoint": "" } -} +}
\ No newline at end of file diff --git a/doc/README.md b/doc/README.md index 66af8f34c..4c7c4cc0e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -19,7 +19,7 @@ - [Code Contribution Guidelines](developer/Code-Contribution-Guidelines.md) - [Developer Machine Setup](developer/Setup.md) - [Mattermost Style Guide](developer/Style-Guide.md) -- [API Overview] (api/Overview.md) +- [API Overview](api/Overview.md) ## Usage Help diff --git a/doc/integrations/webhooks/Incoming.md b/doc/integrations/webhooks/Incoming.md index 6e25f182e..0814eb420 100644 --- a/doc/integrations/webhooks/Incoming.md +++ b/doc/integrations/webhooks/Incoming.md @@ -56,7 +56,7 @@ payload={"channel": "off-topic", "text": "Hello, this is some text."} Combining everything above, here is an example message made using a curl command: ``` -curl -i -X POST 'payload={"channel": "off-topic", "text": "Hello, this is some text."}' http://yourmattermost.com/hooks/xxxxxxxxxxxxxxxxxxxxxxxxxx +curl -i -X POST -d 'payload={"channel": "off-topic", "text": "Hello, this is some text."}' http://yourmattermost.com/hooks/xxxxxxxxxxxxxxxxxxxxxxxxxx ``` A post with that text will be made to the Off-Topic channel. diff --git a/model/message.go b/model/message.go index ec4817b2a..8598bea0e 100644 --- a/model/message.go +++ b/model/message.go @@ -31,8 +31,8 @@ func (m *Message) Add(key string, value string) { m.Props[key] = value } -func NewMessage(teamId string, channekId string, userId string, action string) *Message { - return &Message{TeamId: teamId, ChannelId: channekId, UserId: userId, Action: action, Props: make(map[string]string)} +func NewMessage(teamId string, channelId string, userId string, action string) *Message { + return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)} } func (o *Message) ToJson() string { diff --git a/store/sql_post_store.go b/store/sql_post_store.go index 668f45fbb..8d62eaad0 100644 --- a/store/sql_post_store.go +++ b/store/sql_post_store.go @@ -396,6 +396,17 @@ func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) S return storeChannel } +var specialSearchChar = []string{ + "<", + ">", + "+", + "-", + "(", + ")", + "~", + "@", +} + func (s SqlPostStore) Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel { storeChannel := make(StoreChannel) @@ -411,10 +422,10 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht } } - // @ has a speical meaning in INNODB FULLTEXT indexes and - // is reserved for calc'ing distances so you - // cannot escape it so we replace it. - terms = strings.Replace(terms, "@", " ", -1) + // these chars have speical meaning and can be treated as spaces + for _, c := range specialSearchChar { + terms = strings.Replace(terms, c, " ", -1) + } var posts []*model.Post diff --git a/store/sql_post_store_test.go b/store/sql_post_store_test.go index 6a6364dc8..62d7b0100 100644 --- a/store/sql_post_store_test.go +++ b/store/sql_post_store_test.go @@ -516,7 +516,7 @@ func TestPostStoreSearch(t *testing.T) { o4.ChannelId = c1.Id o4.UserId = model.NewId() o4.Hashtags = "#hashtag" - o4.Message = "message" + o4.Message = "(message)blargh" o4 = (<-store.Post().Save(o4)).Data.(*model.Post) o5 := &model.Post{} @@ -527,37 +527,37 @@ func TestPostStoreSearch(t *testing.T) { r1 := (<-store.Post().Search(teamId, userId, "corey", false)).Data.(*model.PostList) if len(r1.Order) != 1 && r1.Order[0] != o1.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList) if len(r3.Order) != 2 && r3.Order[0] != o1.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r4 := (<-store.Post().Search(teamId, userId, "john", false)).Data.(*model.PostList) if len(r4.Order) != 1 && r4.Order[0] != o2.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r5 := (<-store.Post().Search(teamId, userId, "matter*", false)).Data.(*model.PostList) if len(r5.Order) != 1 && r5.Order[0] != o1.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r6 := (<-store.Post().Search(teamId, userId, "#hashtag", true)).Data.(*model.PostList) if len(r6.Order) != 1 && r6.Order[0] != o4.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r7 := (<-store.Post().Search(teamId, userId, "#secret", true)).Data.(*model.PostList) if len(r7.Order) != 1 && r7.Order[0] != o5.Id { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r8 := (<-store.Post().Search(teamId, userId, "@thisshouldmatchnothing", true)).Data.(*model.PostList) if len(r8.Order) != 0 { - t.Fatal("returned wrong serach result") + t.Fatal("returned wrong search result") } r9 := (<-store.Post().Search(teamId, userId, "mattermost jersey", false)).Data.(*model.PostList) @@ -569,4 +569,14 @@ func TestPostStoreSearch(t *testing.T) { if len(r10.Order) != 2 { t.Fatal("returned wrong search result") } + + r11 := (<-store.Post().Search(teamId, userId, "message blargh", false)).Data.(*model.PostList) + if len(r11.Order) != 1 { + t.Fatal("returned wrong search result") + } + + r12 := (<-store.Post().Search(teamId, userId, "blargh>", false)).Data.(*model.PostList) + if len(r12.Order) != 1 { + t.Fatal("returned wrong search result") + } } diff --git a/utils/config.go b/utils/config.go index 44c4c43af..90e44259a 100644 --- a/utils/config.go +++ b/utils/config.go @@ -179,6 +179,8 @@ func getClientProperties(c *model.Config) map[string]string { props["BuildHash"] = model.BuildHash props["SiteName"] = c.TeamSettings.SiteName + props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation) + props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) props["SegmentDeveloperKey"] = c.ServiceSettings.SegmentDeveloperKey diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index aee2541b5..ff370c32e 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -31,6 +31,11 @@ export default class ActivityLogModal extends React.Component { } submitRevoke(altId, e) { e.preventDefault(); + var modalContent = $(e.target).closest('.modal-content'); + modalContent.addClass('animation--highlight'); + setTimeout(() => { + modalContent.removeClass('animation--highlight'); + }, 1500); Client.revokeSession(altId, function handleRevokeSuccess() { AsyncClient.getSessions(); diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx index 32812e875..c5c6e19d4 100644 --- a/web/react/components/admin_console/user_item.jsx +++ b/web/react/components/admin_console/user_item.jsx @@ -127,7 +127,6 @@ export default class UserItem extends React.Component { if (user.delete_at > 0) { currentRoles = 'Inactive'; - currentRoles = 'Inactive'; showMakeMember = false; showMakeAdmin = false; showMakeSystemAdmin = false; diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index fed8e789e..ed76b7bce 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -195,9 +195,7 @@ export default class ChannelNotifications extends React.Component { const extraInfo = ( <span> - {'Selecting an option other than "Default" will override the global notification settings.'} - <br/> - {'Desktop notifications are available on Firefox, Safari, and Chrome.'} + {'Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'} </span> ); diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 9c233ea26..550f85d3d 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -106,10 +106,11 @@ export default class CreateComment extends React.Component { let state = {}; if (err.message === 'Invalid RootId parameter') { + PostStore.removePendingPost(post.channel_id, post.pending_post_id); + if ($('#post_deleted').length > 0) { $('#post_deleted').modal('show'); } - PostStore.removePendingPost(post.pending_post_id); } else { post.state = Constants.POST_FAILED; PostStore.updatePendingPost(post); diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index 05726e860..5aa55be93 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -9,28 +9,71 @@ export default class ErrorBar extends React.Component { this.onErrorChange = this.onErrorChange.bind(this); this.handleClose = this.handleClose.bind(this); + this.resize = this.resize.bind(this); this.prevTimer = null; this.state = ErrorStore.getLastError(); - if (this.state && this.state.message) { + if (this.isValidError(this.state)) { this.prevTimer = setTimeout(this.handleClose, 10000); } } + isValidError(s) { + if (!s) { + return false; + } + + if (!s.message) { + return false; + } + + if (s.connErrorCount && s.connErrorCount >= 1 && s.connErrorCount < 7) { + return false; + } + + return true; + } + + isConnectionError(s) { + if (!s.connErrorCount || s.connErrorCount === 0) { + return false; + } + + if (s.connErrorCount > 7) { + return true; + } + + return false; + } + + resize() { + if (this.isValidError(this.state)) { + var height = $(React.findDOMNode(this)).outerHeight(); + height = height < 30 ? 30 : height; + $('body').css('padding-top', height + 'px'); + } else { + $('body').css('padding-top', '0'); + } + } + componentDidMount() { ErrorStore.addChangeListener(this.onErrorChange); - $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); + $(window).resize(() => { - if (this.state && this.state.message) { - $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); - } + this.resize(); }); + + this.resize(); } componentWillUnmount() { ErrorStore.removeChangeListener(this.onErrorChange); } + componentDidUpdate() { + this.resize(); + } + onErrorChange() { var newState = ErrorStore.getLastError(); @@ -41,7 +84,9 @@ export default class ErrorBar extends React.Component { if (newState) { this.setState(newState); - this.prevTimer = setTimeout(this.handleClose, 10000); + if (!this.isConnectionError(newState)) { + this.prevTimer = setTimeout(this.handleClose, 10000); + } } else { this.setState({message: null}); } @@ -52,22 +97,11 @@ export default class ErrorBar extends React.Component { e.preventDefault(); } - ErrorStore.storeLastError(null); - ErrorStore.emitChange(); - - $('body').css('padding-top', '0'); + this.setState({message: null}); } render() { - if (!this.state) { - return <div/>; - } - - if (!this.state.message) { - return <div/>; - } - - if (this.state.connErrorCount < 7) { + if (!this.isValidError(this.state)) { return <div/>; } diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx index b7e81f843..629fb2ec4 100644 --- a/web/react/components/member_list_team_item.jsx +++ b/web/react/components/member_list_team_item.jsx @@ -24,32 +24,32 @@ export default class MemberListTeamItem extends React.Component { }; Client.updateRoles(data, - function handleMakeMemberSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeMemberError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeActive() { Client.updateActive(this.props.user.id, true, - function handleMakeActiveSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeActiveError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeNotActive() { Client.updateActive(this.props.user.id, false, - function handleMakeNotActiveSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeNotActiveError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } handleMakeAdmin() { @@ -59,12 +59,12 @@ export default class MemberListTeamItem extends React.Component { }; Client.updateRoles(data, - function handleMakeAdminSuccess() { + () => { AsyncClient.getProfiles(); }, - function handleMakeAdmitError(err) { + (err) => { this.setState({serverError: err.message}); - }.bind(this) + } ); } render() { @@ -82,14 +82,18 @@ export default class MemberListTeamItem extends React.Component { const timestamp = UserStore.getCurrentUser().update_at; if (user.roles.length > 0) { - currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + if (user.roles.indexOf('system_admin') > -1) { + currentRoles = 'System Admin'; + } else { + currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + } } const email = user.email; - let showMakeMember = user.roles === 'admin'; - let showMakeAdmin = user.roles === ''; + let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; + let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; let showMakeActive = false; - let showMakeNotActive = true; + let showMakeNotActive = user.roles !== 'system_admin'; if (user.delete_at > 0) { currentRoles = 'Inactive'; @@ -108,7 +112,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeAdmin} > - Make Admin + {'Make Admin'} </a> </li> ); @@ -123,7 +127,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeMember} > - Make Member + {'Make Member'} </a> </li> ); @@ -138,7 +142,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeActive} > - Make Active + {'Make Active'} </a> </li> ); @@ -153,7 +157,7 @@ export default class MemberListTeamItem extends React.Component { href='#' onClick={this.handleMakeNotActive} > - Make Inactive + {'Make Inactive'} </a> </li> ); diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index b90ce4170..ff7a53848 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -145,30 +145,35 @@ export default class NavbarDropdown extends React.Component { var teams = []; - teams.push( - <li - className='divider' - key='div' - > - </li> - ); - if (this.state.teams.length > 1) { + teams.push( + <li + className='divider' + key='div' + > + </li> + ); + this.state.teams.forEach((teamName) => { if (teamName !== this.props.teamName) { teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>); } }); } - teams.push(<li key='newTeam_li'> - <a - key='newTeam_a' - target='_blank' - href={Utils.getWindowLocationOrigin() + '/signup_team'} - > - {'Create a New Team'} - </a> - </li>); + + if (global.window.config.EnableTeamCreation === 'true') { + teams.push( + <li key='newTeam_li'> + <a + key='newTeam_a' + target='_blank' + href={Utils.getWindowLocationOrigin() + '/signup_team'} + > + {'Create a New Team'} + </a> + </li> + ); + } return ( <ul className='nav navbar-nav navbar-right'> diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 6cfd243de..1d94cab47 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -215,7 +215,7 @@ export default class PostBody extends React.Component { comment = ( <p className='post-link'> <span> - {'Commented on '}{name}{apostrophe}{' message:'} + {'Commented on '}{name}{apostrophe}{' message: '} <a className='theme' onClick={this.props.handleCommentClick} diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index d284a9d1b..3f487d20f 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -2,13 +2,41 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; export default class PostDeletedModal extends React.Component { constructor(props) { super(props); + this.handleClose = this.handleClose.bind(this); + this.state = {}; } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { + this.handleClose(); + }); + } + handleClose() { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH, + results: null + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_SEARCH_TERM, + term: null, + do_search: false, + is_mention_search: false + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_POST_SELECTED, + results: null + }); + } render() { var currentUser = UserStore.getCurrentUser(); @@ -31,17 +59,17 @@ export default class PostDeletedModal extends React.Component { data-dismiss='modal' aria-label='Close' > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> <h4 className='modal-title' id='myModalLabel' > - Comment could not be posted + {'Comment could not be posted'} </h4> </div> <div className='modal-body'> - <p>Someone deleted the message on which you tried to post a comment.</p> + <p>{'Someone deleted the message on which you tried to post a comment.'}</p> </div> <div className='modal-footer'> <button @@ -49,7 +77,7 @@ export default class PostDeletedModal extends React.Component { className='btn btn-primary' data-dismiss='modal' > - Okay + {'Okay'} </button> </div> </div> diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index dba75ac5f..c1e8979a4 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -13,12 +13,6 @@ export default class PostInfo extends React.Component { super(props); this.state = {}; } - shouldShowComment(state, type, isOwner) { - if (state === Constants.POST_FAILED || state === Constants.POST_LOADING) { - return false; - } - return isOwner || (this.props.allowReply === 'true' && type !== 'Comment'); - } createDropdown() { var post = this.props.post; var isOwner = UserStore.getCurrentId() === post.user_id; @@ -33,10 +27,6 @@ export default class PostInfo extends React.Component { type = 'Comment'; } - if (!this.shouldShowComment(post.state, type, isOwner)) { - return ''; - } - var dropdownContents = []; var dataComments = 0; if (type === 'Post') { @@ -106,6 +96,10 @@ export default class PostInfo extends React.Component { ); } + if (dropdownContents.length === 0) { + return ''; + } + return ( <div> <a diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 5b4694eb1..aa355f8cc 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -70,19 +70,84 @@ export default class RhsComment extends React.Component { componentDidUpdate() { this.parseEmojis(); } - render() { + createDropdown() { var post = this.props.post; - var currentUserCss = ''; - if (UserStore.getCurrentId() === post.user_id) { - currentUserCss = 'current--user'; + if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) { + return ''; } var isOwner = UserStore.getCurrentId() === post.user_id; + var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles); + + var dropdownContents = []; + + if (isOwner) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#edit_post' + data-title='Comment' + data-message={post.message} + data-postid={post.id} + data-channelid={post.channel_id} + > + Edit + </a> + </li> + ); + } - var type = 'Post'; - if (post.root_id.length > 0) { - type = 'Comment'; + if (isOwner || isAdmin) { + dropdownContents.push( + <li role='presentation'> + <a + href='#' + role='menuitem' + data-toggle='modal' + data-target='#delete_post' + data-title='Comment' + data-postid={post.id} + data-channelid={post.channel_id} + data-comments={0} + > + Delete + </a> + </li> + ); + } + + if (dropdownContents.length === 0) { + return ''; + } + + return ( + <div className='dropdown'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + data-toggle='dropdown' + aria-expanded='false' + /> + <ul + className='dropdown-menu' + role='menu' + > + {dropdownContents} + </ul> + </div> + ); + } + render() { + var post = this.props.post; + + var currentUserCss = ''; + if (UserStore.getCurrentId() === post.user_id) { + currentUserCss = 'current--user'; } var timestamp = UserStore.getCurrentUser().update_at; @@ -110,53 +175,7 @@ export default class RhsComment extends React.Component { ); } - var ownerOptions; - if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { - ownerOptions = ( - <div className='dropdown'> - <a - href='#' - className='dropdown-toggle theme' - type='button' - data-toggle='dropdown' - aria-expanded='false' - /> - <ul - className='dropdown-menu' - role='menu' - > - <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} - > - Edit - </a> - </li> - <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={0} - > - Delete - </a> - </li> - </ul> - </div> - ); - } + var dropdown = this.createDropdown(); var fileAttachment; if (post.filenames && post.filenames.length > 0) { @@ -190,7 +209,7 @@ export default class RhsComment extends React.Component { </time> </li> <li className='post-header-col post-header__reply'> - {ownerOptions} + {dropdown} </li> </ul> <div className='post-body'> diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index 2f23d80d9..27a784701 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -23,7 +23,7 @@ export default class RhsThread extends React.Component { } getStateFromStores() { var postList = PostStore.getSelectedPost(); - if (!postList || postList.order.length < 1) { + if (!postList || postList.order.length < 1 || !postList.posts[postList.order[0]]) { return {postList: {}}; } @@ -49,7 +49,10 @@ export default class RhsThread extends React.Component { }.bind(this)); } componentDidUpdate() { - $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); + if ($('.post-right__scroll')[0]) { + $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); + } + $('.post-right__scroll').perfectScrollbar('update'); this.resize(); } @@ -67,7 +70,7 @@ export default class RhsThread extends React.Component { // if something was changed in the channel like adding a // comment or post then lets refresh the sidebar list var currentSelected = PostStore.getSelectedPost(); - if (!currentSelected || currentSelected.order.length === 0) { + if (!currentSelected || currentSelected.order.length === 0 || !currentSelected.posts[currentSelected.order[0]]) { return; } @@ -103,7 +106,7 @@ export default class RhsThread extends React.Component { render() { var postList = this.state.postList; - if (postList == null) { + if (postList == null || !postList.order) { return ( <div></div> ); diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index b696f4b53..88eaed335 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -205,7 +205,7 @@ export default class Sidebar extends React.Component { const user = UserStore.getCurrentUser(); const member = ChannelStore.getMember(msg.channel_id); - var notifyLevel = member.notify_props.desktop; + var notifyLevel = member && member.notify_props ? member.notify_props.desktop : 'default'; if (notifyLevel === 'default') { notifyLevel = user.notify_props.desktop; } diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx index 4e8ee03fa..3301c6596 100644 --- a/web/react/components/user_settings/import_theme_modal.jsx +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -49,7 +49,6 @@ export default class ImportThemeModal extends React.Component { theme.sidebarText = colors[5]; theme.sidebarUnreadText = colors[5]; theme.sidebarTextHoverBg = colors[4]; - theme.sidebarTextHoverColor = colors[5]; theme.sidebarTextActiveBg = colors[2]; theme.sidebarTextActiveColor = colors[3]; theme.sidebarHeaderBg = colors[1]; diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index c4a137ed8..be6cf1f42 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -214,14 +214,14 @@ export default class UserSettingsAppearance extends React.Component { <div className='divider-dark first'/> {themeUI} <div className='divider-dark'/> + <br/> + <a + className='theme' + onClick={this.handleImportModal} + > + {'Import theme colors from Slack'} + </a> </div> - <br/> - <a - className='theme' - onClick={this.handleImportModal} - > - {'Import theme colors from Slack'} - </a> </div> ); } diff --git a/web/react/package.json b/web/react/package.json index a097b6aa8..e6a662375 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -5,7 +5,6 @@ "dependencies": { "autolinker": "0.18.1", "babel-runtime": "5.8.24", - "bootstrap-colorpicker": "2.2.0", "flux": "2.1.1", "keymirror": "0.1.1", "marked": "0.3.5", @@ -13,18 +12,18 @@ "twemoji": "1.4.1" }, "devDependencies": { - "browserify": "11.0.1", - "envify": "3.4.0", - "babelify": "6.1.3", + "browserify": "11.2.0", + "babelify": "6.3.0", "uglify-js": "2.4.24", - "watchify": "3.3.1", + "watchify": "3.4.0", "eslint": "1.6.0", "eslint-plugin-react": "3.5.1" }, "scripts": { - "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", - "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js", - "test": "jest" + "check": "", + "build-libs": "browserify -r crypto -r autolinker -r flux -r keymirror -r marked -r object-assign -r twemoji | uglifyjs -c -m --screw-ie8 > ../static/js/libs.min.js", + "start": "watchify --fast -x crypto -x node -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji -o ../static/js/bundle.js -v -d ./**/*.jsx", + "build": "browserify -x crypto -x autolinker -x flux -x keymirror -x marked -x object-assign -x twemoji ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js" }, "browserify": { "transform": [ @@ -35,11 +34,7 @@ "runtime" ] } - ], - "envify" + ] ] - }, - "jest": { - "rootDir": "." } } diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index e45d3d981..27a74fb2b 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -41,7 +41,13 @@ class BrowserStoreClass { } setGlobalItem(name, value) { - localStorage.setItem(name, JSON.stringify(value)); + try { + localStorage.setItem(name, JSON.stringify(value)); + } catch (err) { + console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console + localStorage.clear(); + window.location.href = window.location.href; + } } getGlobalItem(name, defaultValue) { diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 1d853f979..9f354965e 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -50,8 +50,10 @@ class SocketStoreClass extends EventEmitter { } this.failCount = 0; - ErrorStore.storeLastError(null); - ErrorStore.emitChange(); + if (ErrorStore.getLastError()) { + ErrorStore.storeLastError(null); + ErrorStore.emitChange(); + } }; conn.onclose = () => { diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index d9f486009..6dccfcdeb 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -2,6 +2,7 @@ var BrowserStore = require('../stores/browser_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); +var ErrorStore = require('../stores/error_store.jsx'); export function track(category, action, label, prop, val) { global.window.analytics.track(action, {category: category, label: label, property: prop, value: val}); @@ -27,7 +28,16 @@ function handleError(methodName, xhr, status, err) { msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; if (xhr.status === 0) { - e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1}; + let errorCount = 1; + const oldError = ErrorStore.getLastError(); + let connectError = 'There appears to be a problem with your internet connection'; + + if (oldError && oldError.connErrorCount) { + errorCount += oldError.connErrorCount; + connectError = 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'; + } + + e = {message: connectError, connErrorCount: errorCount}; } else { e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'}; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index aba63b91c..8fd0ab79b 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -122,10 +122,9 @@ module.exports = { default: { type: 'Mattermost', sidebarBg: '#fafafa', - sidebarText: '#999999', + sidebarText: '#333333', sidebarUnreadText: '#333333', sidebarTextHoverBg: '#e6f2fa', - sidebarTextHoverColor: '#999999', sidebarTextActiveBg: '#e1e1e1', sidebarTextActiveColor: '#111111', sidebarHeaderBg: '#2389d7', @@ -139,15 +138,15 @@ module.exports = { linkColor: '#2389d7', buttonBg: '#2389d7', buttonColor: '#FFFFFF', - mentionHighlightBg: '#fff2bb' + mentionHighlightBg: '#fff2bb', + mentionHighlightLink: '#2f81b7' }, organization: { type: 'Organization', sidebarBg: '#2071a7', - sidebarText: '#bfcde8', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#136197', - sidebarTextHoverColor: '#bfcde8', sidebarTextActiveBg: '#136197', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#2f81b7', @@ -161,15 +160,15 @@ module.exports = { linkColor: '#2f81b7', buttonBg: '#1dacfc', buttonColor: '#FFFFFF', - mentionHighlightBg: '#fff2bb' + mentionHighlightBg: '#fff2bb', + mentionHighlightLink: '#2f81b7' }, mattermostDark: { type: 'Mattermost Dark', sidebarBg: '#1B2C3E', - sidebarText: '#bbbbbb', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#4A5664', - sidebarTextHoverColor: '#bbbbbb', sidebarTextActiveBg: '#39769C', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1B2C3E', @@ -183,15 +182,15 @@ module.exports = { linkColor: '#A4FFEB', buttonBg: '#4CBBA4', buttonColor: '#FFFFFF', - mentionHighlightBg: '#338886' + mentionHighlightBg: '#984063', + mentionHighlightLink: '#A4FFEB' }, windows10: { type: 'Windows Dark', sidebarBg: '#171717', - sidebarText: '#999', + sidebarText: '#fff', sidebarUnreadText: '#fff', sidebarTextHoverBg: '#302e30', - sidebarTextHoverColor: '#999', sidebarTextActiveBg: '#484748', sidebarTextActiveColor: '#FFFFFF', sidebarHeaderBg: '#1f1f1f', @@ -205,7 +204,8 @@ module.exports = { linkColor: '#0177e7', buttonBg: '#0177e7', buttonColor: '#FFFFFF', - mentionHighlightBg: '#276198' + mentionHighlightBg: '#784098', + mentionHighlightLink: '#A4FFEB' } }, THEME_ELEMENTS: [ @@ -234,10 +234,6 @@ module.exports = { uiName: 'Sidebar Text Hover BG' }, { - id: 'sidebarTextHoverColor', - uiName: 'Sidebar Text Hover Color' - }, - { id: 'sidebarTextActiveBg', uiName: 'Sidebar Text Active BG' }, @@ -284,6 +280,10 @@ module.exports = { { id: 'mentionHighlightBg', uiName: 'Mention Highlight BG' + }, + { + id: 'mentionHighlightLink', + uiName: 'Mention Highlight Link' } ] }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 3a04f3623..f79f3492f 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -399,9 +399,9 @@ export function applyTheme(theme) { } if (theme.sidebarText) { - changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + theme.sidebarText, 1); + changeCss('.sidebar--left .nav-pills__container li>a, .sidebar--right, .settings-modal .nav-pills>li a, .sidebar--menu', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li>a', 'color:' + theme.sidebarText, 1); - changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1); + changeCss('.sidebar--left .nav-pills__container li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.6), 1); changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1); changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1); changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1); @@ -417,11 +417,6 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'background:' + theme.sidebarTextHoverBg, 1); } - if (theme.sidebarTextHoverColor) { - changeCss('.sidebar--left .nav-pills__container li>a:hover, .sidebar--left .nav-pills__container li>a:focus, .settings-modal .nav-pills>li:hover a, .settings-modal .nav-pills>li:focus a', 'color:' + theme.sidebarTextHoverColor, 2); - changeCss('@media(max-width: 768px){.settings-modal .settings-table .nav>li:hover a', 'color:' + theme.sidebarTextHoverColor, 2); - } - if (theme.sidebarTextActiveBg) { changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'background:' + theme.sidebarTextActiveBg, 1); } @@ -495,7 +490,7 @@ export function applyTheme(theme) { changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1); changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2); changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1); - changeCss('.search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); @@ -503,7 +498,7 @@ export function applyTheme(theme) { changeCss('.modal .custom-textarea:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1); changeCss('.channel-intro, .settings-modal .settings-table .settings-content .divider-dark, hr, .settings-modal .settings-table .settings-links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); - changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2); + changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body, .modal .more-channel-table tbody>tr td, .member-div:first-child, .member-div, .access-history__table .access__report, .activity-log__table', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); @@ -536,6 +531,10 @@ export function applyTheme(theme) { if (theme.mentionHighlightBg) { changeCss('.mention-highlight, .search-highlight', 'background:' + theme.mentionHighlightBg, 1); } + + if (theme.mentionHighlightLink) { + changeCss('.mention-highlight .mention-link', 'color:' + theme.mentionHighlightLink, 1); + } } export function changeCss(className, classValue, classRepeat) { // we need invisible container to store additional css definitions diff --git a/web/sass-files/sass/partials/_access-history.scss b/web/sass-files/sass/partials/_access-history.scss index 412a2a1d0..a3289ecc0 100644 --- a/web/sass-files/sass/partials/_access-history.scss +++ b/web/sass-files/sass/partials/_access-history.scss @@ -24,6 +24,6 @@ font-size: 15px; } .report__info { - color: #999; + @include opacity(0.8); } }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_activity-log.scss b/web/sass-files/sass/partials/_activity-log.scss index 3f0c3090d..2fb37a3bb 100644 --- a/web/sass-files/sass/partials/_activity-log.scss +++ b/web/sass-files/sass/partials/_activity-log.scss @@ -1,3 +1,20 @@ +@keyframes highlight { + from { background: rgba(yellow, 0.5);} + to { background: none;} +} + +.animation--highlight { + &:before { + content: ''; + animation: highlight 1.5s ease; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +} + .activity-log__table { display: table; width: 100%; @@ -26,7 +43,7 @@ } } .report__info { - color: #999; + @include opacity(0.8); } } .session-help-text { diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index 4237ee1fa..18462d92a 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -34,6 +34,10 @@ body { } } +.input-group-addon { + background: transparent; +} + .popover { color: #333; &.bottom { diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index fb74eb4f5..f59cefbc6 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -57,11 +57,4 @@ .mention-highlight { background-color:#fff2bb; - a { - color: inherit; - text-decoration: underline; - &:hover, &:active { - color: inherit; - } - } }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 8bf4b0534..ccd7fd425 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -16,7 +16,7 @@ } .bad-connection { - background-color: rgb(255, 255, 172); + background-color: rgb(255, 255, 172) !important; } .textarea-div { @@ -257,7 +257,7 @@ body.ios { line-height: 18px; display: inline-block; font-size: 13px; - @include opacity(0.6); + @include opacity(0.7); } } } diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss index da5bcbad2..e4860b286 100644 --- a/web/sass-files/sass/partials/_post_right.scss +++ b/web/sass-files/sass/partials/_post_right.scss @@ -29,7 +29,7 @@ min-height: 100px; } .msg-typing { - color: #555; + @include opacity(0.7); float: left; padding-top: 17px; } diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index a7b1ab190..2de1b5380 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -106,11 +106,4 @@ .search-highlight { background-color: #FFF2BB; - a { - color: inherit; - text-decoration: underline; - &:hover, &:active { - color: inherit; - } - } } diff --git a/web/templates/head.html b/web/templates/head.html index faac4975a..8039f48a1 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -1,6 +1,6 @@ {{define "head"}} <head> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="robots" content="noindex, nofollow"> <title>{{ .Props.Title }}</title> @@ -22,24 +22,22 @@ window.config = {{ .ClientProps }}; </script> - + <!-- CSS Should always go first --> <link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css"> - <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet"> - <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css" rel="stylesheet"> + <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css"> + <link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css"> + <link rel="stylesheet" href="/static/css/styles.css"> + <link rel="stylesheet" href="/static/css/google-fonts.css"> + + <link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon"> + <link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon"> <script src="/static/js/react-with-addons-0.13.3.js"></script> <script src="/static/js/jquery-1.11.1.js"></script> <script src="/static/js/bootstrap-3.3.5.js"></script> <script src="/static/js/bootstrap-colorpicker.min.js"></script> <script src="/static/js/react-bootstrap-0.25.1.js"></script> - - <link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon"> - <link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon"> - <link href='/static/css/google-fonts.css' rel='stylesheet' type='text/css'> - <link rel="stylesheet" href="/static/css/styles.css"> - <script src="/static/js/perfect-scrollbar-0.6.5.jquery.js"></script> - <script src="/static/js/jquery-dragster/jquery.dragster.js"></script> <style id="antiClickjack">body{display:none !important;}</style> @@ -60,6 +58,7 @@ } </script> + <script src="/static/js/libs.min.js"></script> <script src="/static/js/bundle.js"></script> <script type="text/javascript"> |