Merge pull request #2064 from hmhealey/plt882
PLT-882 Ephemeral Messages and Out-Of-Channel mentions
Этот коммит содержится в:
134
api/post.go
134
api/post.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -231,6 +232,8 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
|
|||||||
tchan := Srv.Store.Team().Get(c.Session.TeamId)
|
tchan := Srv.Store.Team().Get(c.Session.TeamId)
|
||||||
cchan := Srv.Store.Channel().Get(post.ChannelId)
|
cchan := Srv.Store.Channel().Get(post.ChannelId)
|
||||||
uchan := Srv.Store.User().Get(post.UserId)
|
uchan := Srv.Store.User().Get(post.UserId)
|
||||||
|
pchan := Srv.Store.User().GetProfiles(c.Session.TeamId)
|
||||||
|
mchan := Srv.Store.Channel().GetMembers(post.ChannelId)
|
||||||
|
|
||||||
var team *model.Team
|
var team *model.Team
|
||||||
if result := <-tchan; result.Err != nil {
|
if result := <-tchan; result.Err != nil {
|
||||||
@@ -248,7 +251,24 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
|
|||||||
channel = result.Data.(*model.Channel)
|
channel = result.Data.(*model.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNotificationsAndForget(c, post, team, channel)
|
var profiles map[string]*model.User
|
||||||
|
if result := <-pchan; result.Err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.Session.TeamId, result.Err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
profiles = result.Data.(map[string]*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
var members []model.ChannelMember
|
||||||
|
if result := <-mchan; result.Err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
members = result.Data.([]model.ChannelMember)
|
||||||
|
}
|
||||||
|
|
||||||
|
go sendNotifications(c, post, team, channel, profiles, members)
|
||||||
|
go checkForOutOfChannelMentions(c, post, channel, profiles, members)
|
||||||
|
|
||||||
var user *model.User
|
var user *model.User
|
||||||
if result := <-uchan; result.Err != nil {
|
if result := <-uchan; result.Err != nil {
|
||||||
@@ -413,25 +433,13 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
|
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel, profileMap map[string]*model.User, members []model.ChannelMember) {
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Get a list of user names (to be used as keywords) and ids for the given team
|
|
||||||
uchan := Srv.Store.User().GetProfiles(c.Session.TeamId)
|
|
||||||
echan := Srv.Store.Channel().GetMembers(post.ChannelId)
|
|
||||||
|
|
||||||
var channelName string
|
var channelName string
|
||||||
var bodyText string
|
var bodyText string
|
||||||
var subjectText string
|
var subjectText string
|
||||||
|
|
||||||
var mentionedUsers []string
|
var mentionedUsers []string
|
||||||
|
|
||||||
if result := <-uchan; result.Err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.retrive_profiles.error"), c.Session.TeamId, result.Err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
profileMap := result.Data.(map[string]*model.User)
|
|
||||||
|
|
||||||
if _, ok := profileMap[post.UserId]; !ok {
|
if _, ok := profileMap[post.UserId]; !ok {
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
||||||
return
|
return
|
||||||
@@ -461,20 +469,13 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Find out who is a member of the channel, only keep those profiles
|
// Find out who is a member of the channel, only keep those profiles
|
||||||
if eResult := <-echan; eResult.Err != nil {
|
|
||||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.members.error"), post.ChannelId, eResult.Err.Message)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
tempProfileMap := make(map[string]*model.User)
|
tempProfileMap := make(map[string]*model.User)
|
||||||
members := eResult.Data.([]model.ChannelMember)
|
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
tempProfileMap[member.UserId] = profileMap[member.UserId]
|
tempProfileMap[member.UserId] = profileMap[member.UserId]
|
||||||
}
|
}
|
||||||
|
|
||||||
profileMap = tempProfileMap
|
profileMap = tempProfileMap
|
||||||
}
|
|
||||||
|
|
||||||
// Build map for keywords
|
// Build map for keywords
|
||||||
keywordMap := make(map[string][]string)
|
keywordMap := make(map[string][]string)
|
||||||
@@ -694,7 +695,6 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
message := model.NewMessage(c.Session.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
|
message := model.NewMessage(c.Session.TeamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
|
||||||
message.Add("post", post.ToJson())
|
message.Add("post", post.ToJson())
|
||||||
@@ -717,7 +717,6 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
|
|||||||
}
|
}
|
||||||
|
|
||||||
PublishAndForget(message)
|
PublishAndForget(message)
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMentionCountAndForget(channelId, userId string) {
|
func updateMentionCountAndForget(channelId, userId string) {
|
||||||
@@ -728,6 +727,95 @@ func updateMentionCountAndForget(channelId, userId string) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkForOutOfChannelMentions(c *Context, post *model.Post, channel *model.Channel, allProfiles map[string]*model.User, members []model.ChannelMember) {
|
||||||
|
// don't check for out of channel mentions in direct channels
|
||||||
|
if channel.Type == model.CHANNEL_DIRECT {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mentioned := getOutOfChannelMentions(post, allProfiles, members)
|
||||||
|
if len(mentioned) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
usernames := make([]string, len(mentioned))
|
||||||
|
for i, user := range mentioned {
|
||||||
|
usernames[i] = user.Username
|
||||||
|
}
|
||||||
|
sort.Strings(usernames)
|
||||||
|
|
||||||
|
var message string
|
||||||
|
if len(usernames) == 1 {
|
||||||
|
message = c.T("api.post.check_for_out_of_channel_mentions.message.one", map[string]interface{}{
|
||||||
|
"Username": usernames[0],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message = c.T("api.post.check_for_out_of_channel_mentions.message.multiple", map[string]interface{}{
|
||||||
|
"Usernames": strings.Join(usernames[:len(usernames)-1], ", "),
|
||||||
|
"LastUsername": usernames[len(usernames)-1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SendEphemeralPost(
|
||||||
|
c.Session.TeamId,
|
||||||
|
post.UserId,
|
||||||
|
&model.Post{
|
||||||
|
ChannelId: post.ChannelId,
|
||||||
|
Message: message,
|
||||||
|
CreateAt: post.CreateAt + 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a list of users that were mentioned in a given post that aren't in the channel that the post was made in
|
||||||
|
func getOutOfChannelMentions(post *model.Post, allProfiles map[string]*model.User, members []model.ChannelMember) []*model.User {
|
||||||
|
// copy the profiles map since we'll be removing items from it
|
||||||
|
profiles := make(map[string]*model.User)
|
||||||
|
for id, profile := range allProfiles {
|
||||||
|
profiles[id] = profile
|
||||||
|
}
|
||||||
|
|
||||||
|
// only keep profiles which aren't in the current channel
|
||||||
|
for _, member := range members {
|
||||||
|
delete(profiles, member.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mentioned []*model.User
|
||||||
|
|
||||||
|
for _, profile := range profiles {
|
||||||
|
if pattern, err := regexp.Compile(`(\W|^)@` + regexp.QuoteMeta(profile.Username) + `(\W|$)`); err != nil {
|
||||||
|
l4g.Error(utils.T("api.post.get_out_of_channel_mentions.regex.error"), profile.Id, err)
|
||||||
|
} else if pattern.MatchString(post.Message) {
|
||||||
|
mentioned = append(mentioned, profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mentioned
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendEphemeralPost(teamId, userId string, post *model.Post) {
|
||||||
|
post.Type = model.POST_EPHEMERAL
|
||||||
|
|
||||||
|
// fill in fields which haven't been specified which have sensible defaults
|
||||||
|
if post.Id == "" {
|
||||||
|
post.Id = model.NewId()
|
||||||
|
}
|
||||||
|
if post.CreateAt == 0 {
|
||||||
|
post.CreateAt = model.GetMillis()
|
||||||
|
}
|
||||||
|
if post.Props == nil {
|
||||||
|
post.Props = model.StringInterface{}
|
||||||
|
}
|
||||||
|
if post.Filenames == nil {
|
||||||
|
post.Filenames = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
message := model.NewMessage(teamId, post.ChannelId, userId, model.ACTION_EPHEMERAL_MESSAGE)
|
||||||
|
message.Add("post", post.ToJson())
|
||||||
|
|
||||||
|
PublishAndForget(message)
|
||||||
|
}
|
||||||
|
|
||||||
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
post := model.PostFromJson(r.Body)
|
post := model.PostFromJson(r.Body)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/mattermost/platform/store"
|
"github.com/mattermost/platform/store"
|
||||||
"github.com/mattermost/platform/utils"
|
"github.com/mattermost/platform/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -857,3 +858,97 @@ func TestMakeDirectChannelVisible(t *testing.T) {
|
|||||||
t.Fatal("Failed to set direct channel to be visible for user2")
|
t.Fatal("Failed to set direct channel to be visible for user2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetOutOfChannelMentions(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team1 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
|
||||||
|
team1 = Client.Must(Client.CreateTeam(team1)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user1 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user1"}
|
||||||
|
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
|
||||||
|
|
||||||
|
user2 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user2"}
|
||||||
|
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||||
|
|
||||||
|
user3 := &model.User{TeamId: team1.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user3"}
|
||||||
|
user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user3.Id))
|
||||||
|
|
||||||
|
Client.Must(Client.LoginByEmail(team1.Name, user1.Email, "pwd"))
|
||||||
|
|
||||||
|
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team1.Id}
|
||||||
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
var allProfiles map[string]*model.User
|
||||||
|
if result := <-Srv.Store.User().GetProfiles(team1.Id); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
allProfiles = result.Data.(map[string]*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
var members []model.ChannelMember
|
||||||
|
if result := <-Srv.Store.Channel().GetMembers(channel1.Id); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
members = result.Data.([]model.ChannelMember)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a post that doesn't @mention anybody
|
||||||
|
post1 := &model.Post{ChannelId: channel1.Id, Message: "user1 user2 user3"}
|
||||||
|
if mentioned := getOutOfChannelMentions(post1, allProfiles, members); len(mentioned) != 0 {
|
||||||
|
t.Fatalf("getOutOfChannelMentions returned %v when no users were mentioned", mentioned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a post that @mentions someone in the channel
|
||||||
|
post2 := &model.Post{ChannelId: channel1.Id, Message: "@user1 is user1"}
|
||||||
|
if mentioned := getOutOfChannelMentions(post2, allProfiles, members); len(mentioned) != 0 {
|
||||||
|
t.Fatalf("getOutOfChannelMentions returned %v when only users in the channel were mentioned", mentioned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a post that @mentions someone not in the channel
|
||||||
|
post3 := &model.Post{ChannelId: channel1.Id, Message: "@user2 and @user3 aren't in the channel"}
|
||||||
|
if mentioned := getOutOfChannelMentions(post3, allProfiles, members); len(mentioned) != 2 || (mentioned[0].Id != user2.Id && mentioned[0].Id != user3.Id) || (mentioned[1].Id != user2.Id && mentioned[1].Id != user3.Id) {
|
||||||
|
t.Fatalf("getOutOfChannelMentions returned %v when two users outside the channel were mentioned", mentioned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a post that @mentions someone not in the channel as well as someone in the channel
|
||||||
|
post4 := &model.Post{ChannelId: channel1.Id, Message: "@user2 and @user1 might be in the channel"}
|
||||||
|
if mentioned := getOutOfChannelMentions(post4, allProfiles, members); len(mentioned) != 1 || mentioned[0].Id != user2.Id {
|
||||||
|
t.Fatalf("getOutOfChannelMentions returned %v when someone in the channel and someone outside the channel were mentioned", mentioned)
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Must(Client.Logout())
|
||||||
|
|
||||||
|
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Type: model.TEAM_OPEN}
|
||||||
|
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user4 := &model.User{TeamId: team2.Id, Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd", Username: "user4"}
|
||||||
|
user4 = Client.Must(Client.CreateUser(user4, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user4.Id))
|
||||||
|
|
||||||
|
Client.Must(Client.LoginByEmail(team2.Name, user4.Email, "pwd"))
|
||||||
|
|
||||||
|
channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id}
|
||||||
|
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
if result := <-Srv.Store.User().GetProfiles(team2.Id); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
allProfiles = result.Data.(map[string]*model.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Channel().GetMembers(channel2.Id); result.Err != nil {
|
||||||
|
t.Fatal(result.Err)
|
||||||
|
} else {
|
||||||
|
members = result.Data.([]model.ChannelMember)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test a post that @mentions someone on a different team
|
||||||
|
post5 := &model.Post{ChannelId: channel2.Id, Message: "@user2 and @user3 might be in the channel"}
|
||||||
|
if mentioned := getOutOfChannelMentions(post5, allProfiles, members); len(mentioned) != 0 {
|
||||||
|
t.Fatalf("getOutOfChannelMentions returned %v when two users on a different team were mentioned", mentioned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool {
|
|||||||
return false
|
return false
|
||||||
} else if msg.Action == model.ACTION_PREFERENCE_CHANGED {
|
} else if msg.Action == model.ACTION_PREFERENCE_CHANGED {
|
||||||
return false
|
return false
|
||||||
|
} else if msg.Action == model.ACTION_EPHEMERAL_MESSAGE {
|
||||||
|
// For now, ephemeral messages are sent directly to individual users
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only report events to a user who is the subject of the event, or is in the channel of the event
|
// Only report events to a user who is the subject of the event, or is in the channel of the event
|
||||||
|
|||||||
28
i18n/en.json
28
i18n/en.json
@@ -675,6 +675,14 @@
|
|||||||
"id": "api.oauth.revoke_access_token.get.app_error",
|
"id": "api.oauth.revoke_access_token.get.app_error",
|
||||||
"translation": "Error getting access token from DB before deletion"
|
"translation": "Error getting access token from DB before deletion"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.check_for_out_of_channel_mentions.message.one",
|
||||||
|
"translation": "{{.Username}} was mentioned, but they do not belong to this channel."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.check_for_out_of_channel_mentions.message.multiple",
|
||||||
|
"translation": "{{.Usernames}} and {{.LastUsername}} were mentioned, but they do not belong to this channel."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.post.create_post.bad_filename.error",
|
"id": "api.post.create_post.bad_filename.error",
|
||||||
"translation": "Bad filename discarded, filename=%v"
|
"translation": "Bad filename discarded, filename=%v"
|
||||||
@@ -703,6 +711,10 @@
|
|||||||
"id": "api.post.delete_post.permissions.app_error",
|
"id": "api.post.delete_post.permissions.app_error",
|
||||||
"translation": "You do not have the appropriate permissions"
|
"translation": "You do not have the appropriate permissions"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.get_out_of_channel_mentions.regex.error",
|
||||||
|
"translation": "Failed to compile @mention regex user_id=%v, err=%v"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.post.get_post.permissions.app_error",
|
"id": "api.post.get_post.permissions.app_error",
|
||||||
"translation": "You do not have the appropriate permissions"
|
"translation": "You do not have the appropriate permissions"
|
||||||
@@ -711,6 +723,14 @@
|
|||||||
"id": "api.post.handle_post_events_and_forget.channel.error",
|
"id": "api.post.handle_post_events_and_forget.channel.error",
|
||||||
"translation": "Encountered error getting channel, channel_id=%s, err=%v"
|
"translation": "Encountered error getting channel, channel_id=%s, err=%v"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.handle_post_events_and_forget.members.error",
|
||||||
|
"translation": "Failed to get channel members channel_id=%v err=%v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.handle_post_events_and_forget.profiles.error",
|
||||||
|
"translation": "Failed to retrieve user profiles team_id=%v, err=%v"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.post.handle_post_events_and_forget.team.error",
|
"id": "api.post.handle_post_events_and_forget.team.error",
|
||||||
"translation": "Encountered error getting team, team_id=%s, err=%v"
|
"translation": "Encountered error getting team, team_id=%s, err=%v"
|
||||||
@@ -751,10 +771,6 @@
|
|||||||
"id": "api.post.make_direct_channel_visible.update_pref.error",
|
"id": "api.post.make_direct_channel_visible.update_pref.error",
|
||||||
"translation": "Failed to update direct channel preference user_id=%v other_user_id=%v err=%v"
|
"translation": "Failed to update direct channel preference user_id=%v other_user_id=%v err=%v"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "api.post.send_notifications_and_forget.members.error",
|
|
||||||
"translation": "Failed to get channel members channel_id=%v err=%v"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "api.post.send_notifications_and_forget.mention_body",
|
"id": "api.post.send_notifications_and_forget.mention_body",
|
||||||
"translation": "You have one new mention."
|
"translation": "You have one new mention."
|
||||||
@@ -787,10 +803,6 @@
|
|||||||
"id": "api.post.send_notifications_and_forget.push_notification.error",
|
"id": "api.post.send_notifications_and_forget.push_notification.error",
|
||||||
"translation": "Failed to send push notificationid=%v, err=%v"
|
"translation": "Failed to send push notificationid=%v, err=%v"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "api.post.send_notifications_and_forget.retrive_profiles.error",
|
|
||||||
"translation": "Failed to retrieve user profiles team_id=%v, err=%v"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "api.post.send_notifications_and_forget.send.error",
|
"id": "api.post.send_notifications_and_forget.send.error",
|
||||||
"translation": "Failed to send mention email successfully email=%v err=%v"
|
"translation": "Failed to send mention email successfully email=%v err=%v"
|
||||||
|
|||||||
16
i18n/es.json
16
i18n/es.json
@@ -711,6 +711,14 @@
|
|||||||
"id": "api.post.handle_post_events_and_forget.channel.error",
|
"id": "api.post.handle_post_events_and_forget.channel.error",
|
||||||
"translation": "Se encontró un error obteniendo el canal, channel_id=%s, err=%v"
|
"translation": "Se encontró un error obteniendo el canal, channel_id=%s, err=%v"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.handle_post_events_and_forget.members.error",
|
||||||
|
"translation": "Falla al obtener los miembros del canal channel_id=%v err=%v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.post.handle_post_events_and_forget.profiles.error",
|
||||||
|
"translation": "Falla al recuperar los perfiles de usuario team_id=%v, err=%v"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.post.handle_post_events_and_forget.team.error",
|
"id": "api.post.handle_post_events_and_forget.team.error",
|
||||||
"translation": "Se encontró un error obteniendo el equipo, team_id=%s, err=%v"
|
"translation": "Se encontró un error obteniendo el equipo, team_id=%s, err=%v"
|
||||||
@@ -751,10 +759,6 @@
|
|||||||
"id": "api.post.make_direct_channel_visible.update_pref.error",
|
"id": "api.post.make_direct_channel_visible.update_pref.error",
|
||||||
"translation": "Falla al actualizar las preferencias del canal directo user_id=%v other_user_id=%v err=%v"
|
"translation": "Falla al actualizar las preferencias del canal directo user_id=%v other_user_id=%v err=%v"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "api.post.send_notifications_and_forget.members.error",
|
|
||||||
"translation": "Falla al obtener los miembros del canal channel_id=%v err=%v"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "api.post.send_notifications_and_forget.mention_body",
|
"id": "api.post.send_notifications_and_forget.mention_body",
|
||||||
"translation": "Tienes una mención nueva."
|
"translation": "Tienes una mención nueva."
|
||||||
@@ -787,10 +791,6 @@
|
|||||||
"id": "api.post.send_notifications_and_forget.push_notification.error",
|
"id": "api.post.send_notifications_and_forget.push_notification.error",
|
||||||
"translation": "Falló el envio de la notificación push notificationid=%v, err=%v"
|
"translation": "Falló el envio de la notificación push notificationid=%v, err=%v"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "api.post.send_notifications_and_forget.retrive_profiles.error",
|
|
||||||
"translation": "Falla al recuperar los perfiles de usuario team_id=%v, err=%v"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "api.post.send_notifications_and_forget.send.error",
|
"id": "api.post.send_notifications_and_forget.send.error",
|
||||||
"translation": "Falla al enviar el correo con la mención satisfactoriamente email=%v err=%v"
|
"translation": "Falla al enviar el correo con la mención satisfactoriamente email=%v err=%v"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const (
|
|||||||
ACTION_USER_ADDED = "user_added"
|
ACTION_USER_ADDED = "user_added"
|
||||||
ACTION_USER_REMOVED = "user_removed"
|
ACTION_USER_REMOVED = "user_removed"
|
||||||
ACTION_PREFERENCE_CHANGED = "preference_changed"
|
ACTION_PREFERENCE_CHANGED = "preference_changed"
|
||||||
|
ACTION_EPHEMERAL_MESSAGE = "ephemeral_message"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ const (
|
|||||||
POST_SYSTEM_MESSAGE_PREFIX = "system_"
|
POST_SYSTEM_MESSAGE_PREFIX = "system_"
|
||||||
POST_DEFAULT = ""
|
POST_DEFAULT = ""
|
||||||
POST_SLACK_ATTACHMENT = "slack_attachment"
|
POST_SLACK_ATTACHMENT = "slack_attachment"
|
||||||
|
POST_SYSTEM_GENERIC = "system_generic"
|
||||||
POST_JOIN_LEAVE = "system_join_leave"
|
POST_JOIN_LEAVE = "system_join_leave"
|
||||||
POST_HEADER_CHANGE = "system_header_change"
|
POST_HEADER_CHANGE = "system_header_change"
|
||||||
|
POST_EPHEMERAL = "system_ephemeral"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ const holders = defineMessages({
|
|||||||
write: {
|
write: {
|
||||||
id: 'create_post.write',
|
id: 'create_post.write',
|
||||||
defaultMessage: 'Write a message...'
|
defaultMessage: 'Write a message...'
|
||||||
},
|
|
||||||
deleteMsg: {
|
|
||||||
id: 'create_post.deleteMsg',
|
|
||||||
defaultMessage: '(message deleted)'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,7 +66,6 @@ class CreatePost extends React.Component {
|
|||||||
this.sendMessage = this.sendMessage.bind(this);
|
this.sendMessage = this.sendMessage.bind(this);
|
||||||
|
|
||||||
PostStore.clearDraftUploads();
|
PostStore.clearDraftUploads();
|
||||||
PostStore.deleteMessage(this.props.intl.formatMessage(holders.deleteMsg));
|
|
||||||
|
|
||||||
const draft = this.getCurrentDraft();
|
const draft = this.getCurrentDraft();
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default class DeletePostModal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PostStore.removePost(this.state.post.id, this.state.post.channel_id);
|
PostStore.deletePost(this.state.post);
|
||||||
AsyncClient.getPosts(this.state.post.channel_id);
|
AsyncClient.getPosts(this.state.post.channel_id);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class PostBody extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
links: linkData.links,
|
links: linkData.links,
|
||||||
message: linkData.text,
|
|
||||||
post: this.props.post,
|
post: this.props.post,
|
||||||
hasUserProfiles: profiles && Object.keys(profiles).length > 1
|
hasUserProfiles: profiles && Object.keys(profiles).length > 1
|
||||||
};
|
};
|
||||||
@@ -106,7 +105,9 @@ class PostBody extends React.Component {
|
|||||||
if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) {
|
if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) {
|
||||||
this.embed = this.createEmbed(linkData.links[0]);
|
this.embed = this.createEmbed(linkData.links[0]);
|
||||||
}
|
}
|
||||||
this.setState({links: linkData.links, message: linkData.text});
|
this.setState({
|
||||||
|
links: linkData.links
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmbed(link) {
|
createEmbed(link) {
|
||||||
@@ -310,6 +311,23 @@ class PostBody extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let message;
|
||||||
|
if (this.props.post.state === Constants.POST_DELETED) {
|
||||||
|
message = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='post_body.deleted'
|
||||||
|
defaultMessage='(message deleted)'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = (
|
||||||
|
<span
|
||||||
|
onClick={TextFormatting.handleClick}
|
||||||
|
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message)}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{comment}
|
{comment}
|
||||||
@@ -320,11 +338,7 @@ class PostBody extends React.Component {
|
|||||||
className={postClass}
|
className={postClass}
|
||||||
>
|
>
|
||||||
{loading}
|
{loading}
|
||||||
<span
|
{message}
|
||||||
ref='message_span'
|
|
||||||
onClick={TextFormatting.handleClick}
|
|
||||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.message)}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<PostBodyAdditionalContent
|
<PostBodyAdditionalContent
|
||||||
post={this.state.post}
|
post={this.state.post}
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ export default class PostInfo extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this);
|
this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this);
|
||||||
|
this.removePost = this.removePost.bind(this);
|
||||||
}
|
}
|
||||||
createDropdown() {
|
createDropdown() {
|
||||||
var post = this.props.post;
|
var post = this.props.post;
|
||||||
var isOwner = UserStore.getCurrentId() === post.user_id;
|
var isOwner = UserStore.getCurrentId() === post.user_id;
|
||||||
var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
|
var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
|
||||||
|
|
||||||
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) {
|
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || Utils.isPostEphemeral(post)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +167,25 @@ export default class PostInfo extends React.Component {
|
|||||||
this.setState({copiedLink: false});
|
this.setState({copiedLink: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
removePost() {
|
||||||
|
EventHelpers.emitRemovePost(this.props.post);
|
||||||
|
}
|
||||||
|
createRemovePostButton(post) {
|
||||||
|
if (!Utils.isPostEphemeral(post)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className='post__remove theme'
|
||||||
|
type='button'
|
||||||
|
onClick={this.removePost}
|
||||||
|
>
|
||||||
|
{'×'}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
var post = this.props.post;
|
var post = this.props.post;
|
||||||
var comments = '';
|
var comments = '';
|
||||||
@@ -178,7 +198,7 @@ export default class PostInfo extends React.Component {
|
|||||||
commentCountText = '';
|
commentCountText = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING && post.state !== Constants.POST_DELETED) {
|
if (post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING && !Utils.isPostEphemeral(post)) {
|
||||||
comments = (
|
comments = (
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
@@ -264,6 +284,7 @@ export default class PostInfo extends React.Component {
|
|||||||
>
|
>
|
||||||
{permalinkOverlay}
|
{permalinkOverlay}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
{this.createRemovePostButton(post)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Constants from '../utils/constants.jsx';
|
|||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
import * as AsyncClient from '../utils/async_client.jsx';
|
import * as AsyncClient from '../utils/async_client.jsx';
|
||||||
import * as Client from '../utils/client.jsx';
|
import * as Client from '../utils/client.jsx';
|
||||||
|
import * as Utils from '../utils/utils.jsx';
|
||||||
|
|
||||||
export function emitChannelClickEvent(channel) {
|
export function emitChannelClickEvent(channel) {
|
||||||
AsyncClient.getChannels(true);
|
AsyncClient.getChannels(true);
|
||||||
@@ -180,3 +181,27 @@ export function emitPreferenceChangedEvent(preference) {
|
|||||||
preference
|
preference
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function emitRemovePost(post) {
|
||||||
|
AppDispatcher.handleViewAction({
|
||||||
|
type: Constants.ActionTypes.REMOVE_POST,
|
||||||
|
post
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendEphemeralPost(message, channelId) {
|
||||||
|
const timestamp = Utils.getTimestamp();
|
||||||
|
const post = {
|
||||||
|
id: Utils.generateId(),
|
||||||
|
user_id: '0',
|
||||||
|
channel_id: channelId || ChannelStore.getCurrentId(),
|
||||||
|
message,
|
||||||
|
type: Constants.POST_TYPE_EPHEMERAL,
|
||||||
|
create_at: timestamp,
|
||||||
|
update_at: timestamp,
|
||||||
|
filenames: [],
|
||||||
|
props: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
emitPostRecievedEvent(post);
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class PostStoreClass extends EventEmitter {
|
|||||||
this.clearFocusedPost = this.clearFocusedPost.bind(this);
|
this.clearFocusedPost = this.clearFocusedPost.bind(this);
|
||||||
this.clearChannelVisibility = this.clearChannelVisibility.bind(this);
|
this.clearChannelVisibility = this.clearChannelVisibility.bind(this);
|
||||||
|
|
||||||
|
this.deletePost = this.deletePost.bind(this);
|
||||||
this.removePost = this.removePost.bind(this);
|
this.removePost = this.removePost.bind(this);
|
||||||
|
|
||||||
this.getPendingPosts = this.getPendingPosts.bind(this);
|
this.getPendingPosts = this.getPendingPosts.bind(this);
|
||||||
@@ -65,10 +66,6 @@ class PostStoreClass extends EventEmitter {
|
|||||||
this.clearPendingPosts = this.clearPendingPosts.bind(this);
|
this.clearPendingPosts = this.clearPendingPosts.bind(this);
|
||||||
this.updatePendingPost = this.updatePendingPost.bind(this);
|
this.updatePendingPost = this.updatePendingPost.bind(this);
|
||||||
|
|
||||||
this.storeUnseenDeletedPost = this.storeUnseenDeletedPost.bind(this);
|
|
||||||
this.getUnseenDeletedPosts = this.getUnseenDeletedPosts.bind(this);
|
|
||||||
this.clearUnseenDeletedPosts = this.clearUnseenDeletedPosts.bind(this);
|
|
||||||
|
|
||||||
// These functions are bad and work should be done to remove this system when the RHS dies
|
// These functions are bad and work should be done to remove this system when the RHS dies
|
||||||
this.storeSelectedPost = this.storeSelectedPost.bind(this);
|
this.storeSelectedPost = this.storeSelectedPost.bind(this);
|
||||||
this.getSelectedPost = this.getSelectedPost.bind(this);
|
this.getSelectedPost = this.getSelectedPost.bind(this);
|
||||||
@@ -211,28 +208,6 @@ class PostStoreClass extends EventEmitter {
|
|||||||
postList.order = this.postsInfo[id].pendingPosts.order.concat(postList.order);
|
postList.order = this.postsInfo[id].pendingPosts.order.concat(postList.order);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add deleted posts
|
|
||||||
if (this.postsInfo[id].hasOwnProperty('deletedPosts')) {
|
|
||||||
Object.assign(postList.posts, this.postsInfo[id].deletedPosts);
|
|
||||||
|
|
||||||
for (const postID in this.postsInfo[id].deletedPosts) {
|
|
||||||
if (this.postsInfo[id].deletedPosts.hasOwnProperty(postID)) {
|
|
||||||
postList.order.push(postID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge would be faster
|
|
||||||
postList.order.sort((a, b) => {
|
|
||||||
if (postList.posts[a].create_at > postList.posts[b].create_at) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (postList.posts[a].create_at < postList.posts[b].create_at) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return postList;
|
return postList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,15 +261,6 @@ class PostStoreClass extends EventEmitter {
|
|||||||
if (combinedPosts.order.indexOf(pid) === -1) {
|
if (combinedPosts.order.indexOf(pid) === -1) {
|
||||||
combinedPosts.order.push(pid);
|
combinedPosts.order.push(pid);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (pid in combinedPosts.posts) {
|
|
||||||
Reflect.deleteProperty(combinedPosts.posts, pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = combinedPosts.order.indexOf(pid);
|
|
||||||
if (index !== -1) {
|
|
||||||
combinedPosts.order.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,6 +331,22 @@ class PostStoreClass extends EventEmitter {
|
|||||||
this.postsInfo[id].atBottom = atBottom;
|
this.postsInfo[id].atBottom = atBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletePost(post) {
|
||||||
|
const postList = this.postsInfo[post.channel_id].postList;
|
||||||
|
|
||||||
|
if (isPostListNull(postList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.id in postList.posts) {
|
||||||
|
// make sure to copy the post so that component state changes work properly
|
||||||
|
postList.posts[post.id] = Object.assign({}, post, {
|
||||||
|
state: Constants.POST_DELETED,
|
||||||
|
filenames: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removePost(post) {
|
removePost(post) {
|
||||||
const channelId = post.channel_id;
|
const channelId = post.channel_id;
|
||||||
this.makePostsInfo(channelId);
|
this.makePostsInfo(channelId);
|
||||||
@@ -439,37 +421,6 @@ class PostStoreClass extends EventEmitter {
|
|||||||
this.emitChange();
|
this.emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
storeUnseenDeletedPost(post) {
|
|
||||||
let posts = this.getUnseenDeletedPosts(post.channel_id);
|
|
||||||
|
|
||||||
if (!posts) {
|
|
||||||
posts = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
post.message = this.delete_message;
|
|
||||||
post.state = Constants.POST_DELETED;
|
|
||||||
post.filenames = [];
|
|
||||||
|
|
||||||
posts[post.id] = post;
|
|
||||||
|
|
||||||
this.makePostsInfo(post.channel_id);
|
|
||||||
this.postsInfo[post.channel_id].deletedPosts = posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnseenDeletedPosts(channelId) {
|
|
||||||
if (this.postsInfo.hasOwnProperty(channelId)) {
|
|
||||||
return this.postsInfo[channelId].deletedPosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearUnseenDeletedPosts(channelId) {
|
|
||||||
if (this.postsInfo.hasOwnProperty(channelId)) {
|
|
||||||
Reflect.deleteProperty(this.postsInfo[channelId], 'deletedPosts');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storeSelectedPost(postList) {
|
storeSelectedPost(postList) {
|
||||||
this.selectedPost = postList;
|
this.selectedPost = postList;
|
||||||
}
|
}
|
||||||
@@ -581,9 +532,6 @@ class PostStoreClass extends EventEmitter {
|
|||||||
|
|
||||||
return commentCount;
|
return commentCount;
|
||||||
}
|
}
|
||||||
deleteMessage(msg) {
|
|
||||||
this.delete_message = msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var PostStore = new PostStoreClass();
|
var PostStore = new PostStoreClass();
|
||||||
@@ -615,7 +563,6 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
|
|||||||
case ActionTypes.CLICK_CHANNEL:
|
case ActionTypes.CLICK_CHANNEL:
|
||||||
PostStore.clearFocusedPost();
|
PostStore.clearFocusedPost();
|
||||||
PostStore.clearChannelVisibility(action.id, true);
|
PostStore.clearChannelVisibility(action.id, true);
|
||||||
PostStore.clearUnseenDeletedPosts(action.prev);
|
|
||||||
break;
|
break;
|
||||||
case ActionTypes.CREATE_POST:
|
case ActionTypes.CREATE_POST:
|
||||||
PostStore.storePendingPost(action.post);
|
PostStore.storePendingPost(action.post);
|
||||||
@@ -623,7 +570,10 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
|
|||||||
PostStore.jumpPostsViewToBottom();
|
PostStore.jumpPostsViewToBottom();
|
||||||
break;
|
break;
|
||||||
case ActionTypes.POST_DELETED:
|
case ActionTypes.POST_DELETED:
|
||||||
PostStore.storeUnseenDeletedPost(action.post);
|
PostStore.deletePost(action.post);
|
||||||
|
PostStore.emitChange();
|
||||||
|
break;
|
||||||
|
case ActionTypes.REMOVE_POST:
|
||||||
PostStore.removePost(action.post);
|
PostStore.removePost(action.post);
|
||||||
PostStore.emitChange();
|
PostStore.emitChange();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class SocketStoreClass extends EventEmitter {
|
|||||||
handleMessage(msg) {
|
handleMessage(msg) {
|
||||||
switch (msg.action) {
|
switch (msg.action) {
|
||||||
case SocketEvents.POSTED:
|
case SocketEvents.POSTED:
|
||||||
|
case SocketEvents.EPHEMERAL_MESSAGE:
|
||||||
handleNewPostEvent(msg, this.translations);
|
handleNewPostEvent(msg, this.translations);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -179,7 +180,6 @@ function handleNewPostEvent(msg, translations) {
|
|||||||
mentions = JSON.parse(msg.props.mentions);
|
mentions = JSON.parse(msg.props.mentions);
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelType = msgProps.channel_type;
|
|
||||||
const channel = ChannelStore.get(msg.channel_id);
|
const channel = ChannelStore.get(msg.channel_id);
|
||||||
const user = UserStore.getCurrentUser();
|
const user = UserStore.getCurrentUser();
|
||||||
const member = ChannelStore.getMember(msg.channel_id);
|
const member = ChannelStore.getMember(msg.channel_id);
|
||||||
@@ -191,7 +191,7 @@ function handleNewPostEvent(msg, translations) {
|
|||||||
|
|
||||||
if (notifyLevel === 'none') {
|
if (notifyLevel === 'none') {
|
||||||
return;
|
return;
|
||||||
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channelType !== Constants.DM_CHANNEL) {
|
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== Constants.DM_CHANNEL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default {
|
|||||||
LEAVE_CHANNEL: null,
|
LEAVE_CHANNEL: null,
|
||||||
CREATE_POST: null,
|
CREATE_POST: null,
|
||||||
POST_DELETED: null,
|
POST_DELETED: null,
|
||||||
|
REMOVE_POST: null,
|
||||||
|
|
||||||
RECIEVED_CHANNELS: null,
|
RECIEVED_CHANNELS: null,
|
||||||
RECIEVED_CHANNEL: null,
|
RECIEVED_CHANNEL: null,
|
||||||
@@ -78,7 +79,8 @@ export default {
|
|||||||
USER_ADDED: 'user_added',
|
USER_ADDED: 'user_added',
|
||||||
USER_REMOVED: 'user_removed',
|
USER_REMOVED: 'user_removed',
|
||||||
TYPING: 'typing',
|
TYPING: 'typing',
|
||||||
PREFERENCE_CHANGED: 'preference_changed'
|
PREFERENCE_CHANGED: 'preference_changed',
|
||||||
|
EPHEMERAL_MESSAGE: 'ephemeral_message'
|
||||||
},
|
},
|
||||||
|
|
||||||
//SPECIAL_MENTIONS: ['all', 'channel'],
|
//SPECIAL_MENTIONS: ['all', 'channel'],
|
||||||
@@ -126,6 +128,7 @@ export default {
|
|||||||
POST_LOADING: 'loading',
|
POST_LOADING: 'loading',
|
||||||
POST_FAILED: 'failed',
|
POST_FAILED: 'failed',
|
||||||
POST_DELETED: 'deleted',
|
POST_DELETED: 'deleted',
|
||||||
|
POST_TYPE_EPHEMERAL: 'system_ephemeral',
|
||||||
POST_TYPE_JOIN_LEAVE: 'system_join_leave',
|
POST_TYPE_JOIN_LEAVE: 'system_join_leave',
|
||||||
SYSTEM_MESSAGE_PREFIX: 'system_',
|
SYSTEM_MESSAGE_PREFIX: 'system_',
|
||||||
SYSTEM_MESSAGE_PROFILE_NAME: 'System',
|
SYSTEM_MESSAGE_PROFILE_NAME: 'System',
|
||||||
|
|||||||
@@ -1355,3 +1355,7 @@ export function languages() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPostEphemeral(post) {
|
||||||
|
return post.type === Constants.POST_TYPE_EPHEMERAL || post.state === Constants.POST_DELETED;
|
||||||
|
}
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ body.ios {
|
|||||||
@include legacy-pie-clearfix;
|
@include legacy-pie-clearfix;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.dropdown, .comment-icon__container, .post__reply {
|
.dropdown, .comment-icon__container, .post__reply, .post__remove {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
.permalink-icon {
|
.permalink-icon {
|
||||||
@@ -646,6 +646,13 @@ body.ios {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post__remove {
|
||||||
|
display: inline-block;
|
||||||
|
visibility: hidden;
|
||||||
|
margin-right: 5px;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
.post__body {
|
.post__body {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
padding: 0.2em 0.5em 0em;
|
padding: 0.2em 0.5em 0em;
|
||||||
|
|||||||
@@ -566,7 +566,6 @@
|
|||||||
"create_post.comment": "Comment",
|
"create_post.comment": "Comment",
|
||||||
"create_post.post": "Post",
|
"create_post.post": "Post",
|
||||||
"create_post.write": "Write a message...",
|
"create_post.write": "Write a message...",
|
||||||
"create_post.deleteMsg": "(message deleted)",
|
|
||||||
"create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>",
|
"create_post.tutorialTip": "<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>",
|
||||||
"delete_channel.channel": "channel",
|
"delete_channel.channel": "channel",
|
||||||
"delete_channel.group": "group",
|
"delete_channel.group": "group",
|
||||||
@@ -772,6 +771,7 @@
|
|||||||
"members_popover.title": "Members",
|
"members_popover.title": "Members",
|
||||||
"post_attachment.collapse": "▲ collapse text",
|
"post_attachment.collapse": "▲ collapse text",
|
||||||
"post_attachment.more": "▼ read more",
|
"post_attachment.more": "▼ read more",
|
||||||
|
"post_body.deleted": "(message deleted)",
|
||||||
"post_body.plusOne": " plus 1 other file",
|
"post_body.plusOne": " plus 1 other file",
|
||||||
"post_body.plusMore": " plus {count} other files",
|
"post_body.plusMore": " plus {count} other files",
|
||||||
"post_body.commentedOn": "Commented on {name}{apostrophe} message: ",
|
"post_body.commentedOn": "Commented on {name}{apostrophe} message: ",
|
||||||
|
|||||||
@@ -585,7 +585,6 @@
|
|||||||
"create_comment.file": "Subiendo archivo",
|
"create_comment.file": "Subiendo archivo",
|
||||||
"create_comment.files": "Subiendo archivos",
|
"create_comment.files": "Subiendo archivos",
|
||||||
"create_post.comment": "Comentario",
|
"create_post.comment": "Comentario",
|
||||||
"create_post.deleteMsg": "(mensaje eliminado)",
|
|
||||||
"create_post.post": "Mensaje",
|
"create_post.post": "Mensaje",
|
||||||
"create_post.tutorialTip": "<h4>Enviar Mensajes</h4> <p>Escribe aquí para redactar un mensaje y presiona <strong>Retorno</strong> para enviarlo.</p><p>Pincha el botón de <strong>Adjuntar</strong> para subir una imagen o archivo.</p>",
|
"create_post.tutorialTip": "<h4>Enviar Mensajes</h4> <p>Escribe aquí para redactar un mensaje y presiona <strong>Retorno</strong> para enviarlo.</p><p>Pincha el botón de <strong>Adjuntar</strong> para subir una imagen o archivo.</p>",
|
||||||
"create_post.write": "Escribe un mensaje...",
|
"create_post.write": "Escribe un mensaje...",
|
||||||
@@ -805,6 +804,7 @@
|
|||||||
"post_attachment.collapse": "▲ colapsar texto",
|
"post_attachment.collapse": "▲ colapsar texto",
|
||||||
"post_attachment.more": "▼ leer más",
|
"post_attachment.more": "▼ leer más",
|
||||||
"post_body.commentedOn": "Comentó el mensaje de {name}{apostrophe}: ",
|
"post_body.commentedOn": "Comentó el mensaje de {name}{apostrophe}: ",
|
||||||
|
"post_body.deleted": "(mensaje eliminado)",
|
||||||
"post_body.plusMore": " más {count} otros archivos",
|
"post_body.plusMore": " más {count} otros archivos",
|
||||||
"post_body.plusOne": " más 1 archivo",
|
"post_body.plusOne": " más 1 archivo",
|
||||||
"post_body.retry": "Reintentar",
|
"post_body.retry": "Reintentar",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user