summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/channel.go66
-rw-r--r--i18n/en.json8
-rw-r--r--model/post.go3
-rw-r--r--webapp/components/post_view/post_focus_view_controller.jsx16
-rw-r--r--webapp/components/post_view/post_view_controller.jsx15
-rw-r--r--webapp/components/user_settings/user_settings_advanced.jsx93
-rw-r--r--webapp/i18n/en.json2
-rw-r--r--webapp/stores/notification_store.jsx5
-rw-r--r--webapp/stores/post_store.jsx18
9 files changed, 191 insertions, 35 deletions
diff --git a/api/channel.go b/api/channel.go
index 3fef273e5..5d3021cee 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 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
- }
+ if !c.HasPermissionsToTeam(channel.TeamId, "removeMember") {
+ return
+ }
- c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove)
+ 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
+ }
- 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)
+
+ 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)))
+ }
}
}
diff --git a/i18n/en.json b/i18n/en.json
index 823a6f259..a4d01239b 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 175aecdd7..33caeb9ea 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 4a7d312f5..a1c474184 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 1dd5e9176..2451dfc8d 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 c647dd0fd..157488559 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 (
+ <SettingItemMax
+ title={
+ <FormattedMessage
+ id='user.settings.advance.joinLeaveTitle'
+ defaultMessage='Enable Join/Leave Messages'
+ />
+ }
+ inputs={
+ <div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='join_leave'
+ checked={this.state.settings.join_leave !== 'false'}
+ onChange={this.updateSetting.bind(this, 'join_leave', 'true')}
+ />
+ <FormattedMessage
+ id='user.settings.advance.on'
+ defaultMessage='On'
+ />
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ name='join_leave'
+ checked={this.state.settings.join_leave === 'false'}
+ onChange={this.updateSetting.bind(this, 'join_leave', 'false')}
+ />
+ <FormattedMessage
+ id='user.settings.advance.off'
+ defaultMessage='Off'
+ />
+ </label>
+ <br/>
+ </div>
+ <div>
+ <br/>
+ <FormattedMessage
+ id='user.settings.advance.joinLeaveDesc'
+ defaultMessage='When "On", System Messages saying a user has joined or left a channel will be visible. When "Off", the System Messages about joining or leaving a channel will be hidden. A message will still show up when you are added to a channel, so you can receive a notification.'
+ />
+ </div>
+ </div>
+ }
+ submit={() => this.handleSubmit('join_leave')}
+ server_error={this.state.serverError}
+ updateSection={(e) => {
+ this.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ }
+
+ return (
+ <SettingItemMin
+ title={
+ <FormattedMessage
+ id='user.settings.advance.joinLeaveTitle'
+ defaultMessage='Enable Join/Leave Messages'
+ />
+ }
+ describe={this.renderOnOffLabel(this.state.settings.join_leave)}
+ updateSection={() => this.props.updateSection('join_leave')}
+ />
+ );
+ }
+
+ return null;
+ }
+
renderFeatureLabel(feature) {
switch (feature) {
case 'MARKDOWN_PREVIEW':
@@ -342,6 +427,12 @@ export default class AdvancedSettingsDisplay extends React.Component {
formattingSectionDivider = <div className='divider-light'/>;
}
+ const displayJoinLeaveSection = this.renderJoinLeaveSection();
+ let displayJoinLeaveSectionDivider = null;
+ if (displayJoinLeaveSection) {
+ displayJoinLeaveSectionDivider = <div className='divider-light'/>;
+ }
+
let previewFeaturesSection;
let previewFeaturesSectionDivider;
if (this.state.preReleaseFeaturesKeys.length > 0) {
@@ -454,6 +545,8 @@ export default class AdvancedSettingsDisplay extends React.Component {
{ctrlSendSection}
{formattingSectionDivider}
{formattingSection}
+ {displayJoinLeaveSectionDivider}
+ {displayJoinLeaveSection}
{previewFeaturesSectionDivider}
{previewFeaturesSection}
<div className='divider-dark'/>
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 6c69c3f7c..d8a5dfc6b 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -1713,6 +1713,8 @@
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} Enabled",
"user.settings.advance.formattingDesc": "If enabled, posts will be formatted to create links, show emoji, style the text, and add line breaks. By default, this setting is enabled. Changing this setting requires the page to be refreshed.",
"user.settings.advance.formattingTitle": "Enable Post Formatting",
+ "user.settings.advance.joinLeaveDesc": "When \"On\", System Messages saying a user has joined or left a channel will be visible. When \"Off\", the System Messages about joining or leaving a channel will be hidden. A message will still show up when you are added to a channel, so you can receive a notification.",
+ "user.settings.advance.joinLeaveTitle": "Enable Join/Leave Messages",
"user.settings.advance.markdown_preview": "Show markdown preview option in message input box",
"user.settings.advance.off": "Off",
"user.settings.advance.on": "On",
diff --git a/webapp/stores/notification_store.jsx b/webapp/stores/notification_store.jsx
index e05d20329..507954a10 100644
--- a/webapp/stores/notification_store.jsx
+++ b/webapp/stores/notification_store.jsx
@@ -6,6 +6,7 @@ import EventEmitter from 'events';
import Constants from 'utils/constants.jsx';
import UserStore from './user_store.jsx';
import ChannelStore from './channel_store.jsx';
+import PreferenceStore from './preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
const ActionTypes = Constants.ActionTypes;
@@ -28,7 +29,9 @@ class NotificationStoreClass extends EventEmitter {
handleRecievedPost(post, msgProps) {
// Send desktop notification
if ((UserStore.getCurrentId() !== post.user_id || post.props.from_webhook === 'true')) {
- if (PostUtils.isSystemMessage(post) && post.type !== 'system_join_leave') {
+ if (PostUtils.isSystemMessage(post)) {
+ return;
+ } else if (!PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true) && post.type === Constants.POST_TYPE_JOIN_LEAVE) {
return;
}
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index 135563866..0b2277255 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -548,6 +548,24 @@ class PostStoreClass extends EventEmitter {
return commentCount;
}
+
+ filterPosts(channelId, joinLeave) {
+ const postsList = JSON.parse(JSON.stringify(this.getVisiblePosts(channelId)));
+
+ if (!joinLeave && postsList) {
+ postsList.order = postsList.order.filter((id) => {
+ if (postsList.posts[id].type === Constants.POST_TYPE_JOIN_LEAVE) {
+ Reflect.deleteProperty(postsList.posts, id);
+
+ return false;
+ }
+
+ return true;
+ });
+ }
+
+ return postsList;
+ }
}
var PostStore = new PostStoreClass();