diff options
-rw-r--r-- | api/channel.go | 59 | ||||
-rw-r--r-- | api/channel_test.go | 11 | ||||
-rw-r--r-- | api/command.go | 2 | ||||
-rw-r--r-- | api/command_test.go | 2 | ||||
-rw-r--r-- | api/team.go | 21 | ||||
-rw-r--r-- | api/team_test.go | 16 | ||||
-rw-r--r-- | api/templates/invite_body.html | 2 | ||||
-rw-r--r-- | api/templates/post_body.html | 2 | ||||
-rw-r--r-- | api/user.go | 15 | ||||
-rw-r--r-- | manualtesting/manual_testing.go | 2 | ||||
-rw-r--r-- | utils/mail.go | 3 | ||||
-rw-r--r-- | web/react/components/file_preview.jsx | 2 | ||||
-rw-r--r-- | web/react/components/post_body.jsx | 2 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 13 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 1 | ||||
-rw-r--r-- | web/web.go | 2 |
16 files changed, 117 insertions, 38 deletions
diff --git a/api/channel.go b/api/channel.go index d3f6ca2de..c0c2d1548 100644 --- a/api/channel.go +++ b/api/channel.go @@ -57,7 +57,7 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - if sc, err := CreateChannel(c, channel, r.URL.Path, true); err != nil { + if sc, err := CreateChannel(c, channel, true); err != nil { c.Err = err return } else { @@ -65,7 +65,7 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { } } -func CreateChannel(c *Context, channel *model.Channel, path string, addMember bool) (*model.Channel, *model.AppError) { +func CreateChannel(c *Context, channel *model.Channel, addMember bool) (*model.Channel, *model.AppError) { if result := <-Srv.Store.Channel().Save(channel); result.Err != nil { return nil, result.Err } else { @@ -100,7 +100,7 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - if sc, err := CreateDirectChannel(c, userId, r.URL.Path); err != nil { + if sc, err := CreateDirectChannel(c, userId); err != nil { c.Err = err return } else { @@ -108,7 +108,7 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) { } } -func CreateDirectChannel(c *Context, otherUserId string, path string) (*model.Channel, *model.AppError) { +func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model.AppError) { if len(otherUserId) != 26 { return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId) } @@ -132,7 +132,7 @@ func CreateDirectChannel(c *Context, otherUserId string, path string) (*model.Ch return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId) } - if sc, err := CreateChannel(c, channel, path, true); err != nil { + if sc, err := CreateChannel(c, channel, true); err != nil { return nil, err } else { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, @@ -146,6 +146,23 @@ func CreateDirectChannel(c *Context, otherUserId string, path string) (*model.Ch } } +func CreateDefaultChannels(c *Context, teamId string) ([]*model.Channel, *model.AppError) { + townSquare := &model.Channel{DisplayName: "Town Square", Name: "town-square", Type: model.CHANNEL_OPEN, TeamId: teamId} + + if _, err := CreateChannel(c, townSquare, false); err != nil { + return nil, err + } + + offTopic := &model.Channel{DisplayName: "Off-Topic", Name: "off-topic", Type: model.CHANNEL_OPEN, TeamId: teamId} + + if _, err := CreateChannel(c, offTopic, false); err != nil { + return nil, err + } + + channels := []*model.Channel{townSquare, offTopic} + return channels, nil +} + func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { channel := model.ChannelFromJson(r.Body) @@ -303,7 +320,7 @@ func joinChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelId := params["id"] - JoinChannel(c, channelId, r.URL.Path) + JoinChannel(c, channelId, "") if c.Err != nil { return @@ -314,7 +331,7 @@ func joinChannel(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(result))) } -func JoinChannel(c *Context, channelId string, path string) { +func JoinChannel(c *Context, channelId string, role string) { sc := Srv.Store.Channel().Get(channelId) uc := Srv.Store.User().Get(c.Session.UserId) @@ -340,7 +357,7 @@ func JoinChannel(c *Context, channelId string, path string) { } if channel.Type == model.CHANNEL_OPEN { - cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_ALL} + cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: role} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err @@ -363,6 +380,32 @@ func JoinChannel(c *Context, channelId string, path string) { } } +func JoinDefaultChannels(c *Context, user *model.User, channelRole string) *model.AppError { + // We don't call JoinChannel here since c.Session is not populated on user creation + + var err *model.AppError = nil + + 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} + if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { + err = cmResult.Err + } + } + + 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} + if cmResult := <-Srv.Store.Channel().SaveMember(cm); cmResult.Err != nil { + err = cmResult.Err + } + } + + return err +} + func leaveChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) diff --git a/api/channel_test.go b/api/channel_test.go index 2e2e3683a..dfae840dc 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -35,8 +35,15 @@ func TestCreateChannel(t *testing.T) { } rget := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - if rget.Channels[0].Name != channel.Name { - t.Fatal("full name didn't match") + nameMatch := false + for _, c := range rget.Channels { + if c.Name == channel.Name { + nameMatch = true + } + } + + if !nameMatch { + t.Fatal("Did not create channel with correct name") } if _, err := Client.CreateChannel(rchannel.Data.(*model.Channel)); err == nil { diff --git a/api/command.go b/api/command.go index aedbe07cc..810a8a07e 100644 --- a/api/command.go +++ b/api/command.go @@ -197,7 +197,7 @@ func joinCommand(c *Context, command *model.Command) bool { return false } - JoinChannel(c, v.Id, "/command") + JoinChannel(c, v.Id, "") if c.Err != nil { return false diff --git a/api/command_test.go b/api/command_test.go index d3b0da455..5b7734628 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -129,7 +129,7 @@ func TestJoinCommands(t *testing.T) { c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) - if len(c1.Channels) != 3 { // 3 because of town-square and direct + if len(c1.Channels) != 4 { // 4 because of town-square, off-topic and direct t.Fatal("didn't join channel") } diff --git a/api/team.go b/api/team.go index 775bc29ae..15e4e2c17 100644 --- a/api/team.go +++ b/api/team.go @@ -146,10 +146,8 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { } else { rteam := result.Data.(*model.Team) - channel := &model.Channel{DisplayName: "Town Square", Name: "town-square", Type: model.CHANNEL_OPEN, TeamId: rteam.Id} - - if _, err := CreateChannel(c, channel, r.URL.Path, false); err != nil { - c.Err = err + if _, err := CreateDefaultChannels(c, rteam.Id); err != nil { + c.Err = nil return } @@ -197,10 +195,8 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { } else { rteam := result.Data.(*model.Team) - channel := &model.Channel{DisplayName: "Town Square", Name: "town-square", Type: model.CHANNEL_OPEN, TeamId: rteam.Id} - - if _, err := CreateChannel(c, channel, r.URL.Path, false); err != nil { - c.Err = err + if _, err := CreateDefaultChannels(c, rteam.Id); err != nil { + c.Err = nil return } @@ -488,12 +484,21 @@ func InviteMembers(team *model.Team, user *model.User, invites []string) { } else { sender = user.FullName } + + senderRole := "" + if strings.Contains(user.Roles, model.ROLE_ADMIN) || strings.Contains(user.Roles, model.ROLE_SYSTEM_ADMIN) { + senderRole = "administrator" + } else { + senderRole = "member" + } + subjectPage := NewServerTemplatePage("invite_subject", teamUrl) subjectPage.Props["SenderName"] = sender subjectPage.Props["TeamName"] = team.Name bodyPage := NewServerTemplatePage("invite_body", teamUrl) bodyPage.Props["TeamName"] = team.Name bodyPage.Props["SenderName"] = sender + bodyPage.Props["SenderStatus"] = senderRole bodyPage.Props["Email"] = invite diff --git a/api/team_test.go b/api/team_test.go index 042c0a2e9..bb77d43a0 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -55,6 +55,11 @@ func TestCreateFromSignupTeam(t *testing.T) { } } + c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) + if len(c1.Channels) != 2 { + t.Fatal("default channels not created") + } + ts.Data = "garbage" _, err = Client.CreateTeamFromSignup(&ts) if err == nil { @@ -71,6 +76,17 @@ func TestCreateTeam(t *testing.T) { t.Fatal(err) } + user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) + Srv.Store.User().VerifyEmail(user.Id) + + Client.LoginByEmail(team.Domain, user.Email, "pwd") + + c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) + if len(c1.Channels) != 2 { + t.Fatal("default channels not created") + } + if rteam.Data.(*model.Team).Name != team.Name { t.Fatal("full name didn't match") } diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html index 06f48759c..8be2ac0df 100644 --- a/api/templates/invite_body.html +++ b/api/templates/invite_body.html @@ -18,7 +18,7 @@ <tr> <td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;"> <h2 style="font-weight: normal; margin-top: 10px;">You've been invited</h2> - <p>{{.Props.TeamName}} started using {{.SiteName}}.<br> The team administrator <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamName}}</strong>.</p> + <p>{{.Props.TeamName}} started using {{.SiteName}}.<br> The team {{.Props.SenderStatus}} <strong>{{.Props.SenderName}}</strong>, has invited you to join <strong>{{.Props.TeamName}}</strong>.</p> <p style="margin: 20px 0 15px"> <a href="{{.Props.Link}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Join Team</a> </p> diff --git a/api/templates/post_body.html b/api/templates/post_body.html index 663ec66d2..069cdf1fb 100644 --- a/api/templates/post_body.html +++ b/api/templates/post_body.html @@ -20,7 +20,7 @@ <h2 style="font-weight: normal; margin-top: 10px;">You were mentioned</h2> <p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} GMT, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif;">{{.Props.PostMessage}}</pre></p> <p style="margin: 20px 0 15px"> - <a href="{{.Props.TeamLink}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Go To Channel</a> + <a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Go To Channel</a> </p> </td> </tr> diff --git a/api/user.go b/api/user.go index f8382cf2f..292d2b61b 100644 --- a/api/user.go +++ b/api/user.go @@ -176,21 +176,16 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User { } else { ruser := result.Data.(*model.User) - // Do not error if user cannot be added to the town-square channel - if cresult := <-Srv.Store.Channel().GetByName(team.Id, "town-square"); cresult.Err != nil { - l4g.Error("Failed to get town-square err=%v", cresult.Err) - } else { - cm := &model.ChannelMember{ChannelId: cresult.Data.(*model.Channel).Id, UserId: ruser.Id, NotifyLevel: model.CHANNEL_NOTIFY_ALL, Roles: channelRole} - if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { - l4g.Error("Failed to add member town-square err=%v", cmresult.Err) - } + // Soft error if there is an issue joining the default channels + if err := JoinDefaultChannels(c, ruser, channelRole); err != nil { + l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err) } //fireAndForgetWelcomeEmail(strings.Split(ruser.FullName, " ")[0], ruser.Email, team.Name, c.TeamUrl+"/channels/town-square") if user.EmailVerified { if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { - l4g.Error("Failed to get town-square err=%v", cresult.Err) + l4g.Error("Failed to set email verified err=%v", cresult.Err) } } else { FireAndForgetVerifyEmail(result.Data.(*model.User).Id, strings.Split(ruser.FullName, " ")[0], ruser.Email, team.Name, c.TeamUrl) @@ -198,7 +193,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User { ruser.Sanitize(map[string]bool{}) - //This message goes to every channel, so the channelId is irrelevant + // This message goes to every channel, so the channelId is irrelevant message := model.NewMessage(team.Id, "", ruser.Id, model.ACTION_NEW_USER) store.PublishAndForget(message) diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index ead441108..929f7ab5d 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -78,7 +78,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { createdTeam := result.Data.(*model.Team) channel := &model.Channel{DisplayName: "Town Square", Name: "town-square", Type: model.CHANNEL_OPEN, TeamId: createdTeam.Id} - if _, err := api.CreateChannel(c, channel, r.URL.Path, false); err != nil { + if _, err := api.CreateChannel(c, channel, false); err != nil { c.Err = err return } diff --git a/utils/mail.go b/utils/mail.go index 2fb7f801d..3cd37ffef 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -11,6 +11,7 @@ import ( "net" "net/mail" "net/smtp" + "html" ) func CheckMailSettings() *model.AppError { @@ -84,7 +85,7 @@ func SendMail(to, subject, body string) *model.AppError { headers := make(map[string]string) headers["From"] = fromMail.String() headers["To"] = toMail.String() - headers["Subject"] = subject + headers["Subject"] = html.UnescapeString(subject) headers["MIME-version"] = "1.0" headers["Content-Type"] = "text/html" diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index 99327c22f..17a1e2bc2 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -10,7 +10,7 @@ var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ handleRemove: function(e) { var previewDiv = e.target.parentNode.parentNode; - this.props.onRemove(previewDiv.dataset.filename); + this.props.onRemove(previewDiv.getAttribute('data-filename')); }, render: function() { var previews = []; diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 3079917ec..7d5ef4d33 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -102,7 +102,7 @@ module.exports = React.createClass({ images.push(filenames[i]); } else if (i < Constants.MAX_DISPLAY_FILES) { postFiles.push( - <div className="post-image__column custom-file" key={fileInfo.name}> + <div className="post-image__column custom-file" key={fileInfo.name+i}> <a href={fileInfo.path+"."+fileInfo.ext} download={fileInfo.name+"."+fileInfo.ext}> <div className={"file-icon "+utils.getIconClassName(type)}/> </a> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 37e3faef2..e37de8d88 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -356,6 +356,7 @@ module.exports = React.createClass({ var ui_name = channel.display_name var members = ChannelStore.getCurrentExtraInfo().members; var creator_name = ""; + var userStyle = { color: UserStore.getCurrentUser().props.theme } for (var i = 0; i < members.length; i++) { if (members[i].roles.indexOf('admin') > -1) { @@ -382,8 +383,18 @@ module.exports = React.createClass({ </p> </div> ); + } else if (channel.name === Constants.OFFTOPIC_CHANNEL) { + more_messages = ( + <div className="channel-intro"> + <h4 className="channel-intro-title">Welcome</h4> + <p> + {"This is the start of " + ui_name + ", a channel for conversations you’d prefer out of more focused channels."} + <br/> + <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={ui_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a> + </p> + </div> + ); } else { - var userStyle = { color: UserStore.getCurrentUser().props.theme } var ui_type = channel.type === 'P' ? "private group" : "channel"; more_messages = ( <div className="channel-intro"> diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index f3f3399d9..3aadfb4b0 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -52,6 +52,7 @@ module.exports = { MAX_UPLOAD_FILES: 5, MAX_FILE_SIZE: 50000000, // 50 MB DEFAULT_CHANNEL: 'town-square', + OFFTOPIC_CHANNEL: 'off-topic', POST_CHUNK_SIZE: 60, RESERVED_DOMAINS: [ "www", diff --git a/web/web.go b/web/web.go index 7357124b5..443a75916 100644 --- a/web/web.go +++ b/web/web.go @@ -285,7 +285,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { otherUserId = ids[0] } - if sc, err := api.CreateDirectChannel(c, otherUserId, r.URL.Path); err != nil { + if sc, err := api.CreateDirectChannel(c, otherUserId); err != nil { api.Handle404(w, r) return } else { |