Make GM behave as DM (#24289)
* Make GM behave as DM * Fix lint * Add desktop notification special behavior * Change notification preferences menu * Make changes to the GM channel intro * Fix tests * Fix i18n and style lint * Add system notice and update style * Fix style and fix tests * Fix tests * Handle push notifications as desktop notifications * Fix tests * Add test and default GMs to none when user level config is none * Fix test * Update only for mentions text * Add tests * Fix lint * Fix lint
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
39d6cb8008
Коммит
88d043a971
@@ -50,8 +50,8 @@ func TestGetPreferences(t *testing.T) {
|
|||||||
prefs, _, err := client.GetPreferences(context.Background(), user1.Id)
|
prefs, _, err := client.GetPreferences(context.Background(), user1.Id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// 5 because we have 2 initial preferences tutorial_step and recommended_next_steps added when creating a new user
|
// 6 because we have 3 initial preferences tutorial_step, recommended_next_steps and system_notification are added when creating a new user
|
||||||
require.Equal(t, len(prefs), 5, "received the wrong number of preferences")
|
require.Equal(t, len(prefs), 6, "received the wrong number of preferences")
|
||||||
|
|
||||||
for _, preference := range prefs {
|
for _, preference := range prefs {
|
||||||
require.Equal(t, preference.UserId, th.BasicUser.Id, "user id does not match")
|
require.Equal(t, preference.UserId, th.BasicUser.Id, "user id does not match")
|
||||||
|
|||||||
@@ -387,7 +387,9 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
|||||||
status = &model.Status{UserId: id, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
status = &model.Status{UserId: id, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], true, status, post) {
|
isExplicitlyMentioned := mentions.Mentions[id] > GMMention
|
||||||
|
isGM := channel.Type == model.ChannelTypeGroup
|
||||||
|
if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], isExplicitlyMentioned, status, post, isGM) {
|
||||||
mentionType := mentions.Mentions[id]
|
mentionType := mentions.Mentions[id]
|
||||||
|
|
||||||
replyToThreadType := ""
|
replyToThreadType := ""
|
||||||
@@ -428,7 +430,8 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
|||||||
status = &model.Status{UserId: id, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
status = &model.Status{UserId: id, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], false, status, post) {
|
isGM := channel.Type == model.ChannelTypeGroup
|
||||||
|
if ShouldSendPushNotification(profileMap[id], channelMemberNotifyPropsMap[id], false, status, post, isGM) {
|
||||||
a.sendPushNotification(
|
a.sendPushNotification(
|
||||||
notification,
|
notification,
|
||||||
profileMap[id],
|
profileMap[id],
|
||||||
@@ -770,6 +773,14 @@ func (a *App) getExplicitMentionsAndKeywords(c request.CTX, post *model.Post, ch
|
|||||||
keywords = a.getMentionKeywordsInChannel(profileMap, allowChannelMentions, channelMemberNotifyPropsMap)
|
keywords = a.getMentionKeywordsInChannel(profileMap, allowChannelMentions, channelMemberNotifyPropsMap)
|
||||||
|
|
||||||
mentions = getExplicitMentions(post, keywords, groups)
|
mentions = getExplicitMentions(post, keywords, groups)
|
||||||
|
|
||||||
|
// Add a GM mention to all members of a GM channel
|
||||||
|
if channel.Type == model.ChannelTypeGroup {
|
||||||
|
for id := range channelMemberNotifyPropsMap {
|
||||||
|
mentions.addMention(id, GMMention)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add an implicit mention when a user is added to a channel
|
// Add an implicit mention when a user is added to a channel
|
||||||
// even if the user has set 'username mentions' to false in account settings.
|
// even if the user has set 'username mentions' to false in account settings.
|
||||||
if post.Type == model.PostTypeAddToChannel {
|
if post.Type == model.PostTypeAddToChannel {
|
||||||
@@ -1063,6 +1074,9 @@ const (
|
|||||||
// A placeholder that should never be used in practice
|
// A placeholder that should never be used in practice
|
||||||
NoMention MentionType = iota
|
NoMention MentionType = iota
|
||||||
|
|
||||||
|
// The post is in a GM
|
||||||
|
GMMention
|
||||||
|
|
||||||
// The post is in a thread that the user has commented on
|
// The post is in a thread that the user has commented on
|
||||||
ThreadMention
|
ThreadMention
|
||||||
|
|
||||||
|
|||||||
@@ -512,12 +512,12 @@ func (a *App) getMobileAppSessions(userID string) ([]*model.Session, *model.AppE
|
|||||||
return sessions, nil
|
return sessions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post) bool {
|
func ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post, isGM bool) bool {
|
||||||
return DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned) &&
|
return DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned, isGM) &&
|
||||||
DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId)
|
DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned bool) bool {
|
func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned, isGM bool) bool {
|
||||||
userNotifyProps := user.NotifyProps
|
userNotifyProps := user.NotifyProps
|
||||||
userNotify := userNotifyProps[model.PushNotifyProp]
|
userNotify := userNotifyProps[model.PushNotifyProp]
|
||||||
channelNotify, ok := channelNotifyProps[model.PushNotifyProp]
|
channelNotify, ok := channelNotifyProps[model.PushNotifyProp]
|
||||||
@@ -525,6 +525,14 @@ func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
|
|||||||
channelNotify = model.ChannelNotifyDefault
|
channelNotify = model.ChannelNotifyDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notify := channelNotify
|
||||||
|
if channelNotify == model.ChannelNotifyDefault {
|
||||||
|
notify = userNotify
|
||||||
|
if isGM && userNotify == model.UserNotifyMention {
|
||||||
|
notify = model.ChannelNotifyAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the channel is muted do not send push notifications
|
// If the channel is muted do not send push notifications
|
||||||
if channelNotifyProps[model.MarkUnreadNotifyProp] == model.ChannelMarkUnreadMention {
|
if channelNotifyProps[model.MarkUnreadNotifyProp] == model.ChannelMarkUnreadMention {
|
||||||
return false
|
return false
|
||||||
@@ -534,28 +542,19 @@ func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if channelNotify == model.UserNotifyNone {
|
if notify == model.ChannelNotifyNone {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if channelNotify == model.ChannelNotifyMention && !wasMentioned {
|
if notify == model.ChannelNotifyMention && !wasMentioned {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if userNotify == model.UserNotifyMention && channelNotify == model.ChannelNotifyDefault && !wasMentioned {
|
if (notify == model.ChannelNotifyAll) &&
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userNotify == model.UserNotifyAll || channelNotify == model.ChannelNotifyAll) &&
|
|
||||||
(post.UserId != user.Id || post.GetProp("from_webhook") == "true") {
|
(post.UserId != user.Id || post.GetProp("from_webhook") == "true") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if userNotify == model.UserNotifyNone &&
|
|
||||||
channelNotify == model.ChannelNotifyDefault {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned bool
|
wasMentioned bool
|
||||||
isMuted bool
|
isMuted bool
|
||||||
expected bool
|
expected bool
|
||||||
|
isGM bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "When post is a System Message and has no mentions",
|
name: "When post is a System Message and has no mentions",
|
||||||
@@ -45,6 +46,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When post is a System Message and has mentions",
|
name: "When post is a System Message and has mentions",
|
||||||
@@ -54,6 +56,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, no channel props is set and has no mentions",
|
name: "When default is ALL, no channel props is set and has no mentions",
|
||||||
@@ -63,6 +66,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, no channel props is set and has mentions",
|
name: "When default is ALL, no channel props is set and has mentions",
|
||||||
@@ -72,6 +76,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, no channel props is set and has no mentions",
|
name: "When default is MENTION, no channel props is set and has no mentions",
|
||||||
@@ -81,6 +86,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, no channel props is set and has mentions",
|
name: "When default is MENTION, no channel props is set and has mentions",
|
||||||
@@ -90,6 +96,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, no channel props is set and has no mentions",
|
name: "When default is NONE, no channel props is set and has no mentions",
|
||||||
@@ -99,6 +106,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, no channel props is set and has mentions",
|
name: "When default is NONE, no channel props is set and has mentions",
|
||||||
@@ -108,6 +116,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is DEFAULT and has no mentions",
|
name: "When default is ALL, channel is DEFAULT and has no mentions",
|
||||||
@@ -117,6 +126,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is DEFAULT and has mentions",
|
name: "When default is ALL, channel is DEFAULT and has mentions",
|
||||||
@@ -126,6 +136,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is DEFAULT and has no mentions",
|
name: "When default is MENTION, channel is DEFAULT and has no mentions",
|
||||||
@@ -135,6 +146,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is DEFAULT and has mentions",
|
name: "When default is MENTION, channel is DEFAULT and has mentions",
|
||||||
@@ -144,6 +156,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is DEFAULT and has no mentions",
|
name: "When default is NONE, channel is DEFAULT and has no mentions",
|
||||||
@@ -153,6 +166,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is DEFAULT and has mentions",
|
name: "When default is NONE, channel is DEFAULT and has mentions",
|
||||||
@@ -162,6 +176,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is ALL and has no mentions",
|
name: "When default is ALL, channel is ALL and has no mentions",
|
||||||
@@ -171,6 +186,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is ALL and has mentions",
|
name: "When default is ALL, channel is ALL and has mentions",
|
||||||
@@ -180,6 +196,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is ALL and has no mentions",
|
name: "When default is MENTION, channel is ALL and has no mentions",
|
||||||
@@ -189,6 +206,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is ALL and has mentions",
|
name: "When default is MENTION, channel is ALL and has mentions",
|
||||||
@@ -198,6 +216,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is ALL and has no mentions",
|
name: "When default is NONE, channel is ALL and has no mentions",
|
||||||
@@ -207,6 +226,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is ALL and has mentions",
|
name: "When default is NONE, channel is ALL and has mentions",
|
||||||
@@ -216,6 +236,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is MENTION and has no mentions",
|
name: "When default is ALL, channel is MENTION and has no mentions",
|
||||||
@@ -225,6 +246,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is MENTION and has mentions",
|
name: "When default is ALL, channel is MENTION and has mentions",
|
||||||
@@ -234,6 +256,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is MENTION and has no mentions",
|
name: "When default is MENTION, channel is MENTION and has no mentions",
|
||||||
@@ -243,6 +266,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is MENTION and has mentions",
|
name: "When default is MENTION, channel is MENTION and has mentions",
|
||||||
@@ -252,6 +276,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is MENTION and has no mentions",
|
name: "When default is NONE, channel is MENTION and has no mentions",
|
||||||
@@ -261,6 +286,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is MENTION and has mentions",
|
name: "When default is NONE, channel is MENTION and has mentions",
|
||||||
@@ -270,6 +296,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: true,
|
expected: true,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is NONE and has no mentions",
|
name: "When default is ALL, channel is NONE and has no mentions",
|
||||||
@@ -279,6 +306,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, channel is NONE and has mentions",
|
name: "When default is ALL, channel is NONE and has mentions",
|
||||||
@@ -288,6 +316,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is NONE and has no mentions",
|
name: "When default is MENTION, channel is NONE and has no mentions",
|
||||||
@@ -297,6 +326,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is MENTION, channel is NONE and has mentions",
|
name: "When default is MENTION, channel is NONE and has mentions",
|
||||||
@@ -306,6 +336,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is NONE and has no mentions",
|
name: "When default is NONE, channel is NONE and has no mentions",
|
||||||
@@ -315,6 +346,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is NONE, channel is NONE and has mentions",
|
name: "When default is NONE, channel is NONE and has mentions",
|
||||||
@@ -324,6 +356,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: true,
|
wasMentioned: true,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "When default is ALL, and channel is MUTED",
|
name: "When default is ALL, and channel is MUTED",
|
||||||
@@ -333,6 +366,47 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
wasMentioned: false,
|
wasMentioned: false,
|
||||||
isMuted: true,
|
isMuted: true,
|
||||||
expected: false,
|
expected: false,
|
||||||
|
isGM: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "For GM default for NONE is NONE",
|
||||||
|
userNotifySetting: model.UserNotifyNone,
|
||||||
|
channelNotifySetting: model.ChannelNotifyDefault,
|
||||||
|
withSystemPost: false,
|
||||||
|
wasMentioned: false,
|
||||||
|
isMuted: false,
|
||||||
|
expected: false,
|
||||||
|
isGM: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "For GM, mentioned is only called if explicitly mentioned",
|
||||||
|
userNotifySetting: model.UserNotifyNone,
|
||||||
|
channelNotifySetting: model.ChannelNotifyMention,
|
||||||
|
withSystemPost: false,
|
||||||
|
wasMentioned: true,
|
||||||
|
isMuted: false,
|
||||||
|
expected: true,
|
||||||
|
isGM: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "For GM default for MENTION is ALL",
|
||||||
|
userNotifySetting: model.UserNotifyMention,
|
||||||
|
channelNotifySetting: model.ChannelNotifyDefault,
|
||||||
|
withSystemPost: false,
|
||||||
|
wasMentioned: false,
|
||||||
|
isMuted: false,
|
||||||
|
expected: true,
|
||||||
|
isGM: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "For GM, mentioned is only called if explicitly mentioned",
|
||||||
|
userNotifySetting: model.UserNotifyNone,
|
||||||
|
channelNotifySetting: model.ChannelNotifyMention,
|
||||||
|
withSystemPost: false,
|
||||||
|
wasMentioned: false,
|
||||||
|
isMuted: false,
|
||||||
|
expected: false,
|
||||||
|
isGM: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +426,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
|
|||||||
if tc.isMuted {
|
if tc.isMuted {
|
||||||
channelNotifyProps[model.MarkUnreadNotifyProp] = model.ChannelMarkUnreadMention
|
channelNotifyProps[model.MarkUnreadNotifyProp] = model.ChannelMarkUnreadMention
|
||||||
}
|
}
|
||||||
assert.Equal(t, tc.expected, DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, tc.wasMentioned))
|
assert.Equal(t, tc.expected, DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, tc.wasMentioned, tc.isGM))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,19 +38,21 @@ func TestSendNotifications(t *testing.T) {
|
|||||||
|
|
||||||
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false)
|
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false)
|
||||||
|
|
||||||
post1, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
post1, createPostErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
||||||
UserId: th.BasicUser.Id,
|
UserId: th.BasicUser.Id,
|
||||||
ChannelId: th.BasicChannel.Id,
|
ChannelId: th.BasicChannel.Id,
|
||||||
Message: "@" + th.BasicUser2.Username,
|
Message: "@" + th.BasicUser2.Username,
|
||||||
Type: model.PostTypeAddToChannel,
|
Type: model.PostTypeAddToChannel,
|
||||||
Props: map[string]any{model.PostPropsAddedUserId: "junk"},
|
Props: map[string]any{model.PostPropsAddedUserId: "junk"},
|
||||||
}, true, true)
|
}, true, true)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, createPostErr)
|
||||||
|
|
||||||
mentions, err := th.App.SendNotifications(th.Context, post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
t.Run("Basic channel", func(t *testing.T) {
|
||||||
require.NoError(t, err)
|
mentions, err := th.App.SendNotifications(th.Context, post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
||||||
require.NotNil(t, mentions)
|
require.NoError(t, err)
|
||||||
require.True(t, pUtils.Contains(mentions, th.BasicUser2.Id), "mentions", mentions)
|
require.NotNil(t, mentions)
|
||||||
|
require.True(t, pUtils.Contains(mentions, th.BasicUser2.Id), "mentions", mentions)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("license is required for group mention", func(t *testing.T) {
|
t.Run("license is required for group mention", func(t *testing.T) {
|
||||||
group := th.CreateGroup()
|
group := th.CreateGroup()
|
||||||
@@ -70,7 +72,7 @@ func TestSendNotifications(t *testing.T) {
|
|||||||
groupMentionPost, createPostErr := th.App.CreatePost(th.Context, groupMentionPost, th.BasicChannel, false, true)
|
groupMentionPost, createPostErr := th.App.CreatePost(th.Context, groupMentionPost, th.BasicChannel, false, true)
|
||||||
require.Nil(t, createPostErr)
|
require.Nil(t, createPostErr)
|
||||||
|
|
||||||
mentions, err = th.App.SendNotifications(th.Context, groupMentionPost, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
mentions, err := th.App.SendNotifications(th.Context, groupMentionPost, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, mentions)
|
require.NotNil(t, mentions)
|
||||||
require.Len(t, mentions, 0)
|
require.Len(t, mentions, 0)
|
||||||
@@ -83,40 +85,83 @@ func TestSendNotifications(t *testing.T) {
|
|||||||
require.Len(t, mentions, 1)
|
require.Len(t, mentions, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
t.Run("message in DM generate mention", func(t *testing.T) {
|
||||||
require.Nil(t, appErr)
|
dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
post2, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
post2, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
||||||
UserId: th.BasicUser.Id,
|
UserId: th.BasicUser.Id,
|
||||||
ChannelId: dm.Id,
|
ChannelId: dm.Id,
|
||||||
Message: "dm message",
|
Message: "dm message",
|
||||||
}, true, true)
|
}, true, true)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
mentions, err = th.App.SendNotifications(th.Context, post2, th.BasicTeam, dm, th.BasicUser, nil, true)
|
mentions, err := th.App.SendNotifications(th.Context, post2, th.BasicTeam, dm, th.BasicUser, nil, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, mentions)
|
require.NotNil(t, mentions)
|
||||||
|
|
||||||
_, appErr = th.App.UpdateActive(th.Context, th.BasicUser2, false)
|
_, appErr = th.App.UpdateActive(th.Context, th.BasicUser2, false)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
appErr = th.App.Srv().InvalidateAllCaches()
|
appErr = th.App.Srv().InvalidateAllCaches()
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
post3, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
post3, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
||||||
UserId: th.BasicUser.Id,
|
UserId: th.BasicUser.Id,
|
||||||
ChannelId: dm.Id,
|
ChannelId: dm.Id,
|
||||||
Message: "dm message",
|
Message: "dm message",
|
||||||
}, true, true)
|
}, true, true)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
mentions, err = th.App.SendNotifications(th.Context, post3, th.BasicTeam, dm, th.BasicUser, nil, true)
|
mentions, err = th.App.SendNotifications(th.Context, post3, th.BasicTeam, dm, th.BasicUser, nil, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, mentions)
|
require.NotNil(t, mentions)
|
||||||
|
|
||||||
th.BasicChannel.DeleteAt = 1
|
th.BasicChannel.DeleteAt = 1
|
||||||
mentions, err = th.App.SendNotifications(th.Context, post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
mentions, err = th.App.SendNotifications(th.Context, post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, mentions)
|
require.Empty(t, mentions)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("message in GM generate mention", func(t *testing.T) {
|
||||||
|
users := []*model.User{}
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
user := th.CreateUser()
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
channel := th.CreateGroupChannel(th.Context, users[0], users[1])
|
||||||
|
|
||||||
|
post2, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
||||||
|
UserId: users[0].Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "gm message",
|
||||||
|
}, true, true)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
mentions, err := th.App.SendNotifications(th.Context, post2, th.BasicTeam, channel, users[0], nil, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, mentions)
|
||||||
|
|
||||||
|
_, appErr = th.App.UpdateActive(th.Context, users[1], false)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
appErr = th.App.Srv().InvalidateAllCaches()
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
post3, appErr := th.App.CreatePostMissingChannel(th.Context, &model.Post{
|
||||||
|
UserId: users[0].Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "gm message",
|
||||||
|
}, true, true)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
mentions, err = th.App.SendNotifications(th.Context, post3, th.BasicTeam, channel, users[0], nil, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, mentions)
|
||||||
|
|
||||||
|
th.BasicChannel.DeleteAt = 1
|
||||||
|
mentions, err = th.App.SendNotifications(th.Context, post1, th.BasicTeam, th.BasicChannel, users[0], nil, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, mentions)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("replies to post created by OAuth bot should not notify user", func(t *testing.T) {
|
t.Run("replies to post created by OAuth bot should not notify user", func(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
@@ -129,7 +174,7 @@ func TestSendNotifications(t *testing.T) {
|
|||||||
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
|
Props: model.StringInterface{"from_webhook": "true", "override_username": "a bot"},
|
||||||
}
|
}
|
||||||
|
|
||||||
rootPost, appErr = th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)
|
rootPost, appErr := th.App.CreatePostMissingChannel(th.Context, rootPost, false, true)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
childPost := &model.Post{
|
childPost := &model.Post{
|
||||||
@@ -145,11 +190,12 @@ func TestSendNotifications(t *testing.T) {
|
|||||||
Order: []string{rootPost.Id, childPost.Id},
|
Order: []string{rootPost.Id, childPost.Id},
|
||||||
Posts: map[string]*model.Post{rootPost.Id: rootPost, childPost.Id: childPost},
|
Posts: map[string]*model.Post{rootPost.Id: rootPost, childPost.Id: childPost},
|
||||||
}
|
}
|
||||||
mentions, err = th.App.SendNotifications(th.Context, childPost, th.BasicTeam, th.BasicChannel, th.BasicUser2, &postList, true)
|
mentions, err := th.App.SendNotifications(th.Context, childPost, th.BasicTeam, th.BasicChannel, th.BasicUser2, &postList, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.False(t, pUtils.Contains(mentions, user.Id))
|
require.False(t, pUtils.Contains(mentions, user.Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var appErr *model.AppError
|
||||||
th.BasicUser.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny
|
th.BasicUser.NotifyProps[model.CommentsNotifyProp] = model.CommentsNotifyAny
|
||||||
th.BasicUser, appErr = th.App.UpdateUser(th.Context, th.BasicUser, false)
|
th.BasicUser, appErr = th.App.UpdateUser(th.Context, th.BasicUser, false)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
|
|||||||
@@ -179,17 +179,19 @@ func TestPluginAPIGetUserPreferences(t *testing.T) {
|
|||||||
|
|
||||||
preferences, err := api.GetPreferencesForUser(user1.Id)
|
preferences, err := api.GetPreferencesForUser(user1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(preferences))
|
assert.Equal(t, 3, len(preferences))
|
||||||
|
|
||||||
assert.Equal(t, user1.Id, preferences[0].UserId)
|
assert.Equal(t, user1.Id, preferences[0].UserId)
|
||||||
assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category)
|
assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category)
|
||||||
assert.Equal(t, "hide", preferences[0].Name)
|
assert.Equal(t, "hide", preferences[0].Name)
|
||||||
assert.Equal(t, "false", preferences[0].Value)
|
assert.Equal(t, "false", preferences[0].Value)
|
||||||
|
|
||||||
assert.Equal(t, user1.Id, preferences[1].UserId)
|
assert.Equal(t, model.PreferenceCategorySystemNotice, preferences[1].Category)
|
||||||
assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[1].Category)
|
|
||||||
assert.Equal(t, user1.Id, preferences[1].Name)
|
assert.Equal(t, user1.Id, preferences[2].UserId)
|
||||||
assert.Equal(t, "0", preferences[1].Value)
|
assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[2].Category)
|
||||||
|
assert.Equal(t, user1.Id, preferences[2].Name)
|
||||||
|
assert.Equal(t, "0", preferences[2].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPluginAPIDeleteUserPreferences(t *testing.T) {
|
func TestPluginAPIDeleteUserPreferences(t *testing.T) {
|
||||||
@@ -207,7 +209,7 @@ func TestPluginAPIDeleteUserPreferences(t *testing.T) {
|
|||||||
|
|
||||||
preferences, err := api.GetPreferencesForUser(user1.Id)
|
preferences, err := api.GetPreferencesForUser(user1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(preferences))
|
assert.Equal(t, 3, len(preferences))
|
||||||
|
|
||||||
err = api.DeletePreferencesForUser(user1.Id, preferences)
|
err = api.DeletePreferencesForUser(user1.Id, preferences)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@@ -234,16 +236,16 @@ func TestPluginAPIDeleteUserPreferences(t *testing.T) {
|
|||||||
|
|
||||||
preferences, err = api.GetPreferencesForUser(user2.Id)
|
preferences, err = api.GetPreferencesForUser(user2.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 3, len(preferences))
|
assert.Equal(t, 4, len(preferences))
|
||||||
|
|
||||||
err = api.DeletePreferencesForUser(user2.Id, []model.Preference{preference})
|
err = api.DeletePreferencesForUser(user2.Id, []model.Preference{preference})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
preferences, err = api.GetPreferencesForUser(user2.Id)
|
preferences, err = api.GetPreferencesForUser(user2.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(preferences))
|
assert.Equal(t, 3, len(preferences))
|
||||||
assert.ElementsMatch(t,
|
assert.ElementsMatch(t,
|
||||||
[]string{model.PreferenceRecommendedNextSteps, model.PreferenceCategoryTutorialSteps},
|
[]string{model.PreferenceRecommendedNextSteps, model.PreferenceCategoryTutorialSteps, model.PreferenceCategorySystemNotice},
|
||||||
[]string{preferences[0].Category, preferences[1].Category},
|
[]string{preferences[0].Category, preferences[1].Category, preferences[2].Category},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,16 +264,17 @@ func TestPluginAPIUpdateUserPreferences(t *testing.T) {
|
|||||||
|
|
||||||
preferences, err := api.GetPreferencesForUser(user1.Id)
|
preferences, err := api.GetPreferencesForUser(user1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
assert.Equal(t, 2, len(preferences))
|
assert.Equal(t, 3, len(preferences))
|
||||||
|
|
||||||
assert.Equal(t, user1.Id, preferences[0].UserId)
|
assert.Equal(t, user1.Id, preferences[0].UserId)
|
||||||
assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category)
|
assert.Equal(t, model.PreferenceRecommendedNextSteps, preferences[0].Category)
|
||||||
assert.Equal(t, "hide", preferences[0].Name)
|
assert.Equal(t, "hide", preferences[0].Name)
|
||||||
assert.Equal(t, "false", preferences[0].Value)
|
assert.Equal(t, "false", preferences[0].Value)
|
||||||
assert.Equal(t, user1.Id, preferences[1].UserId)
|
assert.Equal(t, model.PreferenceCategorySystemNotice, preferences[1].Category)
|
||||||
assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[1].Category)
|
assert.Equal(t, user1.Id, preferences[2].UserId)
|
||||||
assert.Equal(t, user1.Id, preferences[1].Name)
|
assert.Equal(t, model.PreferenceCategoryTutorialSteps, preferences[2].Category)
|
||||||
assert.Equal(t, "0", preferences[1].Value)
|
assert.Equal(t, user1.Id, preferences[2].Name)
|
||||||
|
assert.Equal(t, "0", preferences[2].Value)
|
||||||
|
|
||||||
preference := model.Preference{
|
preference := model.Preference{
|
||||||
Name: user1.Id,
|
Name: user1.Id,
|
||||||
@@ -286,8 +289,8 @@ func TestPluginAPIUpdateUserPreferences(t *testing.T) {
|
|||||||
preferences, err = api.GetPreferencesForUser(user1.Id)
|
preferences, err = api.GetPreferencesForUser(user1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 3, len(preferences))
|
assert.Equal(t, 4, len(preferences))
|
||||||
expectedCategories := []string{model.PreferenceCategoryTutorialSteps, model.PreferenceCategoryTheme, model.PreferenceRecommendedNextSteps}
|
expectedCategories := []string{model.PreferenceCategoryTutorialSteps, model.PreferenceCategoryTheme, model.PreferenceRecommendedNextSteps, model.PreferenceCategorySystemNotice}
|
||||||
for _, pref := range preferences {
|
for _, pref := range preferences {
|
||||||
assert.Contains(t, expectedCategories, pref.Category)
|
assert.Contains(t, expectedCategories, pref.Category)
|
||||||
assert.Equal(t, user1.Id, pref.UserId)
|
assert.Equal(t, user1.Id, pref.UserId)
|
||||||
|
|||||||
@@ -275,9 +275,9 @@ func (a *App) channelTeamMapsForPosts(posts []*model.Post) (map[string]*model.Ch
|
|||||||
|
|
||||||
func (a *App) sendPersistentNotifications(post *model.Post, channel *model.Channel, team *model.Team, mentions *ExplicitMentions, profileMap model.UserMap, channelNotifyProps map[string]map[string]model.StringMap) error {
|
func (a *App) sendPersistentNotifications(post *model.Post, channel *model.Channel, team *model.Team, mentions *ExplicitMentions, profileMap model.UserMap, channelNotifyProps map[string]map[string]model.StringMap) error {
|
||||||
mentionedUsersList := make(model.StringArray, 0, len(mentions.Mentions))
|
mentionedUsersList := make(model.StringArray, 0, len(mentions.Mentions))
|
||||||
for id := range mentions.Mentions {
|
for id, v := range mentions.Mentions {
|
||||||
// Don't send notification to post owner
|
// Don't send notification to post owner nor GM mentions
|
||||||
if id != post.UserId {
|
if id != post.UserId && v > GMMention {
|
||||||
mentionedUsersList = append(mentionedUsersList, id)
|
mentionedUsersList = append(mentionedUsersList, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,8 @@ func (a *App) sendPersistentNotifications(post *model.Post, channel *model.Chann
|
|||||||
status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ShouldSendPushNotification(profileMap[userID], channelNotifyProps[channel.Id][userID], true, status, post) {
|
isGM := channel.Type == model.ChannelTypeGroup
|
||||||
|
if ShouldSendPushNotification(profileMap[userID], channelNotifyProps[channel.Id][userID], true, status, post, isGM) {
|
||||||
a.sendPushNotification(
|
a.sendPushNotification(
|
||||||
notification,
|
notification,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -282,8 +282,9 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
|
|||||||
|
|
||||||
recommendedNextStepsPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceRecommendedNextSteps, Name: "hide", Value: "false"}
|
recommendedNextStepsPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceRecommendedNextSteps, Name: "hide", Value: "false"}
|
||||||
tutorialStepPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryTutorialSteps, Name: ruser.Id, Value: "0"}
|
tutorialStepPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceCategoryTutorialSteps, Name: ruser.Id, Value: "0"}
|
||||||
|
gmASdmPref := model.Preference{UserId: ruser.Id, Category: model.PreferenceCategorySystemNotice, Name: "GMasDM", Value: "true"}
|
||||||
|
|
||||||
preferences := model.Preferences{recommendedNextStepsPref, tutorialStepPref}
|
preferences := model.Preferences{recommendedNextStepsPref, tutorialStepPref, gmASdmPref}
|
||||||
if err := a.Srv().Store().Preference().Save(preferences); err != nil {
|
if err := a.Srv().Store().Preference().Save(preferences); err != nil {
|
||||||
c.Logger().Warn("Encountered error saving user preferences", mlog.Err(err))
|
c.Logger().Warn("Encountered error saving user preferences", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1825,6 +1825,11 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
|
|||||||
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
||||||
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
||||||
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
||||||
|
|
||||||
|
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
|
||||||
|
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("successfully create a guest user with initial tutorial and recommended steps preferences", func(t *testing.T) {
|
t.Run("successfully create a guest user with initial tutorial and recommended steps preferences", func(t *testing.T) {
|
||||||
@@ -1842,6 +1847,11 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
|
|||||||
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
||||||
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
||||||
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
||||||
|
|
||||||
|
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
|
||||||
|
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const (
|
|||||||
PreferenceNameUseMilitaryTime = "use_military_time"
|
PreferenceNameUseMilitaryTime = "use_military_time"
|
||||||
PreferenceRecommendedNextSteps = "recommended_next_steps"
|
PreferenceRecommendedNextSteps = "recommended_next_steps"
|
||||||
|
|
||||||
|
PreferenceCategorySystemNotice = "system_notice"
|
||||||
|
|
||||||
PreferenceCategoryTheme = "theme"
|
PreferenceCategoryTheme = "theme"
|
||||||
// the name for theme props is the team id
|
// the name for theme props is the team id
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getTeammateNameDisplaySetting,
|
getTeammateNameDisplaySetting,
|
||||||
isCollapsedThreadsEnabled,
|
isCollapsedThreadsEnabled,
|
||||||
} from 'mattermost-redux/selectors/entities/preferences';
|
} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
|
import {getAllUserMentionKeys} from 'mattermost-redux/selectors/entities/search';
|
||||||
import {getCurrentUserId, getCurrentUser, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
import {getCurrentUserId, getCurrentUser, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||||
import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post_utils';
|
import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post_utils';
|
||||||
@@ -18,11 +19,13 @@ import {getChannelURL, getPermalinkURL} from 'selectors/urls';
|
|||||||
import {isThreadOpen} from 'selectors/views/threads';
|
import {isThreadOpen} from 'selectors/views/threads';
|
||||||
|
|
||||||
import {getHistory} from 'utils/browser_history';
|
import {getHistory} from 'utils/browser_history';
|
||||||
import Constants, {NotificationLevels, UserStatuses} from 'utils/constants';
|
import Constants, {NotificationLevels, UserStatuses, IgnoreChannelMentions} from 'utils/constants';
|
||||||
import {t} from 'utils/i18n';
|
import {t} from 'utils/i18n';
|
||||||
import {stripMarkdown} from 'utils/markdown';
|
import {stripMarkdown, formatWithRenderer} from 'utils/markdown';
|
||||||
|
import MentionableRenderer from 'utils/markdown/mentionable_renderer';
|
||||||
import * as NotificationSounds from 'utils/notification_sounds';
|
import * as NotificationSounds from 'utils/notification_sounds';
|
||||||
import {showNotification} from 'utils/notifications';
|
import {showNotification} from 'utils/notifications';
|
||||||
|
import {cjkrPattern, escapeRegex} from 'utils/text_formatting';
|
||||||
import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent';
|
import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent';
|
||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
|
|
||||||
@@ -93,14 +96,94 @@ export function sendDesktopNotification(post, msgProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifyLevel = member?.notify_props?.desktop || NotificationLevels.DEFAULT;
|
const channelNotifyProp = member?.notify_props?.desktop || NotificationLevels.DEFAULT;
|
||||||
|
let notifyLevel = channelNotifyProp;
|
||||||
|
|
||||||
if (notifyLevel === NotificationLevels.DEFAULT) {
|
if (notifyLevel === NotificationLevels.DEFAULT) {
|
||||||
notifyLevel = user?.notify_props?.desktop || NotificationLevels.ALL;
|
notifyLevel = user?.notify_props?.desktop || NotificationLevels.ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel.type === 'G' && channelNotifyProp === NotificationLevels.DEFAULT && user?.notify_props?.desktop === NotificationLevels.MENTION) {
|
||||||
|
notifyLevel = NotificationLevels.ALL;
|
||||||
|
}
|
||||||
|
|
||||||
if (notifyLevel === NotificationLevels.NONE) {
|
if (notifyLevel === NotificationLevels.NONE) {
|
||||||
return;
|
return;
|
||||||
|
} else if (channel.type === 'G' && notifyLevel === NotificationLevels.MENTION) {
|
||||||
|
// Compose the whole text in the message, including interactive messages.
|
||||||
|
let text = post.message;
|
||||||
|
|
||||||
|
// We do this on a try catch block to avoid errors from malformed props
|
||||||
|
try {
|
||||||
|
if (post.props && post.props.attachments) {
|
||||||
|
const attachments = post.props.attachments;
|
||||||
|
function appendText(toAppend) {
|
||||||
|
if (toAppend) {
|
||||||
|
text += `\n${toAppend}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
appendText(attachment.pretext);
|
||||||
|
appendText(attachment.title);
|
||||||
|
appendText(attachment.text);
|
||||||
|
appendText(attachment.footer);
|
||||||
|
if (attachment.fields) {
|
||||||
|
for (const field of attachment.fields) {
|
||||||
|
appendText(field.title);
|
||||||
|
appendText(field.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Could not process the whole attachment for mentions', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allMentions = getAllUserMentionKeys(state);
|
||||||
|
|
||||||
|
const ignoreChannelMentionProp = member?.notify_props?.ignore_channel_mentions || IgnoreChannelMentions.DEFAULT;
|
||||||
|
let ignoreChannelMention = ignoreChannelMentionProp === IgnoreChannelMentions.ON;
|
||||||
|
if (ignoreChannelMentionProp === IgnoreChannelMentions.DEFAULT) {
|
||||||
|
ignoreChannelMention = user?.notify_props?.channel === 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mentionableText = formatWithRenderer(text, new MentionableRenderer());
|
||||||
|
let isExplicitlyMentioned = false;
|
||||||
|
for (const mention of allMentions) {
|
||||||
|
if (!mention || !mention.key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreChannelMention && ['@all', '@here', '@channel'].includes(mention.key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = 'g';
|
||||||
|
if (!mention.caseSensitive) {
|
||||||
|
flags += 'i';
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern;
|
||||||
|
if (cjkrPattern.test(mention.key)) {
|
||||||
|
// In the case of CJK mention key, even if there's no delimiters (such as spaces) at both ends of a word, it is recognized as a mention key
|
||||||
|
pattern = new RegExp(`()(${escapeRegex(mention.key)})()`, flags);
|
||||||
|
} else {
|
||||||
|
pattern = new RegExp(
|
||||||
|
`(^|\\W)(${escapeRegex(mention.key)})(\\b|_+\\b)`,
|
||||||
|
flags,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.test(mentionableText)) {
|
||||||
|
isExplicitlyMentioned = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isExplicitlyMentioned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else if (notifyLevel === NotificationLevels.MENTION && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) {
|
} else if (notifyLevel === NotificationLevels.MENTION && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) {
|
||||||
return;
|
return;
|
||||||
} else if (isCrtReply && notifyLevel === NotificationLevels.ALL && followers.indexOf(currentUserId) === -1) {
|
} else if (isCrtReply && notifyLevel === NotificationLevels.ALL && followers.indexOf(currentUserId) === -1) {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ describe('notification_actions', () => {
|
|||||||
desktop: NotificationLevels.ALL,
|
desktop: NotificationLevels.ALL,
|
||||||
desktop_sound: false,
|
desktop_sound: false,
|
||||||
desktop_threads: NotificationLevels.ALL,
|
desktop_threads: NotificationLevels.ALL,
|
||||||
|
mention_keys: 'mentionkey',
|
||||||
|
first_name: 'true',
|
||||||
|
channel: 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
post = {
|
post = {
|
||||||
@@ -79,8 +82,13 @@ describe('notification_actions', () => {
|
|||||||
current_user_id: {
|
current_user_id: {
|
||||||
id: 'current_user_id',
|
id: 'current_user_id',
|
||||||
notify_props: userSettings,
|
notify_props: userSettings,
|
||||||
|
username: 'currentusername',
|
||||||
|
first_name: 'currentuserfirstname',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
profilesInChannel: {
|
||||||
|
gm_channel: new Set(['current_user_id']),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
teams: {
|
teams: {
|
||||||
currentTeamId: 'team_id',
|
currentTeamId: 'team_id',
|
||||||
@@ -109,12 +117,20 @@ describe('notification_actions', () => {
|
|||||||
id: 'another_channel_id',
|
id: 'another_channel_id',
|
||||||
team_id: 'team_id',
|
team_id: 'team_id',
|
||||||
},
|
},
|
||||||
|
gm_channel: {
|
||||||
|
id: 'gm_channel',
|
||||||
|
type: 'G',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
myMembers: {
|
myMembers: {
|
||||||
channel_id: {
|
channel_id: {
|
||||||
id: 'current_user_id',
|
id: 'current_user_id',
|
||||||
notify_props: channelSettings,
|
notify_props: channelSettings,
|
||||||
},
|
},
|
||||||
|
gm_channel: {
|
||||||
|
id: 'gm_channel',
|
||||||
|
notify_props: channelSettings,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
membersInChannel: {
|
membersInChannel: {
|
||||||
channel_id: {
|
channel_id: {
|
||||||
@@ -123,6 +139,12 @@ describe('notification_actions', () => {
|
|||||||
notify_props: channelSettings,
|
notify_props: channelSettings,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
gm_channel: {
|
||||||
|
current_user_id: {
|
||||||
|
id: 'gm_channel',
|
||||||
|
notify_props: channelSettings,
|
||||||
|
},
|
||||||
|
},
|
||||||
muted_channel_id: {
|
muted_channel_id: {
|
||||||
current_user_id: {
|
current_user_id: {
|
||||||
id: 'current_user_id',
|
id: 'current_user_id',
|
||||||
@@ -138,6 +160,10 @@ describe('notification_actions', () => {
|
|||||||
'display_settings--collapsed_reply_threads': crt,
|
'display_settings--collapsed_reply_threads': crt,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
groups: {
|
||||||
|
groups: {},
|
||||||
|
myGroups: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
browser: {
|
browser: {
|
||||||
@@ -401,5 +427,85 @@ describe('notification_actions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GMs', () => {
|
||||||
|
test('should notify for any message when channel setting is DEFAULT and user setting is MENTION', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
userSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
channelSettings.desktop = NotificationLevels.DEFAULT;
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should not notify for any message when channel setting is DEFAULT and user setting is NONE', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
userSettings.desktop = NotificationLevels.NONE;
|
||||||
|
channelSettings.desktop = NotificationLevels.DEFAULT;
|
||||||
|
post.message = '@username';
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should notify when channel setting MENTION and there is a explicit mention', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
channelSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
post.message = '@currentusername';
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should notify when channel setting MENTION and there is a keyword mention', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
channelSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
post.message = 'mentionkey';
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should notify when channel setting MENTION and there is the first name', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
channelSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
post.message = 'currentuserfirstname';
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should notify when channel setting MENTION and there is a channel mention', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
channelSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
post.message = '@all';
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should not notify when channel setting MENTION and there is no explicit mention', async () => {
|
||||||
|
const store = testConfigureStore(baseState);
|
||||||
|
channelSettings.desktop = NotificationLevels.MENTION;
|
||||||
|
post.channel_id = 'gm_channel';
|
||||||
|
msgProps.team_id = '';
|
||||||
|
|
||||||
|
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
/>
|
/>
|
||||||
<NotificationSection
|
<NotificationSection
|
||||||
expand={false}
|
expand={false}
|
||||||
|
isGM={false}
|
||||||
memberNotificationLevel="all"
|
memberNotificationLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@@ -83,6 +84,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
<NotificationSection
|
<NotificationSection
|
||||||
expand={false}
|
expand={false}
|
||||||
ignoreChannelMentions="off"
|
ignoreChannelMentions="off"
|
||||||
|
isGM={false}
|
||||||
memberNotificationLevel="all"
|
memberNotificationLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@@ -98,6 +100,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
expand={false}
|
expand={false}
|
||||||
globalNotificationLevel="all"
|
globalNotificationLevel="all"
|
||||||
globalNotificationSound="Bing"
|
globalNotificationSound="Bing"
|
||||||
|
isGM={false}
|
||||||
isNotificationsSettingSameAsGlobal={true}
|
isNotificationsSettingSameAsGlobal={true}
|
||||||
memberDesktopNotificationSound="Bing"
|
memberDesktopNotificationSound="Bing"
|
||||||
memberDesktopSound="on"
|
memberDesktopSound="on"
|
||||||
@@ -118,6 +121,8 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
/>
|
/>
|
||||||
<NotificationSection
|
<NotificationSection
|
||||||
expand={false}
|
expand={false}
|
||||||
|
globalNotificationLevel="all"
|
||||||
|
isGM={false}
|
||||||
isNotificationsSettingSameAsGlobal={true}
|
isNotificationsSettingSameAsGlobal={true}
|
||||||
memberNotificationLevel="all"
|
memberNotificationLevel="all"
|
||||||
memberThreadsNotificationLevel="all"
|
memberThreadsNotificationLevel="all"
|
||||||
@@ -137,6 +142,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
channelAutoFollowThreads="off"
|
channelAutoFollowThreads="off"
|
||||||
expand={false}
|
expand={false}
|
||||||
ignoreChannelMentions="off"
|
ignoreChannelMentions="off"
|
||||||
|
isGM={false}
|
||||||
memberNotificationLevel="all"
|
memberNotificationLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onSubmit={[Function]}
|
onSubmit={[Function]}
|
||||||
@@ -153,3 +159,148 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
|||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`components/channel_notifications_modal/ChannelNotificationsModal should match snapshot for GMs 1`] = `
|
||||||
|
<Modal
|
||||||
|
animation={true}
|
||||||
|
aria-labelledby="channelNotificationModalLabel"
|
||||||
|
autoFocus={true}
|
||||||
|
backdrop={true}
|
||||||
|
bsClass="modal"
|
||||||
|
dialogClassName="a11y__modal settings-modal settings-modal--tabless"
|
||||||
|
dialogComponentClass={[Function]}
|
||||||
|
enforceFocus={true}
|
||||||
|
keyboard={true}
|
||||||
|
manager={
|
||||||
|
ModalManager {
|
||||||
|
"add": [Function],
|
||||||
|
"containers": Array [],
|
||||||
|
"data": Array [],
|
||||||
|
"handleContainerOverflow": true,
|
||||||
|
"hideSiblingNodes": true,
|
||||||
|
"isTopModal": [Function],
|
||||||
|
"modals": Array [],
|
||||||
|
"remove": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExited={[Function]}
|
||||||
|
onHide={[Function]}
|
||||||
|
renderBackdrop={[Function]}
|
||||||
|
restoreFocus={true}
|
||||||
|
role="dialog"
|
||||||
|
show={true}
|
||||||
|
>
|
||||||
|
<ModalHeader
|
||||||
|
bsClass="modal-header"
|
||||||
|
closeButton={true}
|
||||||
|
closeLabel="Close"
|
||||||
|
>
|
||||||
|
<ModalTitle
|
||||||
|
bsClass="modal-title"
|
||||||
|
componentClass="h1"
|
||||||
|
id="channelNotificationModalLabel"
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Notification Preferences for "
|
||||||
|
id="channel_notifications.preferences"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="name"
|
||||||
|
>
|
||||||
|
channel_display_name
|
||||||
|
</span>
|
||||||
|
</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody
|
||||||
|
bsClass="modal-body"
|
||||||
|
componentClass="div"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settings-table"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="settings-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="user-settings"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
className="divider-dark first"
|
||||||
|
/>
|
||||||
|
<NotificationSection
|
||||||
|
expand={false}
|
||||||
|
isGM={true}
|
||||||
|
memberNotificationLevel="all"
|
||||||
|
onChange={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
onUpdateSection={[Function]}
|
||||||
|
section="markUnread"
|
||||||
|
serverError={null}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="divider-light"
|
||||||
|
/>
|
||||||
|
<NotificationSection
|
||||||
|
expand={false}
|
||||||
|
ignoreChannelMentions="off"
|
||||||
|
isGM={true}
|
||||||
|
memberNotificationLevel="all"
|
||||||
|
onChange={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
onUpdateSection={[Function]}
|
||||||
|
section="ignoreChannelMentions"
|
||||||
|
serverError={null}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="divider-light"
|
||||||
|
/>
|
||||||
|
<NotificationSection
|
||||||
|
expand={false}
|
||||||
|
globalNotificationLevel="all"
|
||||||
|
globalNotificationSound="Bing"
|
||||||
|
isGM={true}
|
||||||
|
isNotificationsSettingSameAsGlobal={false}
|
||||||
|
memberDesktopNotificationSound="Bing"
|
||||||
|
memberDesktopSound="on"
|
||||||
|
memberNotificationLevel="mention"
|
||||||
|
memberThreadsNotificationLevel="all"
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeDesktopSound={[Function]}
|
||||||
|
onChangeNotificationSound={[Function]}
|
||||||
|
onChangeThreads={[Function]}
|
||||||
|
onReset={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
onUpdateSection={[Function]}
|
||||||
|
section="desktop"
|
||||||
|
serverError={null}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="divider-light"
|
||||||
|
/>
|
||||||
|
<NotificationSection
|
||||||
|
expand={false}
|
||||||
|
globalNotificationLevel="all"
|
||||||
|
isGM={true}
|
||||||
|
isNotificationsSettingSameAsGlobal={false}
|
||||||
|
memberNotificationLevel="mention"
|
||||||
|
memberThreadsNotificationLevel="all"
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeThreads={[Function]}
|
||||||
|
onReset={[Function]}
|
||||||
|
onSubmit={[Function]}
|
||||||
|
onUpdateSection={[Function]}
|
||||||
|
section="push"
|
||||||
|
serverError={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="divider-dark"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
`;
|
||||||
|
|||||||
@@ -54,6 +54,37 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should match snapshot for GMs', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ChannelNotificationsModal
|
||||||
|
{...{
|
||||||
|
...baseProps,
|
||||||
|
channel: TestHelper.getChannelMock({
|
||||||
|
id: 'channel_id',
|
||||||
|
display_name: 'channel_display_name',
|
||||||
|
type: 'G',
|
||||||
|
}),
|
||||||
|
channelMember: {
|
||||||
|
notify_props: {
|
||||||
|
...baseProps.channelMember!.notify_props,
|
||||||
|
desktop: NotificationLevels.MENTION,
|
||||||
|
push: NotificationLevels.MENTION,
|
||||||
|
},
|
||||||
|
} as unknown as ChannelMembership,
|
||||||
|
currentUser: TestHelper.getUserMock({
|
||||||
|
id: 'current_user_id',
|
||||||
|
notify_props: {
|
||||||
|
desktop: NotificationLevels.MENTION,
|
||||||
|
desktop_threads: NotificationLevels.ALL,
|
||||||
|
} as UserNotifyProps,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test('should provide default notify props when missing', () => {
|
test('should provide default notify props when missing', () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ChannelNotificationsModal
|
<ChannelNotificationsModal
|
||||||
|
|||||||
@@ -56,9 +56,13 @@ type State = {
|
|||||||
export type DesktopNotificationProps = Pick<State, 'desktopNotifyLevel' | 'desktopNotifySound' | 'desktopSound' | 'desktopThreadsNotifyLevel'>
|
export type DesktopNotificationProps = Pick<State, 'desktopNotifyLevel' | 'desktopNotifySound' | 'desktopSound' | 'desktopThreadsNotifyLevel'>
|
||||||
export type PushNotificationProps = Pick<State, 'pushNotifyLevel' | 'pushThreadsNotifyLevel'>
|
export type PushNotificationProps = Pick<State, 'pushNotifyLevel' | 'pushThreadsNotifyLevel'>
|
||||||
|
|
||||||
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps, isGM: boolean): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
||||||
if (currentUserNotifyProps?.desktop) {
|
if (currentUserNotifyProps?.desktop) {
|
||||||
if (currentUserNotifyProps.desktop === 'default') {
|
if (currentUserNotifyProps.desktop === NotificationLevels.DEFAULT) {
|
||||||
|
return NotificationLevels.ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||||
return NotificationLevels.ALL;
|
return NotificationLevels.ALL;
|
||||||
}
|
}
|
||||||
return currentUserNotifyProps.desktop;
|
return currentUserNotifyProps.desktop;
|
||||||
@@ -86,11 +90,16 @@ const getDefaultDesktopThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyP
|
|||||||
return NotificationLevels.ALL;
|
return NotificationLevels.ALL;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps, isGM: boolean): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
||||||
if (currentUserNotifyProps?.push) {
|
if (currentUserNotifyProps?.push) {
|
||||||
if (currentUserNotifyProps.push === 'default') {
|
if (currentUserNotifyProps.push === NotificationLevels.DEFAULT) {
|
||||||
return NotificationLevels.ALL;
|
return NotificationLevels.ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||||
|
return NotificationLevels.ALL;
|
||||||
|
}
|
||||||
|
|
||||||
return currentUserNotifyProps.push;
|
return currentUserNotifyProps.push;
|
||||||
}
|
}
|
||||||
return NotificationLevels.ALL;
|
return NotificationLevels.ALL;
|
||||||
@@ -142,7 +151,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps) &&
|
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()) &&
|
||||||
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
|
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
|
||||||
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
|
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
|
||||||
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
|
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
|
||||||
@@ -152,6 +161,10 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isGM() {
|
||||||
|
return this.props.channel.type === 'G';
|
||||||
|
}
|
||||||
|
|
||||||
verifyPushNotificationsSettingSameAsGlobal({
|
verifyPushNotificationsSettingSameAsGlobal({
|
||||||
pushNotifyLevel,
|
pushNotifyLevel,
|
||||||
pushThreadsNotifyLevel,
|
pushThreadsNotifyLevel,
|
||||||
@@ -159,7 +172,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps) &&
|
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM()) &&
|
||||||
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps)
|
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
@@ -170,13 +183,15 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||||
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
||||||
|
|
||||||
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps);
|
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM());
|
||||||
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps);
|
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM());
|
||||||
let pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps);
|
let pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps);
|
||||||
|
|
||||||
if (channelMemberNotifyProps?.desktop) {
|
if (channelMemberNotifyProps?.desktop) {
|
||||||
if (channelMemberNotifyProps.desktop !== 'default') {
|
if (channelMemberNotifyProps.desktop !== 'default') {
|
||||||
desktopNotifyLevelDefault = channelMemberNotifyProps.desktop;
|
desktopNotifyLevelDefault = channelMemberNotifyProps.desktop;
|
||||||
|
} else if (this.isGM()) {
|
||||||
|
desktopNotifyLevelDefault = NotificationLevels.ALL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (channelMemberNotifyProps?.push) {
|
if (channelMemberNotifyProps?.push) {
|
||||||
@@ -187,6 +202,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
if (channelMemberNotifyProps?.push_threads) {
|
if (channelMemberNotifyProps?.push_threads) {
|
||||||
if (channelMemberNotifyProps.push_threads !== 'default') {
|
if (channelMemberNotifyProps.push_threads !== 'default') {
|
||||||
pushThreadsNotifyLevelDefault = channelMemberNotifyProps.push_threads;
|
pushThreadsNotifyLevelDefault = channelMemberNotifyProps.push_threads;
|
||||||
|
} else if (this.isGM()) {
|
||||||
|
pushThreadsNotifyLevelDefault = NotificationLevels.ALL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +263,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||||
|
|
||||||
const userDesktopNotificationDefaults = {
|
const userDesktopNotificationDefaults = {
|
||||||
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps),
|
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()),
|
||||||
desktopSound: getDefaultDesktopSound(currentUserNotifyProps),
|
desktopSound: getDefaultDesktopSound(currentUserNotifyProps),
|
||||||
desktopNotifySound: getDefaultDesktopNotificationSound(currentUserNotifyProps),
|
desktopNotifySound: getDefaultDesktopNotificationSound(currentUserNotifyProps),
|
||||||
desktopThreadsNotifyLevel: getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
|
desktopThreadsNotifyLevel: getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
|
||||||
@@ -259,7 +276,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||||
|
|
||||||
const userPushNotificationDefaults = {
|
const userPushNotificationDefaults = {
|
||||||
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps),
|
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM()),
|
||||||
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps),
|
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,6 +417,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
serverErrorTag = <div className='form-group has-error'><label className='control-label'>{serverError}</label></div>;
|
serverErrorTag = <div className='form-group has-error'><label className='control-label'>{serverError}</label></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isGM = this.isGM();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
dialogClassName='a11y__modal settings-modal settings-modal--tabless'
|
dialogClassName='a11y__modal settings-modal settings-modal--tabless'
|
||||||
@@ -435,6 +454,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
onSubmit={this.handleSubmitMarkUnreadLevel}
|
onSubmit={this.handleSubmitMarkUnreadLevel}
|
||||||
onUpdateSection={this.updateSection}
|
onUpdateSection={this.updateSection}
|
||||||
serverError={serverError}
|
serverError={serverError}
|
||||||
|
isGM={isGM}
|
||||||
/>
|
/>
|
||||||
<div className='divider-light'/>
|
<div className='divider-light'/>
|
||||||
<NotificationSection
|
<NotificationSection
|
||||||
@@ -446,6 +466,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
onSubmit={this.handleSubmitIgnoreChannelMentions}
|
onSubmit={this.handleSubmitIgnoreChannelMentions}
|
||||||
onUpdateSection={this.updateSection}
|
onUpdateSection={this.updateSection}
|
||||||
serverError={serverError}
|
serverError={serverError}
|
||||||
|
isGM={isGM}
|
||||||
/>
|
/>
|
||||||
{!isChannelMuted(channelMember) &&
|
{!isChannelMuted(channelMember) &&
|
||||||
<div>
|
<div>
|
||||||
@@ -457,8 +478,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
|
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
|
||||||
memberDesktopSound={desktopSound}
|
memberDesktopSound={desktopSound}
|
||||||
memberDesktopNotificationSound={desktopNotifySound}
|
memberDesktopNotificationSound={desktopNotifySound}
|
||||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.desktop : NotificationLevels.ALL}
|
globalNotificationLevel={getDefaultDesktopNotificationLevel(currentUser.notify_props, isGM)}
|
||||||
globalNotificationSound={(currentUser.notify_props && currentUser.notify_props.desktop_notification_sound) ? currentUser.notify_props.desktop_notification_sound : 'Bing'}
|
globalNotificationSound={getDefaultDesktopNotificationSound(currentUser.notify_props)}
|
||||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||||
onChange={this.handleUpdateDesktopNotifyLevel}
|
onChange={this.handleUpdateDesktopNotifyLevel}
|
||||||
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
|
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
|
||||||
@@ -468,6 +489,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
onSubmit={this.handleSubmitDesktopNotification}
|
onSubmit={this.handleSubmitDesktopNotification}
|
||||||
onUpdateSection={this.updateSection}
|
onUpdateSection={this.updateSection}
|
||||||
serverError={serverError}
|
serverError={serverError}
|
||||||
|
isGM={isGM}
|
||||||
/>
|
/>
|
||||||
<div className='divider-light'/>
|
<div className='divider-light'/>
|
||||||
{sendPushNotifications &&
|
{sendPushNotifications &&
|
||||||
@@ -476,7 +498,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
expand={activeSection === NotificationSections.PUSH}
|
expand={activeSection === NotificationSections.PUSH}
|
||||||
memberNotificationLevel={pushNotifyLevel}
|
memberNotificationLevel={pushNotifyLevel}
|
||||||
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
|
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
|
||||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.push : NotificationLevels.ALL}
|
globalNotificationLevel={getDefaultPushNotifyLevel(currentUser.notify_props, isGM)}
|
||||||
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
|
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
|
||||||
onChange={this.handleUpdatePushNotificationLevel}
|
onChange={this.handleUpdatePushNotificationLevel}
|
||||||
onReset={this.handleResetPushNotification}
|
onReset={this.handleResetPushNotification}
|
||||||
@@ -484,22 +506,28 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
|||||||
onSubmit={this.handleSubmitPushNotificationLevel}
|
onSubmit={this.handleSubmitPushNotificationLevel}
|
||||||
onUpdateSection={this.updateSection}
|
onUpdateSection={this.updateSection}
|
||||||
serverError={serverError}
|
serverError={serverError}
|
||||||
|
isGM={isGM}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className='divider-light'/>
|
{!isGM &&
|
||||||
<NotificationSection
|
<>
|
||||||
section={NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
<div className='divider-light'/>
|
||||||
expand={activeSection === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
<NotificationSection
|
||||||
memberNotificationLevel={markUnreadNotifyLevel}
|
section={NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||||
ignoreChannelMentions={ignoreChannelMentions}
|
expand={activeSection === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
memberNotificationLevel={markUnreadNotifyLevel}
|
||||||
onChange={this.handleUpdateChannelAutoFollowThreads}
|
ignoreChannelMentions={ignoreChannelMentions}
|
||||||
onSubmit={this.handleSubmitChannelAutoFollowThreads}
|
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||||
onUpdateSection={this.updateSection}
|
onChange={this.handleUpdateChannelAutoFollowThreads}
|
||||||
serverError={serverError}
|
onSubmit={this.handleSubmitChannelAutoFollowThreads}
|
||||||
/>
|
onUpdateSection={this.updateSection}
|
||||||
|
serverError={serverError}
|
||||||
|
isGM={isGM}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
<div className='divider-dark'/>
|
<div className='divider-dark'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="For all activity ({isDefault})"
|
defaultMessage="For all activity {isDefault}"
|
||||||
id="channel_notifications.allActivity"
|
id="channel_notifications.allActivity"
|
||||||
values={
|
values={
|
||||||
Object {
|
Object {
|
||||||
@@ -21,7 +21,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
|||||||
|
|
||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="Only for mentions ({isDefault})"
|
defaultMessage="Only for mentions {isDefault}"
|
||||||
id="channel_notifications.onlyMentions"
|
id="channel_notifications.onlyMentions"
|
||||||
values={
|
values={
|
||||||
Object {
|
Object {
|
||||||
@@ -33,7 +33,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
|||||||
|
|
||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on NONE 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on NONE 1`] = `
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="Never ({isDefault})"
|
defaultMessage="Never {isDefault}"
|
||||||
id="channel_notifications.never"
|
id="channel_notifications.never"
|
||||||
values={
|
values={
|
||||||
Object {
|
Object {
|
||||||
|
|||||||
@@ -1,6 +1,269 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, DESKTOP on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||||
|
<SettingItemMax
|
||||||
|
containerStyle=""
|
||||||
|
infoPosition="bottom"
|
||||||
|
inputs={
|
||||||
|
Array [
|
||||||
|
<div>
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Send desktop notifications"
|
||||||
|
id="channel_notifications.sendDesktop"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationAllActivity"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="all"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="all"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="channelNotificationMentions"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="mention"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="mention"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationNever"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="none"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="none"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<ExtraInfo
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<React.Fragment>
|
||||||
|
<hr />
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notification sound"
|
||||||
|
id="channel_notifications.sound"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelDesktopSoundOn"
|
||||||
|
name="channelDesktopSound"
|
||||||
|
type="radio"
|
||||||
|
value="on"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="On"
|
||||||
|
id="channel_notifications.sound.on.title"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelDesktopSoundOff"
|
||||||
|
name="channelDesktopSound"
|
||||||
|
type="radio"
|
||||||
|
value="off"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Off"
|
||||||
|
id="channel_notifications.sound.off.title"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
|
||||||
|
id="channel_notifications.sound_info"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</React.Fragment>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
saving={false}
|
||||||
|
section=""
|
||||||
|
serverError=""
|
||||||
|
submit={[MockFunction]}
|
||||||
|
title={
|
||||||
|
<SectionTitle
|
||||||
|
isExpanded={true}
|
||||||
|
onClickResetButton={[MockFunction]}
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
updateSection={[MockFunction]}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||||
|
<SettingItemMax
|
||||||
|
containerStyle=""
|
||||||
|
infoPosition="bottom"
|
||||||
|
inputs={
|
||||||
|
Array [
|
||||||
|
<div>
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Send mobile push notifications"
|
||||||
|
id="channel_notifications.sendMobilePush"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationAllActivity"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="all"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="all"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="channelNotificationMentions"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="mention"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="mention"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationNever"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="none"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="none"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<ExtraInfo
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
saving={false}
|
||||||
|
section=""
|
||||||
|
serverError=""
|
||||||
|
submit={[MockFunction]}
|
||||||
|
title={
|
||||||
|
<SectionTitle
|
||||||
|
isExpanded={true}
|
||||||
|
onClickResetButton={[MockFunction]}
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
updateSection={[MockFunction]}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view 1`] = `
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
containerStyle=""
|
containerStyle=""
|
||||||
infoPosition="bottom"
|
infoPosition="bottom"
|
||||||
@@ -160,7 +423,206 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
|||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||||
|
<SettingItemMax
|
||||||
|
containerStyle=""
|
||||||
|
infoPosition="bottom"
|
||||||
|
inputs={
|
||||||
|
Array [
|
||||||
|
<div>
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Send desktop notifications"
|
||||||
|
id="channel_notifications.sendDesktop"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationAllActivity"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="all"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="all"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="channelNotificationMentions"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="mention"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="mention"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationNever"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="none"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="none"
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<ExtraInfo
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<React.Fragment>
|
||||||
|
<hr />
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Thread reply notifications"
|
||||||
|
id="user.settings.notifications.threads.desktop"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="checkbox"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="desktopThreadsNotificationAllActivity"
|
||||||
|
name="desktopThreadsNotificationLevel"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notify me about threads I'm following"
|
||||||
|
id="user.settings.notifications.threads.allActivity"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="When enabled, any reply to a thread you're following will send a desktop notification."
|
||||||
|
id="user.settings.notifications.threads"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</React.Fragment>
|
||||||
|
<React.Fragment>
|
||||||
|
<hr />
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notification sound"
|
||||||
|
id="channel_notifications.sound"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelDesktopSoundOn"
|
||||||
|
name="channelDesktopSound"
|
||||||
|
type="radio"
|
||||||
|
value="on"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="On"
|
||||||
|
id="channel_notifications.sound.on.title"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelDesktopSoundOff"
|
||||||
|
name="channelDesktopSound"
|
||||||
|
type="radio"
|
||||||
|
value="off"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Off"
|
||||||
|
id="channel_notifications.sound.off.title"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
|
||||||
|
id="channel_notifications.sound_info"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</React.Fragment>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
saving={false}
|
||||||
|
section=""
|
||||||
|
serverError=""
|
||||||
|
submit={[MockFunction]}
|
||||||
|
title={
|
||||||
|
<SectionTitle
|
||||||
|
isExpanded={true}
|
||||||
|
onClickResetButton={[MockFunction]}
|
||||||
|
section="desktop"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
updateSection={[MockFunction]}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
containerStyle=""
|
containerStyle=""
|
||||||
infoPosition="bottom"
|
infoPosition="bottom"
|
||||||
@@ -234,7 +696,7 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
|||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, PUSH on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view 1`] = `
|
||||||
<SettingItemMax
|
<SettingItemMax
|
||||||
containerStyle=""
|
containerStyle=""
|
||||||
infoPosition="bottom"
|
infoPosition="bottom"
|
||||||
@@ -336,3 +798,145 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
|||||||
updateSection={[MockFunction]}
|
updateSection={[MockFunction]}
|
||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||||
|
<SettingItemMax
|
||||||
|
containerStyle=""
|
||||||
|
infoPosition="bottom"
|
||||||
|
inputs={
|
||||||
|
Array [
|
||||||
|
<div>
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Send mobile push notifications"
|
||||||
|
id="channel_notifications.sendMobilePush"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationAllActivity"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="all"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="all"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="channelNotificationMentions"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="mention"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="mention"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="radio"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={false}
|
||||||
|
id="channelNotificationNever"
|
||||||
|
name="channelNotifications"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="radio"
|
||||||
|
value="none"
|
||||||
|
/>
|
||||||
|
<Describe
|
||||||
|
globalNotifyLevel="default"
|
||||||
|
memberNotifyLevel="none"
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<ExtraInfo
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<React.Fragment>
|
||||||
|
<hr />
|
||||||
|
<fieldset>
|
||||||
|
<legend
|
||||||
|
className="form-legend"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Thread reply notifications"
|
||||||
|
id="user.settings.notifications.threads.push"
|
||||||
|
/>
|
||||||
|
</legend>
|
||||||
|
<div
|
||||||
|
className="checkbox"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked={true}
|
||||||
|
id="pushThreadsNotificationAllActivity"
|
||||||
|
name="pushThreadsNotificationLevel"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="Notify me about threads I'm following"
|
||||||
|
id="user.settings.notifications.push_threads.allActivity"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-5"
|
||||||
|
>
|
||||||
|
<Memo(MemoizedFormattedMessage)
|
||||||
|
defaultMessage="When enabled, any reply to a thread you're following will send a mobile push notification."
|
||||||
|
id="user.settings.notifications.push_threads"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</React.Fragment>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
saving={false}
|
||||||
|
section=""
|
||||||
|
serverError=""
|
||||||
|
submit={[MockFunction]}
|
||||||
|
title={
|
||||||
|
<SectionTitle
|
||||||
|
isExpanded={true}
|
||||||
|
onClickResetButton={[MockFunction]}
|
||||||
|
section="push"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
updateSection={[MockFunction]}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
|||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, DESKTOP on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, DESKTOP on expanded view 1`] = `
|
||||||
<ExpandView
|
<ExpandView
|
||||||
globalNotifyLevel="default"
|
globalNotifyLevel="default"
|
||||||
|
isGM={false}
|
||||||
memberNotifyLevel="all"
|
memberNotifyLevel="all"
|
||||||
memberThreadsNotifyLevel="all"
|
memberThreadsNotifyLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -47,6 +48,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
|||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||||
<ExpandView
|
<ExpandView
|
||||||
globalNotifyLevel={null}
|
globalNotifyLevel={null}
|
||||||
|
isGM={false}
|
||||||
memberNotifyLevel="all"
|
memberNotifyLevel="all"
|
||||||
memberThreadsNotifyLevel="all"
|
memberThreadsNotifyLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -73,6 +75,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
|||||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, PUSH on expanded view 1`] = `
|
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, PUSH on expanded view 1`] = `
|
||||||
<ExpandView
|
<ExpandView
|
||||||
globalNotifyLevel="default"
|
globalNotifyLevel="default"
|
||||||
|
isGM={false}
|
||||||
memberNotifyLevel="all"
|
memberNotifyLevel="all"
|
||||||
memberThreadsNotifyLevel="all"
|
memberThreadsNotifyLevel="all"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
|||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='channel_notifications.onlyMentions'
|
id='channel_notifications.onlyMentions'
|
||||||
defaultMessage='Only for mentions ({isDefault})'
|
defaultMessage='Only for mentions {isDefault}'
|
||||||
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
|
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -113,7 +113,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
|||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='channel_notifications.allActivity'
|
id='channel_notifications.allActivity'
|
||||||
defaultMessage='For all activity ({isDefault})'
|
defaultMessage='For all activity {isDefault}'
|
||||||
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
|
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -132,7 +132,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
|||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='channel_notifications.never'
|
id='channel_notifications.never'
|
||||||
defaultMessage='Never ({isDefault})'
|
defaultMessage='Never {isDefault}'
|
||||||
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
|
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {NotificationLevels, NotificationSections} from 'utils/constants';
|
|||||||
|
|
||||||
jest.mock('react-redux', () => ({
|
jest.mock('react-redux', () => ({
|
||||||
...jest.requireActual('react-redux'),
|
...jest.requireActual('react-redux'),
|
||||||
useSelector: jest.fn(),
|
useSelector: jest.fn(() => true),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('components/channel_notifications_modal/ExpandView', () => {
|
describe('components/channel_notifications_modal/ExpandView', () => {
|
||||||
@@ -25,31 +25,72 @@ describe('components/channel_notifications_modal/ExpandView', () => {
|
|||||||
onCollapseSection: jest.fn(),
|
onCollapseSection: jest.fn(),
|
||||||
onSubmit: jest.fn(),
|
onSubmit: jest.fn(),
|
||||||
onReset: jest.fn(),
|
onReset: jest.fn(),
|
||||||
|
isGM: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
describe('normal channels', () => {
|
||||||
const wrapper = shallow(
|
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||||
<ExpandView {...baseProps}/>,
|
const wrapper = shallow(
|
||||||
);
|
<ExpandView {...baseProps}/>,
|
||||||
|
);
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should match snapshot, PUSH on expanded view', () => {
|
||||||
|
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ExpandView {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
||||||
|
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ExpandView {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||||
|
const props = {...baseProps, memberNotifyLevel: NotificationLevels.MENTION};
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ExpandView {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||||
|
const props = {...baseProps, section: NotificationSections.PUSH, memberNotifyLevel: NotificationLevels.MENTION};
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ExpandView {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, PUSH on expanded view', () => {
|
describe('gms', () => {
|
||||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||||
const wrapper = shallow(
|
const props = {...baseProps, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||||
<ExpandView {...props}/>,
|
const wrapper = shallow(
|
||||||
);
|
<ExpandView {...props}/>,
|
||||||
|
);
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
const props = {...baseProps, section: NotificationSections.PUSH, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ExpandView {...props}/>,
|
<ExpandView {...props}/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ type Props = {
|
|||||||
memberDesktopNotificationSound?: string;
|
memberDesktopNotificationSound?: string;
|
||||||
section: string;
|
section: string;
|
||||||
serverError?: string;
|
serverError?: string;
|
||||||
|
isGM: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sounds = Array.from(notificationSounds.keys());
|
const sounds = Array.from(notificationSounds.keys());
|
||||||
@@ -74,6 +75,7 @@ export default function ExpandView({
|
|||||||
onCollapseSection,
|
onCollapseSection,
|
||||||
ignoreChannelMentions,
|
ignoreChannelMentions,
|
||||||
channelAutoFollowThreads,
|
channelAutoFollowThreads,
|
||||||
|
isGM,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const isCRTEnabled = useSelector(isCollapsedThreadsEnabled);
|
const isCRTEnabled = useSelector(isCollapsedThreadsEnabled);
|
||||||
|
|
||||||
@@ -275,6 +277,7 @@ export default function ExpandView({
|
|||||||
{isCRTEnabled &&
|
{isCRTEnabled &&
|
||||||
section === NotificationSections.DESKTOP &&
|
section === NotificationSections.DESKTOP &&
|
||||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||||
|
!isGM &&
|
||||||
<>
|
<>
|
||||||
<hr/>
|
<hr/>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -378,6 +381,7 @@ export default function ExpandView({
|
|||||||
{isCRTEnabled &&
|
{isCRTEnabled &&
|
||||||
section === NotificationSections.PUSH &&
|
section === NotificationSections.PUSH &&
|
||||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||||
|
!isGM &&
|
||||||
<>
|
<>
|
||||||
<hr/>
|
<hr/>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ export default class NotificationSection extends React.PureComponent {
|
|||||||
* Error string from the server
|
* Error string from the server
|
||||||
*/
|
*/
|
||||||
serverError: PropTypes.string,
|
serverError: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the preferences are those of a GM
|
||||||
|
*/
|
||||||
|
isGM: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOnChange = (e) => {
|
handleOnChange = (e) => {
|
||||||
@@ -134,6 +139,7 @@ export default class NotificationSection extends React.PureComponent {
|
|||||||
onReset,
|
onReset,
|
||||||
section,
|
section,
|
||||||
serverError,
|
serverError,
|
||||||
|
isGM,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (expand) {
|
if (expand) {
|
||||||
@@ -157,6 +163,7 @@ export default class NotificationSection extends React.PureComponent {
|
|||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
serverError={serverError}
|
serverError={serverError}
|
||||||
onCollapseSection={this.handleCollapseSection}
|
onCollapseSection={this.handleCollapseSection}
|
||||||
|
isGM={isGM}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ describe('components/channel_notifications_modal/NotificationSection', () => {
|
|||||||
onSubmit: () => {}, //eslint-disable-line no-empty-function
|
onSubmit: () => {}, //eslint-disable-line no-empty-function
|
||||||
onUpdateSection: () => {}, //eslint-disable-line no-empty-function
|
onUpdateSection: () => {}, //eslint-disable-line no-empty-function
|
||||||
serverError: '',
|
serverError: '',
|
||||||
|
isGM: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should match snapshot, DESKTOP on collapsed view', () => {
|
test('should match snapshot, DESKTOP on collapsed view', () => {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ describe('components/post_view/ChannelIntroMessages', () => {
|
|||||||
const users = [
|
const users = [
|
||||||
{id: 'user1', roles: 'system_user'},
|
{id: 'user1', roles: 'system_user'},
|
||||||
{id: 'guest1', roles: 'system_guest'},
|
{id: 'guest1', roles: 'system_guest'},
|
||||||
|
{id: 'test-user-id', roles: 'system_user'},
|
||||||
] as UserProfile[];
|
] as UserProfile[];
|
||||||
|
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
@@ -153,6 +154,9 @@ describe('components/post_view/ChannelIntroMessages', () => {
|
|||||||
|
|
||||||
expect(editIcon).toBeInTheDocument();
|
expect(editIcon).toBeInTheDocument();
|
||||||
expect(editIcon).toHaveClass('icon-pencil-outline');
|
expect(editIcon).toHaveClass('icon-pencil-outline');
|
||||||
|
|
||||||
|
const notificationPreferencesButton = screen.getByText('Notification Preferences');
|
||||||
|
expect(notificationPreferencesButton).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import {BellRingOutlineIcon} from '@mattermost/compass-icons/components';
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
import type {UserProfile as UserProfileRedux} from '@mattermost/types/users';
|
import type {UserProfile as UserProfileType} from '@mattermost/types/users';
|
||||||
|
|
||||||
import {Permissions} from 'mattermost-redux/constants';
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
import AddGroupsToTeamModal from 'components/add_groups_to_team_modal';
|
import AddGroupsToTeamModal from 'components/add_groups_to_team_modal';
|
||||||
|
import ChannelNotificationsModal from 'components/channel_notifications_modal';
|
||||||
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||||
import LocalizedIcon from 'components/localized_icon';
|
import LocalizedIcon from 'components/localized_icon';
|
||||||
@@ -32,12 +34,12 @@ type Props = {
|
|||||||
channel: Channel;
|
channel: Channel;
|
||||||
fullWidth: boolean;
|
fullWidth: boolean;
|
||||||
locale: string;
|
locale: string;
|
||||||
channelProfiles: UserProfileRedux[];
|
channelProfiles: UserProfileType[];
|
||||||
enableUserCreation?: boolean;
|
enableUserCreation?: boolean;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
teamIsGroupConstrained?: boolean;
|
teamIsGroupConstrained?: boolean;
|
||||||
creatorName: string;
|
creatorName: string;
|
||||||
teammate?: UserProfileRedux;
|
teammate?: UserProfileType;
|
||||||
teammateName?: string;
|
teammateName?: string;
|
||||||
stats: any;
|
stats: any;
|
||||||
usersLimit: number;
|
usersLimit: number;
|
||||||
@@ -89,10 +91,11 @@ export default class ChannelIntroMessage extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: UserProfileRedux[], currentUserId: string) {
|
function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: UserProfileType[], currentUserId: string) {
|
||||||
const channelIntroId = 'channelIntro';
|
const channelIntroId = 'channelIntro';
|
||||||
|
|
||||||
if (profiles.length > 0) {
|
if (profiles.length > 0) {
|
||||||
|
const currentUserProfile = profiles.find((v) => v.id === currentUserId);
|
||||||
const pictures = profiles.
|
const pictures = profiles.
|
||||||
filter((profile) => profile.id !== currentUserId).
|
filter((profile) => profile.id !== currentUserId).
|
||||||
map((profile) => (
|
map((profile) => (
|
||||||
@@ -114,16 +117,21 @@ function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles:
|
|||||||
{pictures}
|
{pictures}
|
||||||
</div>
|
</div>
|
||||||
<p className='channel-intro-text'>
|
<p className='channel-intro-text'>
|
||||||
<FormattedMarkdownMessage
|
<FormattedMessage
|
||||||
id='intro_messages.GM'
|
id='intro_messages.GM'
|
||||||
defaultMessage='This is the start of your group message history with {names}.\nMessages and files shared here are not shown to people outside this area.'
|
defaultMessage={'This is the start of your group message history with {names}.{br}You\'ll be notified <b>for all activity</b> in this group message.'}
|
||||||
values={{
|
values={{
|
||||||
|
b: (chunks) => <b>{chunks}</b>,
|
||||||
names: channel.display_name,
|
names: channel.display_name,
|
||||||
|
br: <br/>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<PluggableIntroButtons channel={channel}/>
|
<div style={{display: 'flex'}}>
|
||||||
{createSetHeaderButton(channel)}
|
{createNotificationPreferencesButton(channel, currentUserProfile)}
|
||||||
|
<PluggableIntroButtons channel={channel}/>
|
||||||
|
{createSetHeaderButton(channel)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -143,7 +151,7 @@ function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?: UserProfileRedux, teammateName?: string) {
|
function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?: UserProfileType, teammateName?: string) {
|
||||||
const channelIntroId = 'channelIntro';
|
const channelIntroId = 'channelIntro';
|
||||||
if (teammate) {
|
if (teammate) {
|
||||||
const src = teammate ? Utils.imageURLForUser(teammate.id, teammate.last_picture_update) : '';
|
const src = teammate ? Utils.imageURLForUser(teammate.id, teammate.last_picture_update) : '';
|
||||||
@@ -185,8 +193,10 @@ function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
{pluggableButton}
|
<div style={{display: 'flex'}}>
|
||||||
{setHeaderButton}
|
{pluggableButton}
|
||||||
|
{setHeaderButton}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -555,3 +565,26 @@ function createSetHeaderButton(channel: Channel) {
|
|||||||
</ToggleModalButton>
|
</ToggleModalButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNotificationPreferencesButton(channel: Channel, currentUser?: UserProfileType) {
|
||||||
|
const isGM = channel.type === 'G';
|
||||||
|
if (!isGM || !currentUser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleModalButton
|
||||||
|
modalId={ModalIdentifiers.CHANNEL_NOTIFICATIONS}
|
||||||
|
ariaLabel={Utils.localizeMessage('intro_messages.notificationPreferences', 'Notification Preferences')}
|
||||||
|
className={'intro-links color--link channelIntroButton'}
|
||||||
|
dialogType={ChannelNotificationsModal}
|
||||||
|
dialogProps={{channel, currentUser}}
|
||||||
|
>
|
||||||
|
<BellRingOutlineIcon size={16}/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='intro_messages.notificationPreferences'
|
||||||
|
defaultMessage='Notification Preferences'
|
||||||
|
/>
|
||||||
|
</ToggleModalButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,64 +5,60 @@ exports[`components/SystemNotice should match snapshot for admin, admin notice 1
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title
|
some title
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__body"
|
|
||||||
>
|
|
||||||
some body
|
some body
|
||||||
</div>
|
<div
|
||||||
<div
|
className="system-notice__info"
|
||||||
className="system-notice__info"
|
>
|
||||||
>
|
<LocalizedIcon
|
||||||
<LocalizedIcon
|
className="fa fa-eye"
|
||||||
className="fa fa-eye"
|
title={
|
||||||
title={
|
Object {
|
||||||
Object {
|
"defaultMessage": "Only visible to System Admins Icon",
|
||||||
"defaultMessage": "Only visible to System Admins Icon",
|
"id": "system_notice.adminVisible.icon",
|
||||||
"id": "system_notice.adminVisible.icon",
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Only visible to System Admins"
|
|
||||||
id="system_notice.adminVisible"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<MemoizedFormattedMessage
|
|
||||||
defaultMessage="Remind Me Later"
|
|
||||||
id="system_notice.remind_me"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_dontshow"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<MemoizedFormattedMessage
|
<MemoizedFormattedMessage
|
||||||
defaultMessage="Don't Show Again"
|
defaultMessage="Only visible to System Admins"
|
||||||
id="system_notice.dont_show"
|
id="system_notice.adminVisible"
|
||||||
/>
|
/>
|
||||||
</button>
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
id="systemnotice_remindme"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Remind Me Later"
|
||||||
|
id="system_notice.remind_me"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
id="systemnotice_dontshow"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -72,47 +68,43 @@ exports[`components/SystemNotice should match snapshot for admin, regular notice
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title
|
some title
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__body"
|
|
||||||
>
|
|
||||||
some body
|
some body
|
||||||
</div>
|
<div
|
||||||
<div
|
className="system-notice__footer"
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Remind Me Later"
|
className="btn btn-primary"
|
||||||
id="system_notice.remind_me"
|
id="systemnotice_remindme"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
<button
|
<MemoizedFormattedMessage
|
||||||
className="btn btn-transparent"
|
defaultMessage="Remind Me Later"
|
||||||
id="systemnotice_dontshow"
|
id="system_notice.remind_me"
|
||||||
onClick={[Function]}
|
/>
|
||||||
>
|
</button>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Don't Show Again"
|
className="btn"
|
||||||
id="system_notice.dont_show"
|
id="systemnotice_dontshow"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -122,47 +114,43 @@ exports[`components/SystemNotice should match snapshot for regular user, admin a
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title2
|
some title2
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__body"
|
|
||||||
>
|
|
||||||
some body2
|
some body2
|
||||||
</div>
|
<div
|
||||||
<div
|
className="system-notice__footer"
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Remind Me Later"
|
className="btn btn-primary"
|
||||||
id="system_notice.remind_me"
|
id="systemnotice_remindme"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
<button
|
<MemoizedFormattedMessage
|
||||||
className="btn btn-transparent"
|
defaultMessage="Remind Me Later"
|
||||||
id="systemnotice_dontshow"
|
id="system_notice.remind_me"
|
||||||
onClick={[Function]}
|
/>
|
||||||
>
|
</button>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Don't Show Again"
|
className="btn"
|
||||||
id="system_notice.dont_show"
|
id="systemnotice_dontshow"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -180,47 +168,43 @@ exports[`components/SystemNotice should match snapshot for regular user, regular
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title
|
some title
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__body"
|
|
||||||
>
|
|
||||||
some body
|
some body
|
||||||
</div>
|
<div
|
||||||
<div
|
className="system-notice__footer"
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Remind Me Later"
|
className="btn btn-primary"
|
||||||
id="system_notice.remind_me"
|
id="systemnotice_remindme"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
<button
|
<MemoizedFormattedMessage
|
||||||
className="btn btn-transparent"
|
defaultMessage="Remind Me Later"
|
||||||
id="systemnotice_dontshow"
|
id="system_notice.remind_me"
|
||||||
onClick={[Function]}
|
/>
|
||||||
>
|
</button>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Don't Show Again"
|
className="btn"
|
||||||
id="system_notice.dont_show"
|
id="systemnotice_dontshow"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -232,47 +216,43 @@ exports[`components/SystemNotice should match snapshot for show function returni
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title
|
some title
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="system-notice__body"
|
|
||||||
>
|
|
||||||
some body
|
some body
|
||||||
</div>
|
<div
|
||||||
<div
|
className="system-notice__footer"
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Remind Me Later"
|
className="btn btn-primary"
|
||||||
id="system_notice.remind_me"
|
id="systemnotice_remindme"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
<button
|
<MemoizedFormattedMessage
|
||||||
className="btn btn-transparent"
|
defaultMessage="Remind Me Later"
|
||||||
id="systemnotice_dontshow"
|
id="system_notice.remind_me"
|
||||||
onClick={[Function]}
|
/>
|
||||||
>
|
</button>
|
||||||
<MemoizedFormattedMessage
|
<button
|
||||||
defaultMessage="Don't Show Again"
|
className="btn"
|
||||||
id="system_notice.dont_show"
|
id="systemnotice_dontshow"
|
||||||
/>
|
onClick={[Function]}
|
||||||
</button>
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -282,37 +262,81 @@ exports[`components/SystemNotice should match snapshot for with allowForget equa
|
|||||||
className="system-notice bg--white shadow--2"
|
className="system-notice bg--white shadow--2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="system-notice__header"
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<MattermostLogo />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="system-notice__logo"
|
|
||||||
>
|
|
||||||
<MattermostLogo />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="system-notice__title"
|
className="system-notice__title"
|
||||||
>
|
>
|
||||||
some title
|
some title
|
||||||
</div>
|
</div>
|
||||||
|
some body
|
||||||
|
<div
|
||||||
|
className="system-notice__footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
id="systemnotice_remindme"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Remind Me Later"
|
||||||
|
id="system_notice.remind_me"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/SystemNotice should match snapshot when a custom icon is passed 1`] = `
|
||||||
|
<div
|
||||||
|
className="system-notice bg--white shadow--2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="system-notice__logo"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
icon
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="system-notice__body"
|
className="system-notice__body"
|
||||||
>
|
>
|
||||||
some body
|
<div
|
||||||
</div>
|
className="system-notice__title"
|
||||||
<div
|
|
||||||
className="system-notice__footer"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-transparent"
|
|
||||||
id="systemnotice_remindme"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<MemoizedFormattedMessage
|
some title
|
||||||
defaultMessage="Remind Me Later"
|
</div>
|
||||||
id="system_notice.remind_me"
|
some body
|
||||||
/>
|
<div
|
||||||
</button>
|
className="system-notice__footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
id="systemnotice_remindme"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Remind Me Later"
|
||||||
|
id="system_notice.remind_me"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
id="systemnotice_dontshow"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<MemoizedFormattedMessage
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
id="system_notice.dont_show"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {getStandardAnalytics} from 'mattermost-redux/actions/admin';
|
|||||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||||
import {Permissions} from 'mattermost-redux/constants';
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||||
|
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
|
import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||||
@@ -55,6 +56,7 @@ function makeMapStateToProps() {
|
|||||||
license,
|
license,
|
||||||
serverVersion,
|
serverVersion,
|
||||||
analytics,
|
analytics,
|
||||||
|
currentChannel: getCurrentChannel(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {FormattedMessage} from 'react-intl';
|
|||||||
import ExternalLink from 'components/external_link';
|
import ExternalLink from 'components/external_link';
|
||||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||||
import type {Notice} from 'components/system_notice/types';
|
import type {Notice} from 'components/system_notice/types';
|
||||||
|
import InfoIcon from 'components/widgets/icons/info_icon';
|
||||||
|
|
||||||
import mattermostIcon from 'images/icon50x50.png';
|
|
||||||
import {DocLinks} from 'utils/constants';
|
import {DocLinks} from 'utils/constants';
|
||||||
import * as ServerVersion from 'utils/server_version';
|
import * as ServerVersion from 'utils/server_version';
|
||||||
import * as UserAgent from 'utils/user_agent';
|
import * as UserAgent from 'utils/user_agent';
|
||||||
@@ -29,12 +29,11 @@ const notices: Notice[] = [
|
|||||||
name: 'apiv3_deprecation',
|
name: 'apiv3_deprecation',
|
||||||
adminOnly: true,
|
adminOnly: true,
|
||||||
title: (
|
title: (
|
||||||
<FormattedMarkdownMessage
|
<FormattedMessage
|
||||||
id='system_notice.title'
|
id='system_notice.title'
|
||||||
defaultMessage='**Notice**\nfrom Mattermost'
|
defaultMessage='Notice from Mattermost'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
icon: mattermostIcon,
|
|
||||||
body: (
|
body: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='system_notice.body.api3'
|
id='system_notice.body.api3'
|
||||||
@@ -63,12 +62,11 @@ const notices: Notice[] = [
|
|||||||
name: 'advanced_permissions',
|
name: 'advanced_permissions',
|
||||||
adminOnly: true,
|
adminOnly: true,
|
||||||
title: (
|
title: (
|
||||||
<FormattedMarkdownMessage
|
<FormattedMessage
|
||||||
id='system_notice.title'
|
id='system_notice.title'
|
||||||
defaultMessage='**Notice**\nfrom Mattermost'
|
defaultMessage='Notice from Mattermost'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
icon: mattermostIcon,
|
|
||||||
body: (
|
body: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='system_notice.body.permissions'
|
id='system_notice.body.permissions'
|
||||||
@@ -103,12 +101,11 @@ const notices: Notice[] = [
|
|||||||
name: 'ee_upgrade_advice',
|
name: 'ee_upgrade_advice',
|
||||||
adminOnly: true,
|
adminOnly: true,
|
||||||
title: (
|
title: (
|
||||||
<FormattedMarkdownMessage
|
<FormattedMessage
|
||||||
id='system_notice.title'
|
id='system_notice.title'
|
||||||
defaultMessage='**Notice**\nfrom Mattermost'
|
defaultMessage='Notice from Mattermost'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
icon: mattermostIcon,
|
|
||||||
body: (
|
body: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='system_notice.body.ee_upgrade_advice'
|
id='system_notice.body.ee_upgrade_advice'
|
||||||
@@ -150,10 +147,9 @@ const notices: Notice[] = [
|
|||||||
title: (
|
title: (
|
||||||
<FormattedMarkdownMessage
|
<FormattedMarkdownMessage
|
||||||
id='system_notice.title'
|
id='system_notice.title'
|
||||||
defaultMessage='**Notice**\nfrom Mattermost'
|
defaultMessage='Notice from Mattermost'
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
icon: mattermostIcon,
|
|
||||||
allowForget: false,
|
allowForget: false,
|
||||||
body: (
|
body: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -185,6 +181,30 @@ const notices: Notice[] = [
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
// This notice is marked as viewed by default for new users on the server.
|
||||||
|
// Any change on this notice should be handled also in the server side.
|
||||||
|
name: 'GMasDM',
|
||||||
|
allowForget: true,
|
||||||
|
title: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_notice.title.gm_as_dm'
|
||||||
|
defaultMessage='Updates to Group Messages'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
icon: (<InfoIcon/>),
|
||||||
|
body: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_noticy.body.gm_as_dm'
|
||||||
|
defaultMessage='You wil now be notified for all activity in your group messages along with a notification badge for every new message.{br}{br}You can configure this in notification preferences for each group message.'
|
||||||
|
values={{br: (<br/>)}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
show: (serverVersion, config, license, analytics, currentChannel) => {
|
||||||
|
return currentChannel?.type === 'G';
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default notices;
|
export default notices;
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ import React from 'react';
|
|||||||
|
|
||||||
import SystemNotice from 'components/system_notice/system_notice';
|
import SystemNotice from 'components/system_notice/system_notice';
|
||||||
|
|
||||||
import mattermostIcon from 'images/icon50x50.png';
|
|
||||||
|
|
||||||
describe('components/SystemNotice', () => {
|
describe('components/SystemNotice', () => {
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
currentUserId: 'someid',
|
currentUserId: 'someid',
|
||||||
preferences: {},
|
preferences: {},
|
||||||
dismissedNotices: {},
|
dismissedNotices: {},
|
||||||
isSystemAdmin: false,
|
isSystemAdmin: false,
|
||||||
notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}],
|
notices: [{name: 'notice1', adminOnly: false, title: 'some title', body: 'some body', allowForget: true, show: () => true}],
|
||||||
serverVersion: '5.1',
|
serverVersion: '5.1',
|
||||||
license: {IsLicensed: 'true'},
|
license: {IsLicensed: 'true'},
|
||||||
config: {},
|
config: {},
|
||||||
@@ -38,13 +36,17 @@ describe('components/SystemNotice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for regular user, admin notice', () => {
|
test('should match snapshot for regular user, admin notice', () => {
|
||||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
const props = {...baseProps, notices: [{...baseProps.notices[0], adminOnly: true}]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for regular user, admin and regular notice', () => {
|
test('should match snapshot for regular user, admin and regular notice', () => {
|
||||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true}, {name: 'notice2', adminOnly: false, title: 'some title2', icon: mattermostIcon, body: 'some body2', allowForget: true, show: () => true}]};
|
const props = {...baseProps,
|
||||||
|
notices: [
|
||||||
|
{...baseProps.notices[0], adminOnly: true},
|
||||||
|
{...baseProps.notices[0], name: 'notice2', title: 'some title2', body: 'some body2'},
|
||||||
|
]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -56,7 +58,7 @@ describe('components/SystemNotice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for admin, admin notice', () => {
|
test('should match snapshot for admin, admin notice', () => {
|
||||||
const props = {...baseProps, isSystemAdmin: true, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
const props = {...baseProps, isSystemAdmin: true, notices: [{...baseProps.notices[0], adminOnly: true}]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -74,19 +76,25 @@ describe('components/SystemNotice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for show function returning false', () => {
|
test('should match snapshot for show function returning false', () => {
|
||||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => false}]};
|
const props = {...baseProps, notices: [{...baseProps.notices[0], show: () => false}]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for show function returning true', () => {
|
test('should match snapshot for show function returning true', () => {
|
||||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
const props = {...baseProps, notices: [{...baseProps.notices[0], show: () => true}]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should match snapshot for with allowForget equal false', () => {
|
test('should match snapshot for with allowForget equal false', () => {
|
||||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: false, show: () => true}]};
|
const props = {...baseProps, notices: [{...baseProps.notices[0], allowForget: false}]};
|
||||||
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should match snapshot when a custom icon is passed', () => {
|
||||||
|
const props = {...baseProps, notices: [{...baseProps.notices[0], icon: <span>{'icon'}</span>}]};
|
||||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import React from 'react';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {AnalyticsRow} from '@mattermost/types/admin';
|
import type {AnalyticsRow} from '@mattermost/types/admin';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
import type {ClientConfig, ClientLicense} from '@mattermost/types/config';
|
import type {ClientConfig, ClientLicense} from '@mattermost/types/config';
|
||||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
import type {PreferenceType} from '@mattermost/types/preferences';
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ type Props = {
|
|||||||
config: Partial<ClientConfig>;
|
config: Partial<ClientConfig>;
|
||||||
license: ClientLicense;
|
license: ClientLicense;
|
||||||
analytics?: Record<string, number | AnalyticsRow[]>;
|
analytics?: Record<string, number | AnalyticsRow[]>;
|
||||||
|
currentChannel?: Channel;
|
||||||
actions: {
|
actions: {
|
||||||
savePreferences(userId: string, preferences: PreferenceType[]): void;
|
savePreferences(userId: string, preferences: PreferenceType[]): void;
|
||||||
dismissNotice(type: string): void;
|
dismissNotice(type: string): void;
|
||||||
@@ -60,7 +62,13 @@ export default class SystemNotice extends React.PureComponent<Props> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notice.show?.(this.props.serverVersion, this.props.config, this.props.license, this.props.analytics)) {
|
if (!notice.show?.(
|
||||||
|
this.props.serverVersion,
|
||||||
|
this.props.config,
|
||||||
|
this.props.license,
|
||||||
|
this.props.analytics,
|
||||||
|
this.props.currentChannel,
|
||||||
|
)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,44 +126,44 @@ export default class SystemNotice extends React.PureComponent<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const icon = notice.icon || <MattermostLogo/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='system-notice bg--white shadow--2'
|
className='system-notice bg--white shadow--2'
|
||||||
>
|
>
|
||||||
<div className='system-notice__header'>
|
<div className='system-notice__logo'>
|
||||||
<div className='system-notice__logo'>
|
{icon}
|
||||||
<MattermostLogo/>
|
</div>
|
||||||
</div>
|
<div className='system-notice__body'>
|
||||||
<div className='system-notice__title'>
|
<div className='system-notice__title'>
|
||||||
{notice.title}
|
{notice.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className='system-notice__body'>
|
|
||||||
{notice.body}
|
{notice.body}
|
||||||
</div>
|
{visibleMessage}
|
||||||
{visibleMessage}
|
<div className='system-notice__footer'>
|
||||||
<div className='system-notice__footer'>
|
|
||||||
<button
|
|
||||||
id='systemnotice_remindme'
|
|
||||||
className='btn btn-transparent'
|
|
||||||
onClick={this.hideAndRemind}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='system_notice.remind_me'
|
|
||||||
defaultMessage='Remind Me Later'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{notice.allowForget &&
|
|
||||||
<button
|
<button
|
||||||
id='systemnotice_dontshow'
|
id='systemnotice_remindme'
|
||||||
className='btn btn-transparent'
|
className='btn btn-primary'
|
||||||
onClick={this.hideAndForget}
|
onClick={this.hideAndRemind}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='system_notice.dont_show'
|
id='system_notice.remind_me'
|
||||||
defaultMessage="Don't Show Again"
|
defaultMessage='Remind Me Later'
|
||||||
/>
|
/>
|
||||||
</button>}
|
</button>
|
||||||
|
{notice.allowForget &&
|
||||||
|
<button
|
||||||
|
id='systemnotice_dontshow'
|
||||||
|
className='btn'
|
||||||
|
onClick={this.hideAndForget}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='system_notice.dont_show'
|
||||||
|
defaultMessage="Don't Show Again"
|
||||||
|
/>
|
||||||
|
</button>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,17 +4,20 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
|
||||||
import type {AnalyticsRow} from '@mattermost/types/admin';
|
import type {AnalyticsRow} from '@mattermost/types/admin';
|
||||||
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
export type Notice = {
|
export type Notice = {
|
||||||
name: string;
|
name: string;
|
||||||
adminOnly?: boolean;
|
adminOnly?: boolean;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
icon: string;
|
icon?: React.ReactNode;
|
||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
allowForget: boolean;
|
allowForget: boolean;
|
||||||
show?(
|
show?(
|
||||||
serverVersion: string,
|
serverVersion: string,
|
||||||
config: any,
|
config: any,
|
||||||
license: any,
|
license: any,
|
||||||
analytics?: Record<string, number | AnalyticsRow[]>): boolean;
|
analytics?: Record<string, number | AnalyticsRow[]>,
|
||||||
|
currentChannel?: Channel,
|
||||||
|
): boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -205,7 +205,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -330,7 +330,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -485,7 +485,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -693,7 +693,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@@ -942,7 +942,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
|||||||
type="radio"
|
type="radio"
|
||||||
/>
|
/>
|
||||||
<Memo(MemoizedFormattedMessage)
|
<Memo(MemoizedFormattedMessage)
|
||||||
defaultMessage="Only for mentions and direct messages"
|
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||||
id="user.settings.notifications.onlyMentions"
|
id="user.settings.notifications.onlyMentions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
|
|||||||
/>
|
/>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='user.settings.notifications.onlyMentions'
|
id='user.settings.notifications.onlyMentions'
|
||||||
defaultMessage='Only for mentions and direct messages'
|
defaultMessage='Only for mentions, direct messages, and group messages'
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -2994,7 +2994,7 @@
|
|||||||
"channel_modal.type.private.title": "Private Channel",
|
"channel_modal.type.private.title": "Private Channel",
|
||||||
"channel_modal.type.public.description": "Anyone can join",
|
"channel_modal.type.public.description": "Anyone can join",
|
||||||
"channel_modal.type.public.title": "Public Channel",
|
"channel_modal.type.public.title": "Public Channel",
|
||||||
"channel_notifications.allActivity": "For all activity",
|
"channel_notifications.allActivity": "For all activity {isDefault}",
|
||||||
"channel_notifications.channelAutoFollowThreads": "Auto-follow all new threads in this channel",
|
"channel_notifications.channelAutoFollowThreads": "Auto-follow all new threads in this channel",
|
||||||
"channel_notifications.channelAutoFollowThreads.help": "When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.",
|
"channel_notifications.channelAutoFollowThreads.help": "When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.",
|
||||||
"channel_notifications.channelAutoFollowThreads.off.title": "Off",
|
"channel_notifications.channelAutoFollowThreads.off.title": "Off",
|
||||||
@@ -3760,7 +3760,7 @@
|
|||||||
"intro_messages.creatorPrivate": "This is the start of the {name} private channel, created by {creator} on {date}.",
|
"intro_messages.creatorPrivate": "This is the start of the {name} private channel, created by {creator} on {date}.",
|
||||||
"intro_messages.default": "**Welcome to {display_name}!**\n \nPost messages here that you want everyone to see. Everyone automatically becomes a permanent member of this channel when they join the team.",
|
"intro_messages.default": "**Welcome to {display_name}!**\n \nPost messages here that you want everyone to see. Everyone automatically becomes a permanent member of this channel when they join the team.",
|
||||||
"intro_messages.DM": "This is the start of your direct message history with {teammate}.\nDirect messages and files shared here are not shown to people outside this area.",
|
"intro_messages.DM": "This is the start of your direct message history with {teammate}.\nDirect messages and files shared here are not shown to people outside this area.",
|
||||||
"intro_messages.GM": "This is the start of your group message history with {names}.\nMessages and files shared here are not shown to people outside this area.",
|
"intro_messages.GM": "This is the start of your group message history with {names}.{br}You'll be notified <b>for all activity</b> in this group message.",
|
||||||
"intro_messages.group_message": "This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.",
|
"intro_messages.group_message": "This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.",
|
||||||
"intro_messages.inviteGropusToChannel.button": "Add groups to this private channel",
|
"intro_messages.inviteGropusToChannel.button": "Add groups to this private channel",
|
||||||
"intro_messages.inviteMembersToChannel.button": "Add members to this channel",
|
"intro_messages.inviteMembersToChannel.button": "Add members to this channel",
|
||||||
@@ -3770,6 +3770,7 @@
|
|||||||
"intro_messages.inviteOthersToWorkspace.title": "Let’s add some people to the workspace!",
|
"intro_messages.inviteOthersToWorkspace.title": "Let’s add some people to the workspace!",
|
||||||
"intro_messages.noCreator": "This is the start of the {name} channel, created on {date}.",
|
"intro_messages.noCreator": "This is the start of the {name} channel, created on {date}.",
|
||||||
"intro_messages.noCreatorPrivate": "This is the start of the {name} private channel, created on {date}.",
|
"intro_messages.noCreatorPrivate": "This is the start of the {name} private channel, created on {date}.",
|
||||||
|
"intro_messages.notificationPreferences": "Notification Preferences",
|
||||||
"intro_messages.offTopic": "This is the start of {display_name}, a channel for non-work-related conversations.",
|
"intro_messages.offTopic": "This is the start of {display_name}, a channel for non-work-related conversations.",
|
||||||
"intro_messages.onlyInvited": " Only invited members can see this private channel.",
|
"intro_messages.onlyInvited": " Only invited members can see this private channel.",
|
||||||
"intro_messages.purpose": " This channel's purpose is: {purpose}",
|
"intro_messages.purpose": " This channel's purpose is: {purpose}",
|
||||||
@@ -4999,7 +5000,9 @@
|
|||||||
"system_notice.body.permissions": "Some policy and permission System Console settings have moved with the release of <link>advanced permissions</link> into Mattermost Free and Professional.",
|
"system_notice.body.permissions": "Some policy and permission System Console settings have moved with the release of <link>advanced permissions</link> into Mattermost Free and Professional.",
|
||||||
"system_notice.dont_show": "Don't Show Again",
|
"system_notice.dont_show": "Don't Show Again",
|
||||||
"system_notice.remind_me": "Remind me Later",
|
"system_notice.remind_me": "Remind me Later",
|
||||||
"system_notice.title": "**Notice**\nfrom Mattermost",
|
"system_notice.title": "Notice from Mattermost",
|
||||||
|
"system_notice.title.gm_as_dm": "Updates to Group Messages",
|
||||||
|
"system_noticy.body.gm_as_dm": "You wil now be notified for all activity in your group messages along with a notification badge for every new message.{br}{br}You can configure this in notification preferences for each group message.",
|
||||||
"system_users_list.count": "{count, number} {count, plural, one {user} other {users}}",
|
"system_users_list.count": "{count, number} {count, plural, one {user} other {users}}",
|
||||||
"system_users_list.countPage": "{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total, number} total",
|
"system_users_list.countPage": "{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total, number} total",
|
||||||
"system_users_list.countSearch": "{count, number} {count, plural, one {user} other {users}} of {total, number} total",
|
"system_users_list.countSearch": "{count, number} {count, plural, one {user} other {users}} of {total, number} total",
|
||||||
@@ -5448,7 +5451,7 @@
|
|||||||
"user.settings.notifications.never": "Never",
|
"user.settings.notifications.never": "Never",
|
||||||
"user.settings.notifications.off": "Off",
|
"user.settings.notifications.off": "Off",
|
||||||
"user.settings.notifications.on": "On",
|
"user.settings.notifications.on": "On",
|
||||||
"user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
|
"user.settings.notifications.onlyMentions": "Only for mentions, direct messages, and group messages",
|
||||||
"user.settings.notifications.push": "Mobile Push Notifications",
|
"user.settings.notifications.push": "Mobile Push Notifications",
|
||||||
"user.settings.notifications.push_notification.status": "Trigger push notifications when",
|
"user.settings.notifications.push_notification.status": "Trigger push notifications when",
|
||||||
"user.settings.notifications.push_threads": "When enabled, any reply to a thread you're following will send a mobile push notification.",
|
"user.settings.notifications.push_threads": "When enabled, any reply to a thread you're following will send a mobile push notification.",
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
width: 280px;
|
display: flex;
|
||||||
|
width: 386px;
|
||||||
padding: 18px 20px 0;
|
padding: 18px 20px 0;
|
||||||
border: 1px solid alpha-color($black, 0.15);
|
border: 1px solid alpha-color($black, 0.15);
|
||||||
background-color: var(--center-channel-bg);
|
background-color: var(--center-channel-bg);
|
||||||
@@ -13,33 +14,27 @@
|
|||||||
box-shadow: 0 20px 30px alpha-color($black, 0.07), 0 14px 20px alpha-color($black, 0.07);
|
box-shadow: 0 20px 30px alpha-color($black, 0.07), 0 14px 20px alpha-color($black, 0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-notice__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-notice__logo {
|
.system-notice__logo {
|
||||||
height: 36px;
|
height: 20px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 36px;
|
width: 20px;
|
||||||
height: 36px;
|
height: 20px;
|
||||||
fill: rgb(22, 109, 224);
|
fill: var(--button-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-notice__title {
|
.system-notice__title {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 10 1 auto;
|
padding-bottom: 10px;
|
||||||
padding: 3px 0 0 8px;
|
font-weight: bold;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
opacity: 0.7;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-notice__info {
|
.system-notice__info {
|
||||||
margin-bottom: 12px;
|
margin-top: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
@@ -49,33 +44,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.system-notice__body {
|
.system-notice__body {
|
||||||
padding: 18px 0 16px;
|
padding: 0 0 16px 16px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.system-notice__footer {
|
.system-notice__footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1px solid alpha-color($black, 0.2);
|
margin-top: 16px;
|
||||||
margin: 0 -20px;
|
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
font-weight: bold;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgb(22, 109, 224);
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-radius: 0 0 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-left: 1px solid alpha-color($black, 0.2);
|
margin-left: 5px;
|
||||||
border-radius: 0 0 4px 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,6 +524,7 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.fa,
|
.fa,
|
||||||
|
svg,
|
||||||
i {
|
i {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {formatWithRenderer} from './markdown';
|
import {formatWithRenderer} from './markdown';
|
||||||
import MentionableRenderer from './markdown/mentionable_renderer';
|
import PlainRenderer from './markdown/plain_renderer';
|
||||||
|
|
||||||
export const emoticonPatterns: { [key: string]: RegExp } = {
|
export const emoticonPatterns: { [key: string]: RegExp } = {
|
||||||
slightly_smiling_face: /(^|\B)(:-?\))($|\B)/g, // :)
|
slightly_smiling_face: /(^|\B)(:-?\))($|\B)/g, // :)
|
||||||
@@ -28,7 +28,7 @@ export const emoticonPatterns: { [key: string]: RegExp } = {
|
|||||||
export const EMOJI_PATTERN = /(:([a-zA-Z0-9_+-]+):)/g;
|
export const EMOJI_PATTERN = /(:([a-zA-Z0-9_+-]+):)/g;
|
||||||
|
|
||||||
export function matchEmoticons(text: string): RegExpMatchArray | null {
|
export function matchEmoticons(text: string): RegExpMatchArray | null {
|
||||||
const markdownCleanedText = formatWithRenderer(text, new MentionableRenderer());
|
const markdownCleanedText = formatWithRenderer(text, new PlainRenderer());
|
||||||
let emojis = markdownCleanedText.match(EMOJI_PATTERN);
|
let emojis = markdownCleanedText.match(EMOJI_PATTERN);
|
||||||
|
|
||||||
for (const name of Object.keys(emoticonPatterns)) {
|
for (const name of Object.keys(emoticonPatterns)) {
|
||||||
|
|||||||
@@ -1,81 +1,14 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import marked from 'marked';
|
import {EMOJI_PATTERN} from 'utils/emoticons';
|
||||||
|
|
||||||
|
import PlainRenderer from './plain_renderer';
|
||||||
|
|
||||||
/** A Markdown renderer that converts a post into plain text that we can search for mentions */
|
/** A Markdown renderer that converts a post into plain text that we can search for mentions */
|
||||||
export default class MentionableRenderer extends marked.Renderer {
|
export default class MentionableRenderer extends PlainRenderer {
|
||||||
public code() {
|
|
||||||
// Code blocks can't contain mentions
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public blockquote(text: string) {
|
|
||||||
return text + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public heading(text: string) {
|
|
||||||
return text + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public hr() {
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public list(body: string) {
|
|
||||||
return body + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public listitem(text: string) {
|
|
||||||
return text + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public paragraph(text: string) {
|
|
||||||
return text + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public table(header: string, body: string) {
|
|
||||||
return header + '\n' + body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public tablerow(content: string) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public tablecell(content: string) {
|
|
||||||
return content + '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public strong(text: string) {
|
|
||||||
return ' ' + text + ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public em(text: string) {
|
|
||||||
return ' ' + text + ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public codespan() {
|
|
||||||
// Code spans can't contain mentions
|
|
||||||
return ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public br() {
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
public del(text: string) {
|
|
||||||
return ' ' + text + ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public link(href: string, title: string, text: string) {
|
|
||||||
return ' ' + text + ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public image(href: string, title: string, text: string) {
|
|
||||||
return ' ' + text + ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
public text(text: string) {
|
public text(text: string) {
|
||||||
return text;
|
// Remove all emojis
|
||||||
|
return text.replace(EMOJI_PATTERN, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
webapp/channels/src/utils/markdown/plain_renderer.ts
Обычный файл
81
webapp/channels/src/utils/markdown/plain_renderer.ts
Обычный файл
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import marked from 'marked';
|
||||||
|
|
||||||
|
/** A Markdown renderer that converts a post into plain text */
|
||||||
|
export default class PlainRenderer extends marked.Renderer {
|
||||||
|
public code() {
|
||||||
|
// Code blocks can't contain mentions
|
||||||
|
return '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public blockquote(text: string) {
|
||||||
|
return text + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public heading(text: string) {
|
||||||
|
return text + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public hr() {
|
||||||
|
return '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public list(body: string) {
|
||||||
|
return body + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public listitem(text: string) {
|
||||||
|
return text + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public paragraph(text: string) {
|
||||||
|
return text + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public table(header: string, body: string) {
|
||||||
|
return header + '\n' + body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tablerow(content: string) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tablecell(content: string) {
|
||||||
|
return content + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public strong(text: string) {
|
||||||
|
return ' ' + text + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public em(text: string) {
|
||||||
|
return ' ' + text + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public codespan() {
|
||||||
|
// Code spans can't contain mentions
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public br() {
|
||||||
|
return '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
public del(text: string) {
|
||||||
|
return ' ' + text + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public link(href: string, title: string, text: string) {
|
||||||
|
return ' ' + text + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public image(href: string, title: string, text: string) {
|
||||||
|
return ' ' + text + ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
public text(text: string) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,7 +227,7 @@ const DEFAULT_OPTIONS: TextFormattingOptions = {
|
|||||||
* Additional CJK and Hangul compatibility characters: \u2de0-\u2dff
|
* Additional CJK and Hangul compatibility characters: \u2de0-\u2dff
|
||||||
**/
|
**/
|
||||||
// eslint-disable-next-line no-misleading-character-class
|
// eslint-disable-next-line no-misleading-character-class
|
||||||
const cjkrPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\uac00-\ud7a3\u1100-\u11ff\u3130-\u318f\u0400-\u04ff\u0500-\u052f\u2de0-\u2dff]/;
|
export const cjkrPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\uac00-\ud7a3\u1100-\u11ff\u3130-\u318f\u0400-\u04ff\u0500-\u052f\u2de0-\u2dff]/;
|
||||||
|
|
||||||
export function formatText(
|
export function formatText(
|
||||||
text: string,
|
text: string,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user