diff --git a/api/channel.go b/api/channel.go
index 3fef273e5a..5d3021cee9 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -530,7 +530,7 @@ func joinChannel(c *Context, channelChannel store.StoreChannel, userChannel stor
if _, err := AddUserToChannel(user, channel); err != nil {
return err, nil
}
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.join_channel.post_and_forget"), user.Username))
+ go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.join_channel.post_and_forget"), user.Username), model.POST_JOIN_LEAVE)
} else {
return model.NewLocAppError("join", "api.channel.join_channel.permissions.app_error", nil, ""), nil
}
@@ -538,11 +538,11 @@ func joinChannel(c *Context, channelChannel store.StoreChannel, userChannel stor
}
}
-func PostUserAddRemoveMessage(c *Context, channelId string, message string) {
+func PostUserAddRemoveMessage(c *Context, channelId string, message, postType string) {
post := &model.Post{
ChannelId: channelId,
Message: message,
- Type: model.POST_JOIN_LEAVE,
+ Type: postType,
}
if _, err := CreatePost(c, post, false); err != nil {
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
@@ -677,7 +677,7 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) {
RemoveUserFromChannel(c.Session.UserId, c.Session.UserId, channel)
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username))
+ go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username), model.POST_JOIN_LEAVE)
result := make(map[string]string)
result["id"] = channel.Id
@@ -992,7 +992,7 @@ func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("name=" + channel.Name + " user_id=" + userId)
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username))
+ go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username), model.POST_ADD_REMOVE)
<-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id)
w.Write([]byte(cm.ToJson()))
@@ -1014,38 +1014,48 @@ func removeMember(c *Context, w http.ResponseWriter, r *http.Request) {
sc := Srv.Store.Channel().Get(channelId)
cmc := Srv.Store.Channel().GetMember(channelId, c.Session.UserId)
+ ouc := Srv.Store.User().Get(userIdToRemove)
- if cresult := <-sc; cresult.Err != nil {
- c.Err = cresult.Err
- return
- } else if cmcresult := <-cmc; cmcresult.Err != nil {
- c.Err = cmcresult.Err
+ if oresult := <-ouc; oresult.Err != nil {
+ c.Err = model.NewLocAppError("removeMember", "api.channel.remove_member.user.app_error", nil, "")
return
} else {
- channel := cresult.Data.(*model.Channel)
- removerChannelMember := cmcresult.Data.(model.ChannelMember)
+ oUser := oresult.Data.(*model.User)
- if !c.HasPermissionsToTeam(channel.TeamId, "removeMember") {
+ if cresult := <-sc; cresult.Err != nil {
+ c.Err = cresult.Err
return
- }
-
- if !strings.Contains(removerChannelMember.Roles, model.CHANNEL_ROLE_ADMIN) && !c.IsTeamAdmin() {
- c.Err = model.NewLocAppError("updateChannel", "api.channel.remove_member.permissions.app_error", nil, "")
- c.Err.StatusCode = http.StatusForbidden
+ } else if cmcresult := <-cmc; cmcresult.Err != nil {
+ c.Err = cmcresult.Err
return
+ } else {
+ channel := cresult.Data.(*model.Channel)
+ removerChannelMember := cmcresult.Data.(model.ChannelMember)
+
+ if !c.HasPermissionsToTeam(channel.TeamId, "removeMember") {
+ return
+ }
+
+ if !strings.Contains(removerChannelMember.Roles, model.CHANNEL_ROLE_ADMIN) && !c.IsTeamAdmin() {
+ c.Err = model.NewLocAppError("updateChannel", "api.channel.remove_member.permissions.app_error", nil, "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ if err := RemoveUserFromChannel(userIdToRemove, c.Session.UserId, channel); err != nil {
+ c.Err = model.NewLocAppError("updateChannel", "api.channel.remove_member.unable.app_error", nil, err.Message)
+ return
+ }
+
+ c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove)
+
+ go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.remove_member.removed"), oUser.Username), model.POST_ADD_REMOVE)
+
+ result := make(map[string]string)
+ result["channel_id"] = channel.Id
+ result["removed_user_id"] = userIdToRemove
+ w.Write([]byte(model.MapToJson(result)))
}
-
- if err := RemoveUserFromChannel(userIdToRemove, c.Session.UserId, channel); err != nil {
- c.Err = model.NewLocAppError("updateChannel", "api.channel.remove_member.unable.app_error", nil, err.Message)
- return
- }
-
- c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove)
-
- result := make(map[string]string)
- result["channel_id"] = channel.Id
- result["removed_user_id"] = userIdToRemove
- w.Write([]byte(model.MapToJson(result)))
}
}
diff --git a/i18n/en.json b/i18n/en.json
index 823a6f2597..a4d01239ba 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -4822,5 +4822,13 @@
{
"id": "web.watcher_fail.error",
"translation": "Failed to add directory to watcher %v"
+ },
+ {
+ "id": "api.channel.remove_member.removed",
+ "translation": "%v was removed from the channel."
+ },
+ {
+ "id": "api.channel.remove_member.user.app_error",
+ "translation": "Failed to find user to be removed"
}
]
diff --git a/model/post.go b/model/post.go
index 175aecdd71..33caeb9ea2 100644
--- a/model/post.go
+++ b/model/post.go
@@ -15,6 +15,7 @@ const (
POST_SLACK_ATTACHMENT = "slack_attachment"
POST_SYSTEM_GENERIC = "system_generic"
POST_JOIN_LEAVE = "system_join_leave"
+ POST_ADD_REMOVE = "system_add_remove"
POST_HEADER_CHANGE = "system_header_change"
POST_CHANNEL_DELETED = "system_channel_deleted"
POST_EPHEMERAL = "system_ephemeral"
@@ -109,7 +110,7 @@ func (o *Post) IsValid() *AppError {
}
// should be removed once more message types are supported
- if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) {
+ if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) {
return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type)
}
diff --git a/webapp/components/post_view/post_focus_view_controller.jsx b/webapp/components/post_view/post_focus_view_controller.jsx
index 4a7d312f53..a1c4741843 100644
--- a/webapp/components/post_view/post_focus_view_controller.jsx
+++ b/webapp/components/post_view/post_focus_view_controller.jsx
@@ -34,8 +34,10 @@ export default class PostFocusView extends React.Component {
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
}
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.state = {
- postList: PostStore.getVisiblePosts(focusedPostId),
+ postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(),
profiles,
scrollType: ScrollTypes.POST,
@@ -79,9 +81,11 @@ export default class PostFocusView extends React.Component {
return;
}
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.setState({
scrollPostId: focusedPostId,
- postList: PostStore.getVisiblePosts(focusedPostId),
+ postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled),
atTop: PostStore.getVisibilityAtTop(focusedPostId),
atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
});
@@ -103,7 +107,15 @@ export default class PostFocusView extends React.Component {
}
onPreferenceChange() {
+ const focusedPostId = PostStore.getFocusedPostId();
+ if (focusedPostId == null) {
+ return;
+ }
+
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.setState({
+ postList: PostStore.filterPosts(focusedPostId, joinLeaveEnabled),
flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
});
}
diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx
index 1dd5e9176a..2451dfc8d3 100644
--- a/webapp/components/post_view/post_view_controller.jsx
+++ b/webapp/components/post_view/post_view_controller.jsx
@@ -44,9 +44,11 @@ export default class PostViewController extends React.Component {
lastViewed = member.last_viewed_at;
}
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.state = {
channel,
- postList: PostStore.getVisiblePosts(channel.id),
+ postList: PostStore.filterPosts(channel.id, joinLeaveEnabled),
currentUser: UserStore.getCurrentUser(),
profiles,
atTop: PostStore.getVisibilityAtTop(channel.id),
@@ -83,7 +85,10 @@ export default class PostViewController extends React.Component {
previewSuffix = '_' + Utils.generateId();
}
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.setState({
+ postList: PostStore.filterPosts(this.state.channel.id, joinLeaveEnabled),
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
@@ -103,8 +108,10 @@ export default class PostViewController extends React.Component {
}
onPostsChange() {
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.setState({
- postList: JSON.parse(JSON.stringify(PostStore.getVisiblePosts(this.state.channel.id))),
+ postList: PostStore.filterPosts(this.state.channel.id, joinLeaveEnabled),
atTop: PostStore.getVisibilityAtTop(this.state.channel.id)
});
}
@@ -152,12 +159,14 @@ export default class PostViewController extends React.Component {
profiles = Object.assign({}, profiles, UserStore.getDirectProfiles());
}
+ const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
+
this.setState({
channel,
lastViewed,
ownNewMessage: false,
profiles: JSON.parse(JSON.stringify(profiles)),
- postList: JSON.parse(JSON.stringify(PostStore.getVisiblePosts(channel.id))),
+ postList: PostStore.filterPosts(channel.id, joinLeaveEnabled),
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
diff --git a/webapp/components/user_settings/user_settings_advanced.jsx b/webapp/components/user_settings/user_settings_advanced.jsx
index c647dd0fd0..157488559c 100644
--- a/webapp/components/user_settings/user_settings_advanced.jsx
+++ b/webapp/components/user_settings/user_settings_advanced.jsx
@@ -27,6 +27,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this);
this.renderFormattingSection = this.renderFormattingSection.bind(this);
+ this.renderJoinLeaveSection = this.renderJoinLeaveSection.bind(this);
this.state = this.getStateFromStores();
}
@@ -44,6 +45,11 @@ export default class AdvancedSettingsDisplay extends React.Component {
Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
'formatting',
'true'
+ ),
+ join_leave: PreferenceStore.get(
+ Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
+ 'join_leave',
+ 'true'
)
};
@@ -232,6 +238,85 @@ export default class AdvancedSettingsDisplay extends React.Component {
);
}
+ renderJoinLeaveSection() {
+ if (window.mm_config.BuildEnterpriseReady === 'true' && window.mm_license && window.mm_license.IsLicensed === 'true') {
+ if (this.props.activeSection === 'join_leave') {
+ return (
+