mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge branch 'master' of https://github.com/mattermost/platform into plt-375
Conflicts: web/react/components/user_settings/user_settings_appearance.jsx
This commit is contained in:
10
Makefile
10
Makefile
@@ -140,11 +140,11 @@ check: install
|
||||
|
||||
test: install
|
||||
@mkdir -p logs
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=180s ./api || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=12s ./model || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./store || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./utils || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=120s ./web || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=600s ./api || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=60s ./model || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=600s ./store || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=600s ./utils || exit 1
|
||||
@$(GO) test $(GOFLAGS) -run=$(TESTS) -test.v -test.timeout=600s ./web || exit 1
|
||||
|
||||
benchmark: install
|
||||
@mkdir -p logs
|
||||
|
||||
24
api/admin.go
24
api/admin.go
@@ -23,8 +23,10 @@ func InitAdmin(r *mux.Router) {
|
||||
sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
|
||||
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
|
||||
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
|
||||
sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
|
||||
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
||||
sr.Handle("/client_props", ApiAppHandler(getClientProperties)).Methods("GET")
|
||||
sr.Handle("/log_client", ApiAppHandler(logClient)).Methods("POST")
|
||||
|
||||
}
|
||||
|
||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -59,6 +61,26 @@ func getClientProperties(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(model.MapToJson(utils.ClientProperties)))
|
||||
}
|
||||
|
||||
func logClient(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
m := model.MapFromJson(r.Body)
|
||||
|
||||
lvl := m["level"]
|
||||
msg := m["message"]
|
||||
|
||||
if len(msg) > 400 {
|
||||
msg = msg[0:399]
|
||||
}
|
||||
|
||||
if lvl == "ERROR" {
|
||||
err := model.NewAppError("client", msg, "")
|
||||
c.LogError(err)
|
||||
}
|
||||
|
||||
rm := make(map[string]string)
|
||||
rm["SUCCESS"] = "true"
|
||||
w.Write([]byte(model.MapToJson(rm)))
|
||||
}
|
||||
|
||||
func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.HasSystemAdminPermissions("getConfig") {
|
||||
return
|
||||
|
||||
@@ -23,7 +23,7 @@ func InitChannel(r *mux.Router) {
|
||||
sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
||||
sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
||||
sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST")
|
||||
sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST")
|
||||
sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).Methods("POST")
|
||||
sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
|
||||
sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
||||
sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(joinChannel)).Methods("POST")
|
||||
@@ -76,7 +76,7 @@ func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.C
|
||||
|
||||
if addMember {
|
||||
cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId,
|
||||
Roles: model.CHANNEL_ROLE_ADMIN, NotifyLevel: model.CHANNEL_NOTIFY_ALL}
|
||||
Roles: model.CHANNEL_ROLE_ADMIN, NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil {
|
||||
return nil, cmresult.Err
|
||||
@@ -134,8 +134,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model
|
||||
if sc, err := CreateChannel(c, channel, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId,
|
||||
Roles: "", NotifyLevel: model.CHANNEL_NOTIFY_ALL}
|
||||
cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil {
|
||||
return nil, cmresult.Err
|
||||
@@ -372,7 +371,8 @@ func JoinChannel(c *Context, channelId string, role string) {
|
||||
}
|
||||
|
||||
if channel.Type == model.CHANNEL_OPEN {
|
||||
cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: role}
|
||||
cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId,
|
||||
Roles: role, NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil {
|
||||
c.Err = cmresult.Err
|
||||
@@ -405,7 +405,9 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
|
||||
if result := <-Srv.Store.Channel().GetByName(user.TeamId, "town-square"); result.Err != nil {
|
||||
err = result.Err
|
||||
} else {
|
||||
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: channelRole}
|
||||
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id,
|
||||
Roles: channelRole, NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil {
|
||||
err = cmResult.Err
|
||||
}
|
||||
@@ -414,7 +416,9 @@ func JoinDefaultChannels(user *model.User, channelRole string) *model.AppError {
|
||||
if result := <-Srv.Store.Channel().GetByName(user.TeamId, "off-topic"); result.Err != nil {
|
||||
err = result.Err
|
||||
} else {
|
||||
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: channelRole}
|
||||
cm := &model.ChannelMember{ChannelId: result.Data.(*model.Channel).Id, UserId: user.Id,
|
||||
Roles: channelRole, NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil {
|
||||
err = cmResult.Err
|
||||
}
|
||||
@@ -694,7 +698,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
oUser := oresult.Data.(*model.User)
|
||||
|
||||
cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_ALL}
|
||||
cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyProps: model.GetDefaultChannelNotifyProps()}
|
||||
|
||||
if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil {
|
||||
l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err)
|
||||
@@ -784,23 +788,18 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
data := model.MapFromJson(r.Body)
|
||||
|
||||
userId := data["user_id"]
|
||||
if len(userId) != 26 {
|
||||
c.SetInvalidParam("updateNotifyLevel", "user_id")
|
||||
c.SetInvalidParam("updateMarkUnreadLevel", "user_id")
|
||||
return
|
||||
}
|
||||
|
||||
channelId := data["channel_id"]
|
||||
if len(channelId) != 26 {
|
||||
c.SetInvalidParam("updateNotifyLevel", "channel_id")
|
||||
return
|
||||
}
|
||||
|
||||
notifyLevel := data["notify_level"]
|
||||
if len(notifyLevel) == 0 || !model.IsChannelNotifyLevelValid(notifyLevel) {
|
||||
c.SetInvalidParam("updateNotifyLevel", "notify_level")
|
||||
c.SetInvalidParam("updateMarkUnreadLevel", "channel_id")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -814,10 +813,29 @@ func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Channel().UpdateNotifyLevel(channelId, userId, notifyLevel); result.Err != nil {
|
||||
result := <-Srv.Store.Channel().GetMember(channelId, userId)
|
||||
if result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(model.MapToJson(data)))
|
||||
member := result.Data.(model.ChannelMember)
|
||||
|
||||
// update whichever notify properties have been provided, but don't change the others
|
||||
if markUnread, exists := data["mark_unread"]; exists {
|
||||
member.NotifyProps["mark_unread"] = markUnread
|
||||
}
|
||||
|
||||
if desktop, exists := data["desktop"]; exists {
|
||||
member.NotifyProps["desktop"] = desktop
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Channel().UpdateMember(&member); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
// return the updated notify properties including any unchanged ones
|
||||
w.Write([]byte(model.MapToJson(member.NotifyProps)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func BenchmarkRemoveChannelMember(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUpdateNotifyLevel(b *testing.B) {
|
||||
func BenchmarkUpdateNotifyProps(b *testing.B) {
|
||||
var (
|
||||
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
|
||||
)
|
||||
@@ -271,9 +271,10 @@ func BenchmarkUpdateNotifyLevel(b *testing.B) {
|
||||
|
||||
for i := range data {
|
||||
newmap := map[string]string{
|
||||
"channel_id": channels[i].Id,
|
||||
"user_id": user.Id,
|
||||
"notify_level": model.CHANNEL_NOTIFY_MENTION,
|
||||
"channel_id": channels[i].Id,
|
||||
"user_id": user.Id,
|
||||
"desktop": model.CHANNEL_NOTIFY_MENTION,
|
||||
"mark_unread": model.CHANNEL_MARK_UNREAD_MENTION,
|
||||
}
|
||||
data[i] = newmap
|
||||
}
|
||||
@@ -282,7 +283,7 @@ func BenchmarkUpdateNotifyLevel(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := range channels {
|
||||
Client.Must(Client.UpdateNotifyLevel(data[j]))
|
||||
Client.Must(Client.UpdateNotifyProps(data[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +803,7 @@ func TestRemoveChannelMember(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestUpdateNotifyLevel(t *testing.T) {
|
||||
func TestUpdateNotifyProps(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
@@ -821,55 +821,94 @@ func TestUpdateNotifyLevel(t *testing.T) {
|
||||
data := make(map[string]string)
|
||||
data["channel_id"] = channel1.Id
|
||||
data["user_id"] = user.Id
|
||||
data["notify_level"] = model.CHANNEL_NOTIFY_MENTION
|
||||
data["desktop"] = model.CHANNEL_NOTIFY_MENTION
|
||||
|
||||
timeBeforeUpdate := model.GetMillis()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if _, err := Client.UpdateNotifyLevel(data); err != nil {
|
||||
// test updating desktop
|
||||
if result, err := Client.UpdateNotifyProps(data); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if notifyProps := result.Data.(map[string]string); notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION {
|
||||
t.Fatal("NotifyProps[\"desktop\"] did not update properly")
|
||||
} else if notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_ALL {
|
||||
t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps["mark_unread"])
|
||||
}
|
||||
|
||||
rget := Client.Must(Client.GetChannels(""))
|
||||
rdata := rget.Data.(*model.ChannelList)
|
||||
if len(rdata.Members) == 0 || rdata.Members[channel1.Id].NotifyLevel != data["notify_level"] {
|
||||
t.Fatal("NotifyLevel did not update properly")
|
||||
}
|
||||
|
||||
if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate {
|
||||
if len(rdata.Members) == 0 || rdata.Members[channel1.Id].NotifyProps["desktop"] != data["desktop"] {
|
||||
t.Fatal("NotifyProps[\"desktop\"] did not update properly")
|
||||
} else if rdata.Members[channel1.Id].LastUpdateAt <= timeBeforeUpdate {
|
||||
t.Fatal("LastUpdateAt did not update")
|
||||
}
|
||||
|
||||
// test an empty update
|
||||
delete(data, "desktop")
|
||||
|
||||
if result, err := Client.UpdateNotifyProps(data); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if notifyProps := result.Data.(map[string]string); notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_ALL {
|
||||
t.Fatalf("NotifyProps[\"mark_unread\"] changed to %v", notifyProps["mark_unread"])
|
||||
} else if notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION {
|
||||
t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps["desktop"])
|
||||
}
|
||||
|
||||
// test updating mark unread
|
||||
data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION
|
||||
|
||||
if result, err := Client.UpdateNotifyProps(data); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if notifyProps := result.Data.(map[string]string); notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_MENTION {
|
||||
t.Fatal("NotifyProps[\"mark_unread\"] did not update properly")
|
||||
} else if notifyProps["desktop"] != model.CHANNEL_NOTIFY_MENTION {
|
||||
t.Fatalf("NotifyProps[\"desktop\"] changed to %v", notifyProps["desktop"])
|
||||
}
|
||||
|
||||
// test updating both
|
||||
data["desktop"] = model.CHANNEL_NOTIFY_NONE
|
||||
data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION
|
||||
|
||||
if result, err := Client.UpdateNotifyProps(data); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if notifyProps := result.Data.(map[string]string); notifyProps["desktop"] != model.CHANNEL_NOTIFY_NONE {
|
||||
t.Fatal("NotifyProps[\"desktop\"] did not update properly")
|
||||
} else if notifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_MENTION {
|
||||
t.Fatal("NotifyProps[\"mark_unread\"] did not update properly")
|
||||
}
|
||||
|
||||
// test error cases
|
||||
data["user_id"] = "junk"
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad user id")
|
||||
}
|
||||
|
||||
data["user_id"] = "12345678901234567890123456"
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad user id")
|
||||
}
|
||||
|
||||
data["user_id"] = user.Id
|
||||
data["channel_id"] = "junk"
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad channel id")
|
||||
}
|
||||
|
||||
data["channel_id"] = "12345678901234567890123456"
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad channel id")
|
||||
}
|
||||
|
||||
data["channel_id"] = channel1.Id
|
||||
data["notify_level"] = ""
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
t.Fatal("Should have errored - empty notify level")
|
||||
data["desktop"] = "junk"
|
||||
data["mark_unread"] = model.CHANNEL_MARK_UNREAD_ALL
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad desktop notify level")
|
||||
}
|
||||
|
||||
data["notify_level"] = "junk"
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
t.Fatal("Should have errored - bad notify level")
|
||||
data["desktop"] = model.CHANNEL_NOTIFY_ALL
|
||||
data["mark_unread"] = "junk"
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - bad mark unread level")
|
||||
}
|
||||
|
||||
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||
@@ -879,8 +918,9 @@ func TestUpdateNotifyLevel(t *testing.T) {
|
||||
|
||||
data["channel_id"] = channel1.Id
|
||||
data["user_id"] = user2.Id
|
||||
data["notify_level"] = model.CHANNEL_NOTIFY_MENTION
|
||||
if _, err := Client.UpdateNotifyLevel(data); err == nil {
|
||||
data["desktop"] = model.CHANNEL_NOTIFY_MENTION
|
||||
data["mark_unread"] = model.CHANNEL_MARK_UNREAD_MENTION
|
||||
if _, err := Client.UpdateNotifyProps(data); err == nil {
|
||||
t.Fatal("Should have errored - user not in channel")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P
|
||||
}
|
||||
}
|
||||
|
||||
post.CreateAt = 0
|
||||
|
||||
post.Hashtags, _ = model.ParseHashtags(post.Message)
|
||||
|
||||
post.UserId = c.Session.UserId
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
### Installation
|
||||
|
||||
- [Software and Hardware Requirements](install/requirements.md)
|
||||
- [Production Installation](install/prod-ubuntu.md)
|
||||
- [Local Machine Setup ](install/single-container-install.md)
|
||||
- [AWS Elastic Beanstalk Setup](install/aws-ebs-setup.md)
|
||||
- [Developer Machine Setup](install/dev-setup.md)
|
||||
@@ -27,3 +28,4 @@
|
||||
## End User Help
|
||||
|
||||
- [Mattermost Markdown Formatting](help/enduser/markdown.md)
|
||||
- [Slack Import](https://github.com/mattermost/platform/blob/master/doc/import/slack-import.md)
|
||||
|
||||
18
doc/import/slack-import.md
Normal file
18
doc/import/slack-import.md
Normal file
@@ -0,0 +1,18 @@
|
||||
#### Slack Import (Preview)
|
||||
|
||||
*Note: As a SaaS service, Slack is able to change its export format quickly. If you encounter issues not mentioned in the documentation below, please let us know by [filing an issue](https://github.com/mattermost/platform/issues).*
|
||||
|
||||
The Slack Import feature in Mattermost is in "Preview" and focus is on supporting migration of teams of less than 100 registered users. The feature can be accessed from by Team Administrators and Team Owners via the `Team Settings -> Import` menu option.
|
||||
|
||||
Mattermost currently supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team.
|
||||
|
||||
- In the feature preview, emails and usernames from Slack are used to create new Mattermost accounts, connected to messages history in imported Slack channels. Users can activate these accounts and by going to the Password Reset screen in Mattermost to set new credentials.
|
||||
- Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost.
|
||||
|
||||
Limitations:
|
||||
|
||||
- Newly added markdown suppport in Slack's Posts 2.0 feature announced on September 28, 2015 is not yet supported.
|
||||
- Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI.
|
||||
- Slack does not export any content from private groups or direct messages that your team has stored in Slack's database.
|
||||
- The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. Also, mentions do not yet resolve as Mattermost usernames (still show Slack ID).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* ``` sudo apt-get update```
|
||||
* ``` sudo apt-get upgrade```
|
||||
|
||||
## Setup Database Server
|
||||
## Set up Database Server
|
||||
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.1
|
||||
1. Install PostgreSQL 9.3+ (or MySQL 5.2+)
|
||||
* ``` sudo apt-get install postgresql postgresql-contrib```
|
||||
@@ -25,13 +25,13 @@
|
||||
1. You can exit the postgres account by typing:
|
||||
* ``` exit```
|
||||
|
||||
## Setup Mattermost Server
|
||||
## Set up Mattermost Server
|
||||
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2
|
||||
1. Download the latest Mattermost Server by typing:
|
||||
* ``` wget https://github.com/mattermost/platform/releases/download/v1.0.0/mattermost.tar.gz```
|
||||
1. Unzip the Mattermost Server by typing:
|
||||
* ``` tar -xvzf mattermost.tar.gz```
|
||||
1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`, in the future we will give guidance for storing under `/opt`.
|
||||
1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`. In the future we will give guidance for storing under `/opt`.
|
||||
1. We have also elected to run the Mattermost Server as the `ubuntu` account for simplicity. We recommend settings up and running the service under a `mattermost` user account with limited permissions.
|
||||
1. Create the storage directory for files. We assume you will have attached a large drive for storage of images and files. For this setup we will assume the directory is located at `/mattermost/data`.
|
||||
* Create the directory by typing:
|
||||
@@ -70,7 +70,7 @@ exec bin/platform
|
||||
* You should see a page titles *Mattermost - Signup*
|
||||
* You can also stop the process by running the command ` sudo stop mattermost`, but we will skip this step for now.
|
||||
|
||||
## Setup Nginx Server
|
||||
## Set up Nginx Server
|
||||
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.3
|
||||
1. We use Nginx for proxying request to the Mattermost Server. The main benefits are:
|
||||
* SSL termination
|
||||
@@ -90,8 +90,7 @@ exec bin/platform
|
||||
1. Configure Nginx to proxy connections from the internet to the Mattermost Server
|
||||
* Create a configuration for Mattermost
|
||||
* ``` sudo touch /etc/nginx/sites-available/mattermost```
|
||||
* Below is a sample configuration with the minimum settings required to configure Mattermost.
|
||||
*
|
||||
* Below is a sample configuration with the minimum settings required to configure Mattermost
|
||||
```
|
||||
server {
|
||||
server_name mattermost.example.com;
|
||||
@@ -118,7 +117,7 @@ exec bin/platform
|
||||
* ``` curl http://localhost```
|
||||
* You should see a page titles *Mattermost - Signup*
|
||||
|
||||
## Setup Nginx with SSL (Recommended)
|
||||
## Set up Nginx with SSL (Recommended)
|
||||
1. You will need a SSL cert from a certificate authority.
|
||||
1. For simplicity we will generate a test certificate.
|
||||
* ``` mkdir ~/cert```
|
||||
@@ -177,9 +176,9 @@ exec bin/platform
|
||||
* Save the Settings
|
||||
1. Update File Settings
|
||||
* Change *Local Directory Location* from `./data/` to `/mattermost/data`
|
||||
1. Update Log Settings
|
||||
1. Update Log Settings.
|
||||
* Set *Log to The Console* to false
|
||||
1. Update Rate Limit Settings
|
||||
1. Update Rate Limit Settings.
|
||||
* Set *Vary By Remote Address* to false
|
||||
* Set *Vary By HTTP Header* to X-Real-IP
|
||||
1. Feel free to modify other settings.
|
||||
|
||||
@@ -355,7 +355,7 @@ Usage:
|
||||
-reset_password Resets the password for a user. It requires the
|
||||
-team_name, -email and -password flag.
|
||||
Example:
|
||||
platform -reset_password -team_name="name" -email="user@example.com" -paossword="newpassword"
|
||||
platform -reset_password -team_name="name" -email="user@example.com" -password="newpassword"
|
||||
|
||||
|
||||
`
|
||||
|
||||
@@ -10,22 +10,24 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CHANNEL_ROLE_ADMIN = "admin"
|
||||
CHANNEL_NOTIFY_ALL = "all"
|
||||
CHANNEL_NOTIFY_MENTION = "mention"
|
||||
CHANNEL_NOTIFY_NONE = "none"
|
||||
CHANNEL_NOTIFY_QUIET = "quiet"
|
||||
CHANNEL_ROLE_ADMIN = "admin"
|
||||
CHANNEL_NOTIFY_DEFAULT = "default"
|
||||
CHANNEL_NOTIFY_ALL = "all"
|
||||
CHANNEL_NOTIFY_MENTION = "mention"
|
||||
CHANNEL_NOTIFY_NONE = "none"
|
||||
CHANNEL_MARK_UNREAD_ALL = "all"
|
||||
CHANNEL_MARK_UNREAD_MENTION = "mention"
|
||||
)
|
||||
|
||||
type ChannelMember struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Roles string `json:"roles"`
|
||||
LastViewedAt int64 `json:"last_viewed_at"`
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
NotifyLevel string `json:"notify_level"`
|
||||
LastUpdateAt int64 `json:"last_update_at"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Roles string `json:"roles"`
|
||||
LastViewedAt int64 `json:"last_viewed_at"`
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
NotifyProps StringMap `json:"notify_props"`
|
||||
LastUpdateAt int64 `json:"last_update_at"`
|
||||
}
|
||||
|
||||
func (o *ChannelMember) ToJson() string {
|
||||
@@ -64,8 +66,14 @@ func (o *ChannelMember) IsValid() *AppError {
|
||||
}
|
||||
}
|
||||
|
||||
if len(o.NotifyLevel) > 20 || !IsChannelNotifyLevelValid(o.NotifyLevel) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+o.NotifyLevel)
|
||||
notifyLevel := o.NotifyProps["desktop"]
|
||||
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+notifyLevel)
|
||||
}
|
||||
|
||||
markUnreadLevel := o.NotifyProps["mark_unread"]
|
||||
if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
|
||||
return NewAppError("ChannelMember.IsValid", "Invalid mark unread level", "mark_unread_level="+markUnreadLevel)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -75,6 +83,24 @@ func (o *ChannelMember) PreSave() {
|
||||
o.LastUpdateAt = GetMillis()
|
||||
}
|
||||
|
||||
func IsChannelNotifyLevelValid(notifyLevel string) bool {
|
||||
return notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET
|
||||
func (o *ChannelMember) PreUpdate() {
|
||||
o.LastUpdateAt = GetMillis()
|
||||
}
|
||||
|
||||
func IsChannelNotifyLevelValid(notifyLevel string) bool {
|
||||
return notifyLevel == CHANNEL_NOTIFY_DEFAULT ||
|
||||
notifyLevel == CHANNEL_NOTIFY_ALL ||
|
||||
notifyLevel == CHANNEL_NOTIFY_MENTION ||
|
||||
notifyLevel == CHANNEL_NOTIFY_NONE
|
||||
}
|
||||
|
||||
func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
|
||||
return markUnreadLevel == CHANNEL_MARK_UNREAD_ALL || markUnreadLevel == CHANNEL_MARK_UNREAD_MENTION
|
||||
}
|
||||
|
||||
func GetDefaultChannelNotifyProps() StringMap {
|
||||
return StringMap{
|
||||
"desktop": CHANNEL_NOTIFY_DEFAULT,
|
||||
"mark_unread": CHANNEL_MARK_UNREAD_ALL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,24 +31,34 @@ func TestChannelMemberIsValid(t *testing.T) {
|
||||
}
|
||||
|
||||
o.Roles = "missing"
|
||||
o.NotifyLevel = CHANNEL_NOTIFY_ALL
|
||||
o.NotifyProps = GetDefaultChannelNotifyProps()
|
||||
o.UserId = NewId()
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.Roles = CHANNEL_ROLE_ADMIN
|
||||
o.NotifyLevel = "junk"
|
||||
o.NotifyProps["desktop"] = "junk"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.NotifyLevel = "123456789012345678901"
|
||||
o.NotifyProps["desktop"] = "123456789012345678901"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.NotifyLevel = CHANNEL_NOTIFY_ALL
|
||||
o.NotifyProps["desktop"] = CHANNEL_NOTIFY_ALL
|
||||
if err := o.IsValid(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
o.NotifyProps["mark_unread"] = "123456789012345678901"
|
||||
if err := o.IsValid(); err == nil {
|
||||
t.Fatal("should be invalid")
|
||||
}
|
||||
|
||||
o.NotifyProps["mark_unread"] = CHANNEL_MARK_UNREAD_ALL
|
||||
if err := o.IsValid(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -450,8 +450,8 @@ func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/channels/update_notify_level", MapToJson(data)); err != nil {
|
||||
func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/channels/update_notify_props", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
|
||||
@@ -120,7 +120,10 @@ func (o *Post) PreSave() {
|
||||
|
||||
o.OriginalId = ""
|
||||
|
||||
o.CreateAt = GetMillis()
|
||||
if o.CreateAt == 0 {
|
||||
o.CreateAt = GetMillis()
|
||||
}
|
||||
|
||||
o.UpdateAt = o.CreateAt
|
||||
|
||||
if o.Props == nil {
|
||||
|
||||
@@ -83,5 +83,18 @@ func TestPostIsValid(t *testing.T) {
|
||||
func TestPostPreSave(t *testing.T) {
|
||||
o := Post{Message: "test"}
|
||||
o.PreSave()
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
t.Fatal("should be set")
|
||||
}
|
||||
|
||||
past := GetMillis() - 1
|
||||
o = Post{Message: "test", CreateAt: past}
|
||||
o.PreSave()
|
||||
|
||||
if o.CreateAt > past {
|
||||
t.Fatal("should not be updated")
|
||||
}
|
||||
|
||||
o.Etag()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
l4g "code.google.com/p/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
@@ -30,13 +31,55 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
|
||||
tablem.ColMap("ChannelId").SetMaxSize(26)
|
||||
tablem.ColMap("UserId").SetMaxSize(26)
|
||||
tablem.ColMap("Roles").SetMaxSize(64)
|
||||
tablem.ColMap("NotifyLevel").SetMaxSize(20)
|
||||
tablem.ColMap("NotifyProps").SetMaxSize(2000)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) UpgradeSchemaIfNeeded() {
|
||||
if s.CreateColumnIfNotExists("ChannelMembers", "NotifyProps", "varchar(2000)", "varchar(2000)", "{}") {
|
||||
// populate NotifyProps from existing NotifyLevel field
|
||||
|
||||
// set default values
|
||||
_, err := s.GetMaster().Exec(
|
||||
`UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
NotifyProps = CONCAT('{"desktop":"', CONCAT(NotifyLevel, '","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}'))`)
|
||||
if err != nil {
|
||||
l4g.Error("Unable to set default values for ChannelMembers.NotifyProps")
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
|
||||
// assume channels with all notifications enabled are just using the default settings
|
||||
_, err = s.GetMaster().Exec(
|
||||
`UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_DEFAULT + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_ALL + `"}'
|
||||
WHERE
|
||||
NotifyLevel = '` + model.CHANNEL_NOTIFY_ALL + `'`)
|
||||
if err != nil {
|
||||
l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=all")
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
|
||||
// set quiet mode channels to have no notifications and only mark the channel unread on mentions
|
||||
_, err = s.GetMaster().Exec(
|
||||
`UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
NotifyProps = '{"desktop":"` + model.CHANNEL_NOTIFY_NONE + `","mark_unread":"` + model.CHANNEL_MARK_UNREAD_MENTION + `"}'
|
||||
WHERE
|
||||
NotifyLevel = 'quiet'`)
|
||||
if err != nil {
|
||||
l4g.Error("Unable to set values for ChannelMembers.NotifyProps when members previously had notifyLevel=quiet")
|
||||
l4g.Error(err.Error())
|
||||
}
|
||||
|
||||
s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel")
|
||||
}
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) CreateIndexesIfNotExists() {
|
||||
@@ -386,6 +429,34 @@ func (s SqlChannelStore) SaveMember(member *model.ChannelMember) StoreChannel {
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) UpdateMember(member *model.ChannelMember) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
member.PreUpdate()
|
||||
|
||||
if result.Err = member.IsValid(); result.Err != nil {
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Update(member); err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.UpdateMember", "We encounted an error updating the channel member",
|
||||
"channel_id="+member.ChannelId+", "+"user_id="+member.UserId+", "+err.Error())
|
||||
} else {
|
||||
result.Data = member
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetMembers(channelId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
@@ -649,35 +720,6 @@ func (s SqlChannelStore) IncrementMentionCount(channelId string, userId string)
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
updateAt := model.GetMillis()
|
||||
|
||||
_, err := s.GetMaster().Exec(
|
||||
`UPDATE
|
||||
ChannelMembers
|
||||
SET
|
||||
NotifyLevel = :NotifyLevel,
|
||||
LastUpdateAt = :LastUpdateAt
|
||||
WHERE
|
||||
UserId = :UserId
|
||||
AND ChannelId = :ChannelId`,
|
||||
map[string]interface{}{"ChannelId": channelId, "UserId": userId, "NotifyLevel": notifyLevel, "LastUpdateAt": updateAt})
|
||||
if err != nil {
|
||||
result.Err = model.NewAppError("SqlChannelStore.UpdateNotifyLevel", "We couldn't update the notify level", "channel_id="+channelId+", user_id="+userId+", "+err.Error())
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
close(storeChannel)
|
||||
}()
|
||||
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetForExport(teamId string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
|
||||
@@ -135,13 +135,13 @@ func TestChannelStoreDelete(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o2.Id
|
||||
m2.UserId = m1.UserId
|
||||
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m2))
|
||||
|
||||
if r := <-store.Channel().Delete(o1.Id, model.GetMillis()); r.Err != nil {
|
||||
@@ -222,13 +222,13 @@ func TestChannelMemberStore(t *testing.T) {
|
||||
o1 := model.ChannelMember{}
|
||||
o1.ChannelId = c1.Id
|
||||
o1.UserId = u1.Id
|
||||
o1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
o1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&o1))
|
||||
|
||||
o2 := model.ChannelMember{}
|
||||
o2.ChannelId = c1.Id
|
||||
o2.UserId = u2.Id
|
||||
o2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
o2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&o2))
|
||||
|
||||
c1t2 := (<-store.Channel().Get(c1.Id)).Data.(*model.Channel)
|
||||
@@ -291,7 +291,7 @@ func TestChannelStorePermissionsTo(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
count := (<-store.Channel().CheckPermissionsTo(o1.TeamId, o1.Id, m1.UserId)).Data.(int64)
|
||||
@@ -371,19 +371,19 @@ func TestChannelStoreGetChannels(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o1.Id
|
||||
m2.UserId = model.NewId()
|
||||
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m2))
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = o2.Id
|
||||
m3.UserId = model.NewId()
|
||||
m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m3))
|
||||
|
||||
cresult := <-store.Channel().GetChannels(o1.TeamId, m1.UserId)
|
||||
@@ -414,19 +414,19 @@ func TestChannelStoreGetMoreChannels(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o1.Id
|
||||
m2.UserId = model.NewId()
|
||||
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m2))
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = o2.Id
|
||||
m3.UserId = model.NewId()
|
||||
m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m3))
|
||||
|
||||
o3 := model.Channel{}
|
||||
@@ -482,19 +482,19 @@ func TestChannelStoreGetChannelCounts(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
m2 := model.ChannelMember{}
|
||||
m2.ChannelId = o1.Id
|
||||
m2.UserId = model.NewId()
|
||||
m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m2))
|
||||
|
||||
m3 := model.ChannelMember{}
|
||||
m3.ChannelId = o2.Id
|
||||
m3.UserId = model.NewId()
|
||||
m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m3.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m3))
|
||||
|
||||
cresult := <-store.Channel().GetChannelCounts(o1.TeamId, m1.UserId)
|
||||
@@ -523,7 +523,7 @@ func TestChannelStoreUpdateLastViewedAt(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
err := (<-store.Channel().UpdateLastViewedAt(m1.ChannelId, m1.UserId)).Err
|
||||
@@ -551,7 +551,7 @@ func TestChannelStoreIncrementMentionCount(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = o1.Id
|
||||
m1.UserId = model.NewId()
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
err := (<-store.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId)).Err
|
||||
|
||||
@@ -484,7 +484,7 @@ func TestPostStoreSearch(t *testing.T) {
|
||||
m1 := model.ChannelMember{}
|
||||
m1.ChannelId = c1.Id
|
||||
m1.UserId = userId
|
||||
m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
|
||||
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
|
||||
Must(store.Channel().SaveMember(&m1))
|
||||
|
||||
c2 := &model.Channel{}
|
||||
|
||||
@@ -62,6 +62,7 @@ type ChannelStore interface {
|
||||
GetForExport(teamId string) StoreChannel
|
||||
|
||||
SaveMember(member *model.ChannelMember) StoreChannel
|
||||
UpdateMember(member *model.ChannelMember) StoreChannel
|
||||
GetMembers(channelId string) StoreChannel
|
||||
GetMember(channelId string, userId string) StoreChannel
|
||||
RemoveMember(channelId string, userId string) StoreChannel
|
||||
@@ -71,7 +72,6 @@ type ChannelStore interface {
|
||||
CheckPermissionsToByName(teamId string, channelName string, userId string) StoreChannel
|
||||
UpdateLastViewedAt(channelId string, userId string) StoreChannel
|
||||
IncrementMentionCount(channelId string, userId string) StoreChannel
|
||||
UpdateNotifyLevel(channelId string, userId string, notifyLevel string) StoreChannel
|
||||
}
|
||||
|
||||
type PostStore interface {
|
||||
|
||||
@@ -73,6 +73,12 @@ export default class SqlSettings extends React.Component {
|
||||
|
||||
handleGenerate(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var cfm = global.window.confirm('Warning: re-generating this salt may cause some columns in the database to return empty results.');
|
||||
if (cfm === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
React.findDOMNode(this.refs.AtRestEncryptKey).value = crypto.randomBytes(256).toString('base64').substring(0, 32);
|
||||
var s = {saveNeeded: true, serverError: this.state.serverError};
|
||||
this.setState(s);
|
||||
|
||||
@@ -15,14 +15,24 @@ export default class ChannelNotifications extends React.Component {
|
||||
|
||||
this.onListenerChange = this.onListenerChange.bind(this);
|
||||
this.updateSection = this.updateSection.bind(this);
|
||||
this.handleUpdate = this.handleUpdate.bind(this);
|
||||
this.handleRadioClick = this.handleRadioClick.bind(this);
|
||||
this.handleQuietToggle = this.handleQuietToggle.bind(this);
|
||||
this.createDesktopSection = this.createDesktopSection.bind(this);
|
||||
this.createQuietSection = this.createQuietSection.bind(this);
|
||||
|
||||
this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''};
|
||||
this.handleSubmitNotifyLevel = this.handleSubmitNotifyLevel.bind(this);
|
||||
this.handleUpdateNotifyLevel = this.handleUpdateNotifyLevel.bind(this);
|
||||
this.createNotifyLevelSection = this.createNotifyLevelSection.bind(this);
|
||||
|
||||
this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this);
|
||||
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
|
||||
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
|
||||
|
||||
this.state = {
|
||||
notifyLevel: '',
|
||||
markUnreadLevel: '',
|
||||
title: '',
|
||||
channelId: '',
|
||||
activeSection: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
ChannelStore.addChangeListener(this.onListenerChange);
|
||||
|
||||
@@ -30,33 +40,34 @@ export default class ChannelNotifications extends React.Component {
|
||||
var button = e.relatedTarget;
|
||||
var channelId = button.getAttribute('data-channelid');
|
||||
|
||||
var notifyLevel = ChannelStore.getMember(channelId).notify_level;
|
||||
var quietMode = false;
|
||||
const member = ChannelStore.getMember(channelId);
|
||||
var notifyLevel = member.notify_props.desktop;
|
||||
var markUnreadLevel = member.notify_props.mark_unread;
|
||||
|
||||
if (notifyLevel === 'quiet') {
|
||||
quietMode = true;
|
||||
}
|
||||
|
||||
this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId});
|
||||
this.setState({
|
||||
notifyLevel,
|
||||
markUnreadLevel,
|
||||
title: button.getAttribute('data-title'),
|
||||
channelId: channelId
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ChannelStore.removeChangeListener(this.onListenerChange);
|
||||
}
|
||||
|
||||
onListenerChange() {
|
||||
if (!this.state.channelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var notifyLevel = ChannelStore.getMember(this.state.channelId).notify_level;
|
||||
var quietMode = false;
|
||||
if (notifyLevel === 'quiet') {
|
||||
quietMode = true;
|
||||
}
|
||||
const member = ChannelStore.getMember(this.state.channelId);
|
||||
var notifyLevel = member.notify_props.desktop;
|
||||
var markUnreadLevel = member.notify_props.mark_unread;
|
||||
|
||||
var newState = this.state;
|
||||
newState.notifyLevel = notifyLevel;
|
||||
newState.quietMode = quietMode;
|
||||
newState.markUnreadLevel = markUnreadLevel;
|
||||
|
||||
if (!Utils.areStatesEqual(this.state, newState)) {
|
||||
this.setState(newState);
|
||||
@@ -65,53 +76,64 @@ export default class ChannelNotifications extends React.Component {
|
||||
updateSection(section) {
|
||||
this.setState({activeSection: section});
|
||||
}
|
||||
handleUpdate() {
|
||||
|
||||
handleSubmitNotifyLevel() {
|
||||
var channelId = this.state.channelId;
|
||||
var notifyLevel = this.state.notifyLevel;
|
||||
if (this.state.quietMode) {
|
||||
notifyLevel = 'quiet';
|
||||
|
||||
if (ChannelStore.getMember(channelId).notify_props.desktop === notifyLevel) {
|
||||
this.updateSection('');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {};
|
||||
data.channel_id = channelId;
|
||||
data.user_id = UserStore.getCurrentId();
|
||||
data.notify_level = notifyLevel;
|
||||
data.desktop = notifyLevel;
|
||||
|
||||
if (!data.notify_level || data.notify_level.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.updateNotifyLevel(data,
|
||||
function success() {
|
||||
Client.updateNotifyProps(data,
|
||||
() => {
|
||||
var member = ChannelStore.getMember(channelId);
|
||||
member.notify_level = notifyLevel;
|
||||
member.notify_props.desktop = notifyLevel;
|
||||
ChannelStore.setChannelMember(member);
|
||||
this.updateSection('');
|
||||
}.bind(this),
|
||||
function error(err) {
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
}
|
||||
handleRadioClick(notifyLevel) {
|
||||
this.setState({notifyLevel: notifyLevel, quietMode: false});
|
||||
|
||||
handleUpdateNotifyLevel(notifyLevel) {
|
||||
this.setState({notifyLevel});
|
||||
React.findDOMNode(this.refs.modal).focus();
|
||||
}
|
||||
handleQuietToggle(quietMode) {
|
||||
this.setState({notifyLevel: 'none', quietMode: quietMode});
|
||||
React.findDOMNode(this.refs.modal).focus();
|
||||
}
|
||||
createDesktopSection(serverError) {
|
||||
|
||||
createNotifyLevelSection(serverError) {
|
||||
var handleUpdateSection;
|
||||
|
||||
const user = UserStore.getCurrentUser();
|
||||
const globalNotifyLevel = user.notify_props.desktop;
|
||||
|
||||
let globalNotifyLevelName;
|
||||
if (globalNotifyLevel === 'all') {
|
||||
globalNotifyLevelName = 'For all activity';
|
||||
} else if (globalNotifyLevel === 'mention') {
|
||||
globalNotifyLevelName = 'Only for mentions';
|
||||
} else {
|
||||
globalNotifyLevelName = 'Never';
|
||||
}
|
||||
|
||||
if (this.state.activeSection === 'desktop') {
|
||||
var notifyActive = [false, false, false];
|
||||
if (this.state.notifyLevel === 'mention') {
|
||||
notifyActive[1] = true;
|
||||
} else if (this.state.notifyLevel === 'all') {
|
||||
var notifyActive = [false, false, false, false];
|
||||
if (this.state.notifyLevel === 'default') {
|
||||
notifyActive[0] = true;
|
||||
} else {
|
||||
} else if (this.state.notifyLevel === 'all') {
|
||||
notifyActive[1] = true;
|
||||
} else if (this.state.notifyLevel === 'mention') {
|
||||
notifyActive[2] = true;
|
||||
} else {
|
||||
notifyActive[3] = true;
|
||||
}
|
||||
|
||||
var inputs = [];
|
||||
@@ -123,9 +145,9 @@ export default class ChannelNotifications extends React.Component {
|
||||
<input
|
||||
type='radio'
|
||||
checked={notifyActive[0]}
|
||||
onChange={this.handleRadioClick.bind(this, 'all')}
|
||||
onChange={this.handleUpdateNotifyLevel.bind(this, 'default')}
|
||||
>
|
||||
For all activity
|
||||
{`Global default (${globalNotifyLevelName})`}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
@@ -135,9 +157,9 @@ export default class ChannelNotifications extends React.Component {
|
||||
<input
|
||||
type='radio'
|
||||
checked={notifyActive[1]}
|
||||
onChange={this.handleRadioClick.bind(this, 'mention')}
|
||||
onChange={this.handleUpdateNotifyLevel.bind(this, 'all')}
|
||||
>
|
||||
Only for mentions
|
||||
{'For all activity'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
@@ -147,9 +169,21 @@ export default class ChannelNotifications extends React.Component {
|
||||
<input
|
||||
type='radio'
|
||||
checked={notifyActive[2]}
|
||||
onChange={this.handleRadioClick.bind(this, 'none')}
|
||||
onChange={this.handleUpdateNotifyLevel.bind(this, 'mention')}
|
||||
>
|
||||
Never
|
||||
{'Only for mentions'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
checked={notifyActive[3]}
|
||||
onChange={this.handleUpdateNotifyLevel.bind(this, 'none')}
|
||||
>
|
||||
{'Never'}
|
||||
</input>
|
||||
</label>
|
||||
</div>
|
||||
@@ -162,30 +196,19 @@ export default class ChannelNotifications extends React.Component {
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
let curChannel = ChannelStore.get(this.state.channelId);
|
||||
let extraInfo = (
|
||||
const extraInfo = (
|
||||
<span>
|
||||
These settings will override the global notification settings.
|
||||
{'Selecting an option other than "Default" will override the global notification settings.'}
|
||||
<br/>
|
||||
Desktop notifications are available on Firefox, Safari, and Chrome.
|
||||
{'Desktop notifications are available on Firefox, Safari, and Chrome.'}
|
||||
</span>
|
||||
);
|
||||
|
||||
if (curChannel && curChannel.display_name) {
|
||||
extraInfo = (
|
||||
<span>
|
||||
These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel.
|
||||
<br/>
|
||||
Desktop notifications are available on Firefox, Safari, and Chrome.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title='Send desktop notifications'
|
||||
inputs={inputs}
|
||||
submit={this.handleUpdate}
|
||||
submit={this.handleSubmitNotifyLevel}
|
||||
server_error={serverError}
|
||||
updateSection={handleUpdateSection}
|
||||
extraInfo={extraInfo}
|
||||
@@ -194,7 +217,9 @@ export default class ChannelNotifications extends React.Component {
|
||||
}
|
||||
|
||||
var describe;
|
||||
if (this.state.notifyLevel === 'mention') {
|
||||
if (this.state.notifyLevel === 'default') {
|
||||
describe = `Global default (${globalNotifyLevelName})`;
|
||||
} else if (this.state.notifyLevel === 'mention') {
|
||||
describe = 'Only for mentions';
|
||||
} else if (this.state.notifyLevel === 'all') {
|
||||
describe = 'For all activity';
|
||||
@@ -215,101 +240,123 @@ export default class ChannelNotifications extends React.Component {
|
||||
/>
|
||||
);
|
||||
}
|
||||
createQuietSection(serverError) {
|
||||
var handleUpdateSection;
|
||||
if (this.state.activeSection === 'quiet') {
|
||||
var quietActive = [false, false];
|
||||
if (this.state.quietMode) {
|
||||
quietActive[0] = true;
|
||||
} else {
|
||||
quietActive[1] = true;
|
||||
|
||||
handleSubmitMarkUnreadLevel() {
|
||||
const channelId = this.state.channelId;
|
||||
const markUnreadLevel = this.state.markUnreadLevel;
|
||||
|
||||
if (ChannelStore.getMember(channelId).notify_props.mark_unread === markUnreadLevel) {
|
||||
this.updateSection('');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
channel_id: channelId,
|
||||
user_id: UserStore.getCurrentId(),
|
||||
mark_unread: markUnreadLevel
|
||||
};
|
||||
|
||||
Client.updateNotifyProps(data,
|
||||
() => {
|
||||
var member = ChannelStore.getMember(channelId);
|
||||
member.notify_props.mark_unread = markUnreadLevel;
|
||||
ChannelStore.setChannelMember(member);
|
||||
this.updateSection('');
|
||||
},
|
||||
(err) => {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var inputs = [];
|
||||
handleUpdateMarkUnreadLevel(markUnreadLevel) {
|
||||
this.setState({markUnreadLevel});
|
||||
React.findDOMNode(this.refs.modal).focus();
|
||||
}
|
||||
|
||||
inputs.push(
|
||||
createMarkUnreadLevelSection(serverError) {
|
||||
let content;
|
||||
|
||||
if (this.state.activeSection === 'markUnreadLevel') {
|
||||
const inputs = [(
|
||||
<div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
checked={quietActive[0]}
|
||||
onChange={this.handleQuietToggle.bind(this, true)}
|
||||
checked={this.state.markUnreadLevel === 'all'}
|
||||
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
|
||||
>
|
||||
On
|
||||
{'For all unread messages'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
<br />
|
||||
</div>
|
||||
<div className='radio'>
|
||||
<label>
|
||||
<input
|
||||
type='radio'
|
||||
checked={quietActive[1]}
|
||||
onChange={this.handleQuietToggle.bind(this, false)}
|
||||
checked={this.state.markUnreadLevel === 'mention'}
|
||||
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
|
||||
>
|
||||
Off
|
||||
{'Only for mentions'}
|
||||
</input>
|
||||
</label>
|
||||
<br/>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)];
|
||||
|
||||
inputs.push(
|
||||
<div>
|
||||
<br/>
|
||||
Enabling quiet mode will turn off desktop notifications and only mark the channel as unread if you have been mentioned.
|
||||
</div>
|
||||
);
|
||||
|
||||
handleUpdateSection = function updateSection(e) {
|
||||
const handleUpdateSection = function handleUpdateSection(e) {
|
||||
this.updateSection('');
|
||||
this.onListenerChange();
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
return (
|
||||
const extraInfo = <span>{'The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'}</span>;
|
||||
|
||||
content = (
|
||||
<SettingItemMax
|
||||
title='Quiet mode'
|
||||
title='Mark Channel Unread'
|
||||
inputs={inputs}
|
||||
submit={this.handleUpdate}
|
||||
submit={this.handleSubmitMarkUnreadLevel}
|
||||
server_error={serverError}
|
||||
updateSection={handleUpdateSection}
|
||||
extraInfo={extraInfo}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
let describe;
|
||||
|
||||
if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') {
|
||||
describe = 'For all unread messages';
|
||||
} else {
|
||||
describe = 'Only for mentions';
|
||||
}
|
||||
|
||||
const handleUpdateSection = function handleUpdateSection(e) {
|
||||
this.updateSection('markUnreadLevel');
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
content = (
|
||||
<SettingItemMin
|
||||
title='Mark Channel Unread'
|
||||
describe={describe}
|
||||
updateSection={handleUpdateSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
var describe;
|
||||
if (this.state.quietMode) {
|
||||
describe = 'On';
|
||||
} else {
|
||||
describe = 'Off';
|
||||
}
|
||||
|
||||
handleUpdateSection = function updateSection(e) {
|
||||
this.updateSection('quiet');
|
||||
e.preventDefault();
|
||||
}.bind(this);
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title='Quiet mode'
|
||||
describe={describe}
|
||||
updateSection={handleUpdateSection}
|
||||
/>
|
||||
);
|
||||
return content;
|
||||
}
|
||||
|
||||
render() {
|
||||
var serverError = null;
|
||||
if (this.state.serverError) {
|
||||
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
|
||||
}
|
||||
|
||||
var desktopSection = this.createDesktopSection(serverError);
|
||||
|
||||
var quietSection = this.createQuietSection(serverError);
|
||||
|
||||
return (
|
||||
<div
|
||||
className='modal fade'
|
||||
@@ -341,9 +388,9 @@ export default class ChannelNotifications extends React.Component {
|
||||
>
|
||||
<br/>
|
||||
<div className='divider-dark first'/>
|
||||
{desktopSection}
|
||||
{this.createNotifyLevelSection(serverError)}
|
||||
<div className='divider-light'/>
|
||||
{quietSection}
|
||||
{this.createMarkUnreadLevelSection(serverError)}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ function getCountsStateFromStores() {
|
||||
count += channel.total_msg_count - channelMember.msg_count;
|
||||
} else if (channelMember.mention_count > 0) {
|
||||
count += channelMember.mention_count;
|
||||
} else if (channelMember.notify_level !== 'quiet' && channel.total_msg_count - channelMember.msg_count > 0) {
|
||||
} else if (channelMember.notify_props.mark_unread !== 'mention' && channel.total_msg_count - channelMember.msg_count > 0) {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
|
||||
|
||||
var dropdown = this.createDropdown();
|
||||
|
||||
let tooltip = <Tooltip id={post.id + 'tooltip'}>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>;
|
||||
let tooltip = <Tooltip id={post.id + 'tooltip'}>{`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}</Tooltip>;
|
||||
|
||||
return (
|
||||
<ul className='post-header post-info'>
|
||||
|
||||
@@ -200,13 +200,17 @@ export default class Sidebar extends React.Component {
|
||||
}
|
||||
var channel = ChannelStore.get(msg.channel_id);
|
||||
|
||||
var user = UserStore.getCurrentUser();
|
||||
if (user.notify_props && ((user.notify_props.desktop === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') || user.notify_props.desktop === 'none')) {
|
||||
return;
|
||||
const user = UserStore.getCurrentUser();
|
||||
const member = ChannelStore.getMember(msg.channel_id);
|
||||
|
||||
var notifyLevel = member.notify_props.desktop;
|
||||
if (notifyLevel === 'default') {
|
||||
notifyLevel = user.notify_props.desktop;
|
||||
}
|
||||
|
||||
var member = ChannelStore.getMember(msg.channel_id);
|
||||
if ((member.notify_level === 'mention' && mentions.indexOf(user.id) === -1) || member.notify_level === 'none' || member.notify_level === 'quiet') {
|
||||
if (notifyLevel === 'none') {
|
||||
return;
|
||||
} else if (notifyLevel === 'mention' && mentions.indexOf(user.id) === -1 && channel.type !== 'D') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -330,7 +334,7 @@ export default class Sidebar extends React.Component {
|
||||
var unread = false;
|
||||
if (channelMember) {
|
||||
msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||
unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
|
||||
unread = (msgCount > 0 && channelMember.notify_props.mark_unread !== 'mention') || channelMember.mention_count > 0;
|
||||
}
|
||||
|
||||
var titleClass = '';
|
||||
|
||||
@@ -148,6 +148,8 @@ export default class ManageIncomingHooks extends React.Component {
|
||||
|
||||
return (
|
||||
<div key='addIncomingHook'>
|
||||
{'Create webhook URLs for channels and private groups. These URLs can be used by outside applications to create posts in any channels or private groups you have access to. The specified channel will be used as the default.'}
|
||||
<br/>
|
||||
<label className='control-label'>{'Add a new incoming webhook'}</label>
|
||||
<div className='padding-top'>
|
||||
<select
|
||||
|
||||
@@ -214,15 +214,14 @@ export default class UserSettingsAppearance extends React.Component {
|
||||
<div className='divider-dark first'/>
|
||||
{themeUI}
|
||||
<div className='divider-dark'/>
|
||||
<br/>
|
||||
<a
|
||||
href='#'
|
||||
className='theme'
|
||||
onClick={this.handleImportModal}
|
||||
>
|
||||
{'Import from Slack'}
|
||||
</a>
|
||||
</div>
|
||||
<br/>
|
||||
<a
|
||||
className='theme'
|
||||
onClick={this.handleImportModal}
|
||||
>
|
||||
{'Import theme colors from Slack'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ function getNotificationsStateFromStores() {
|
||||
if (user.notify_props && user.notify_props.desktop_sound) {
|
||||
sound = user.notify_props.desktop_sound;
|
||||
}
|
||||
var desktop = 'all';
|
||||
var desktop = 'default';
|
||||
if (user.notify_props && user.notify_props.desktop) {
|
||||
desktop = user.notify_props.desktop;
|
||||
}
|
||||
|
||||
@@ -152,21 +152,23 @@ export function getChannel(id) {
|
||||
}
|
||||
|
||||
export function updateLastViewedAt() {
|
||||
if (isCallInProgress('updateLastViewed')) {
|
||||
const channelId = ChannelStore.getCurrentId();
|
||||
|
||||
if (channelId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChannelStore.getCurrentId() == null) {
|
||||
if (isCallInProgress(`updateLastViewed${channelId}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker.updateLastViewed = utils.getTimestamp();
|
||||
callTracker[`updateLastViewed${channelId}`] = utils.getTimestamp();
|
||||
client.updateLastViewedAt(
|
||||
ChannelStore.getCurrentId(),
|
||||
function updateLastViewedAtSuccess() {
|
||||
channelId,
|
||||
() => {
|
||||
callTracker.updateLastViewed = 0;
|
||||
},
|
||||
function updateLastViewdAtFailure(err) {
|
||||
(err) => {
|
||||
callTracker.updateLastViewed = 0;
|
||||
dispatchError(err, 'updateLastViewedAt');
|
||||
}
|
||||
@@ -634,4 +636,4 @@ export function getMyTeam() {
|
||||
dispatchError(err, 'getMyTeam');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +332,20 @@ export function saveConfig(config, success, error) {
|
||||
});
|
||||
}
|
||||
|
||||
export function logClientError(msg) {
|
||||
var l = {};
|
||||
l.level = 'ERROR';
|
||||
l.message = msg;
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/log_client',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(l)
|
||||
});
|
||||
}
|
||||
|
||||
export function testEmail(config, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/test_email',
|
||||
@@ -568,16 +582,16 @@ export function updateChannelDesc(data, success, error) {
|
||||
track('api', 'api_channels_desc');
|
||||
}
|
||||
|
||||
export function updateNotifyLevel(data, success, error) {
|
||||
export function updateNotifyProps(data, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/channels/update_notify_level',
|
||||
url: '/api/v1/channels/update_notify_props',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateNotifyLevel', xhr, status, err);
|
||||
var e = handleError('updateNotifyProps', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -87,8 +87,10 @@ function autolinkUrls(text, tokens) {
|
||||
const linkText = match.getMatchedText();
|
||||
let url = linkText;
|
||||
|
||||
if (url.lastIndexOf('http', 0) !== 0) {
|
||||
url = `http://${linkText}`;
|
||||
if (match.getType() === 'email') {
|
||||
url = `mailto:${url}`;
|
||||
} else if (url.lastIndexOf('http', 0) !== 0) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
const index = tokens.size;
|
||||
|
||||
@@ -43,13 +43,32 @@
|
||||
<script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
|
||||
|
||||
<style id="antiClickjack">body{display:none !important;}</style>
|
||||
|
||||
<script>
|
||||
window.onerror = function(msg, url, line, column, stack) {
|
||||
var l = {};
|
||||
l.level = 'ERROR';
|
||||
l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/admin/log_client',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(l)
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/static/js/bundle.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (self === top) {
|
||||
var blocker = document.getElementById("antiClickjack");
|
||||
blocker.parentNode.removeChild(blocker);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (window.config.SegmentDeveloperKey != null && window.config.SegmentDeveloperKey !== "") {
|
||||
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
|
||||
|
||||
Reference in New Issue
Block a user