Merge pull request #1199 from hmhealey/plt600
PLT-600 Renamed channel description to channel header and added channel purpose field
Этот коммит содержится в:
@@ -22,7 +22,8 @@ func InitChannel(r *mux.Router) {
|
|||||||
sr.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
|
sr.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
|
||||||
sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
||||||
sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
||||||
sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST")
|
sr.Handle("/update_header", ApiUserRequired(updateChannelHeader)).Methods("POST")
|
||||||
|
sr.Handle("/update_purpose", ApiUserRequired(updateChannelPurpose)).Methods("POST")
|
||||||
sr.Handle("/update_notify_props", ApiUserRequired(updateNotifyProps)).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]+}/", 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]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
||||||
@@ -124,7 +125,7 @@ func CreateDirectChannel(c *Context, otherUserId string) (*model.Channel, *model
|
|||||||
channel.Name = model.GetDMNameFromIds(otherUserId, c.Session.UserId)
|
channel.Name = model.GetDMNameFromIds(otherUserId, c.Session.UserId)
|
||||||
|
|
||||||
channel.TeamId = c.Session.TeamId
|
channel.TeamId = c.Session.TeamId
|
||||||
channel.Description = ""
|
channel.Header = ""
|
||||||
channel.Type = model.CHANNEL_DIRECT
|
channel.Type = model.CHANNEL_DIRECT
|
||||||
|
|
||||||
if uresult := <-uc; uresult.Err != nil {
|
if uresult := <-uc; uresult.Err != nil {
|
||||||
@@ -209,7 +210,8 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldChannel.Description = channel.Description
|
oldChannel.Header = channel.Header
|
||||||
|
oldChannel.Purpose = channel.Purpose
|
||||||
|
|
||||||
if len(channel.DisplayName) > 0 {
|
if len(channel.DisplayName) > 0 {
|
||||||
oldChannel.DisplayName = channel.DisplayName
|
oldChannel.DisplayName = channel.DisplayName
|
||||||
@@ -233,18 +235,18 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateChannelDesc(c *Context, w http.ResponseWriter, r *http.Request) {
|
func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
props := model.MapFromJson(r.Body)
|
props := model.MapFromJson(r.Body)
|
||||||
channelId := props["channel_id"]
|
channelId := props["channel_id"]
|
||||||
if len(channelId) != 26 {
|
if len(channelId) != 26 {
|
||||||
c.SetInvalidParam("updateChannelDesc", "channel_id")
|
c.SetInvalidParam("updateChannelHeader", "channel_id")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channelDesc := props["channel_description"]
|
channelHeader := props["channel_header"]
|
||||||
if len(channelDesc) > 1024 {
|
if len(channelHeader) > 1024 {
|
||||||
c.SetInvalidParam("updateChannelDesc", "channel_description")
|
c.SetInvalidParam("updateChannelHeader", "channel_header")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,11 +263,54 @@ func updateChannelDesc(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
channel := cresult.Data.(*model.Channel)
|
channel := cresult.Data.(*model.Channel)
|
||||||
// Don't need to do anything channel member, just wanted to confirm it exists
|
// Don't need to do anything channel member, just wanted to confirm it exists
|
||||||
|
|
||||||
if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelDesc") {
|
if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelHeader") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.Description = channelDesc
|
channel.Header = channelHeader
|
||||||
|
|
||||||
|
if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil {
|
||||||
|
c.Err = ucresult.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.LogAudit("name=" + channel.Name)
|
||||||
|
w.Write([]byte(channel.ToJson()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := model.MapFromJson(r.Body)
|
||||||
|
channelId := props["channel_id"]
|
||||||
|
if len(channelId) != 26 {
|
||||||
|
c.SetInvalidParam("updateChannelPurpose", "channel_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelPurpose := props["channel_purpose"]
|
||||||
|
if len(channelPurpose) > 1024 {
|
||||||
|
c.SetInvalidParam("updateChannelPurpose", "channel_purpose")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := Srv.Store.Channel().Get(channelId)
|
||||||
|
cmc := Srv.Store.Channel().GetMember(channelId, c.Session.UserId)
|
||||||
|
|
||||||
|
if cresult := <-sc; cresult.Err != nil {
|
||||||
|
c.Err = cresult.Err
|
||||||
|
return
|
||||||
|
} else if cmcresult := <-cmc; cmcresult.Err != nil {
|
||||||
|
c.Err = cmcresult.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
channel := cresult.Data.(*model.Channel)
|
||||||
|
// Don't need to do anything channel member, just wanted to confirm it exists
|
||||||
|
|
||||||
|
if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelPurpose") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.Purpose = channelPurpose
|
||||||
|
|
||||||
if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil {
|
if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil {
|
||||||
c.Err = ucresult.Err
|
c.Err = ucresult.Err
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ func BenchmarkCreateDirectChannel(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkUpdateChannel(b *testing.B) {
|
func BenchmarkUpdateChannel(b *testing.B) {
|
||||||
var (
|
var (
|
||||||
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
|
NUM_CHANNELS_RANGE = utils.Range{NUM_CHANNELS, NUM_CHANNELS}
|
||||||
CHANNEL_DESCRIPTION_LEN = 50
|
CHANNEL_HEADER_LEN = 50
|
||||||
)
|
)
|
||||||
team, _, _ := SetupBenchmark()
|
team, _, _ := SetupBenchmark()
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func BenchmarkUpdateChannel(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range channels {
|
for i := range channels {
|
||||||
channels[i].Description = utils.RandString(CHANNEL_DESCRIPTION_LEN, utils.ALPHANUMERIC)
|
channels[i].Header = utils.RandString(CHANNEL_HEADER_LEN, utils.ALPHANUMERIC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark Start
|
// Benchmark Start
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -173,12 +174,17 @@ func TestUpdateChannel(t *testing.T) {
|
|||||||
|
|
||||||
Client.AddChannelMember(channel1.Id, userTeamAdmin.Id)
|
Client.AddChannelMember(channel1.Id, userTeamAdmin.Id)
|
||||||
|
|
||||||
desc := "a" + model.NewId() + "a"
|
header := "a" + model.NewId() + "a"
|
||||||
upChannel1 := &model.Channel{Id: channel1.Id, Description: desc}
|
purpose := "a" + model.NewId() + "a"
|
||||||
|
upChannel1 := &model.Channel{Id: channel1.Id, Header: header, Purpose: purpose}
|
||||||
upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel)
|
upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
if upChannel1.Description != desc {
|
if upChannel1.Header != header {
|
||||||
t.Fatal("Channel admin failed to update desc")
|
t.Fatal("Channel admin failed to update header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if upChannel1.Purpose != purpose {
|
||||||
|
t.Fatal("Channel admin failed to update purpose")
|
||||||
}
|
}
|
||||||
|
|
||||||
if upChannel1.DisplayName != channel1.DisplayName {
|
if upChannel1.DisplayName != channel1.DisplayName {
|
||||||
@@ -187,12 +193,17 @@ func TestUpdateChannel(t *testing.T) {
|
|||||||
|
|
||||||
Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd")
|
Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd")
|
||||||
|
|
||||||
desc = "b" + model.NewId() + "b"
|
header = "b" + model.NewId() + "b"
|
||||||
upChannel1 = &model.Channel{Id: channel1.Id, Description: desc}
|
purpose = "b" + model.NewId() + "b"
|
||||||
|
upChannel1 = &model.Channel{Id: channel1.Id, Header: header, Purpose: purpose}
|
||||||
upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel)
|
upChannel1 = Client.Must(Client.UpdateChannel(upChannel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
if upChannel1.Description != desc {
|
if upChannel1.Header != header {
|
||||||
t.Fatal("Team admin failed to update desc")
|
t.Fatal("Team admin failed to update header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if upChannel1.Purpose != purpose {
|
||||||
|
t.Fatal("Team admin failed to update purpose")
|
||||||
}
|
}
|
||||||
|
|
||||||
if upChannel1.DisplayName != channel1.DisplayName {
|
if upChannel1.DisplayName != channel1.DisplayName {
|
||||||
@@ -203,7 +214,7 @@ func TestUpdateChannel(t *testing.T) {
|
|||||||
data := rget.Data.(*model.ChannelList)
|
data := rget.Data.(*model.ChannelList)
|
||||||
for _, c := range data.Channels {
|
for _, c := range data.Channels {
|
||||||
if c.Name == model.DEFAULT_CHANNEL {
|
if c.Name == model.DEFAULT_CHANNEL {
|
||||||
c.Description = "new desc"
|
c.Header = "new header"
|
||||||
if _, err := Client.UpdateChannel(c); err == nil {
|
if _, err := Client.UpdateChannel(c); err == nil {
|
||||||
t.Fatal("should have errored on updating default channel")
|
t.Fatal("should have errored on updating default channel")
|
||||||
}
|
}
|
||||||
@@ -218,7 +229,7 @@ func TestUpdateChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateChannelDesc(t *testing.T) {
|
func TestUpdateChannelHeader(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
@@ -235,36 +246,33 @@ func TestUpdateChannelDesc(t *testing.T) {
|
|||||||
|
|
||||||
data := make(map[string]string)
|
data := make(map[string]string)
|
||||||
data["channel_id"] = channel1.Id
|
data["channel_id"] = channel1.Id
|
||||||
data["channel_description"] = "new desc"
|
data["channel_header"] = "new header"
|
||||||
|
|
||||||
var upChannel1 *model.Channel
|
var upChannel1 *model.Channel
|
||||||
if result, err := Client.UpdateChannelDesc(data); err != nil {
|
if result, err := Client.UpdateChannelHeader(data); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
upChannel1 = result.Data.(*model.Channel)
|
upChannel1 = result.Data.(*model.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if upChannel1.Description != data["channel_description"] {
|
if upChannel1.Header != data["channel_header"] {
|
||||||
t.Fatal("Failed to update desc")
|
t.Fatal("Failed to update header")
|
||||||
}
|
}
|
||||||
|
|
||||||
data["channel_id"] = "junk"
|
data["channel_id"] = "junk"
|
||||||
if _, err := Client.UpdateChannelDesc(data); err == nil {
|
if _, err := Client.UpdateChannelHeader(data); err == nil {
|
||||||
t.Fatal("should have errored on junk channel id")
|
t.Fatal("should have errored on junk channel id")
|
||||||
}
|
}
|
||||||
|
|
||||||
data["channel_id"] = "12345678901234567890123456"
|
data["channel_id"] = "12345678901234567890123456"
|
||||||
if _, err := Client.UpdateChannelDesc(data); err == nil {
|
if _, err := Client.UpdateChannelHeader(data); err == nil {
|
||||||
t.Fatal("should have errored on non-existent channel id")
|
t.Fatal("should have errored on non-existent channel id")
|
||||||
}
|
}
|
||||||
|
|
||||||
data["channel_id"] = channel1.Id
|
data["channel_id"] = channel1.Id
|
||||||
data["channel_description"] = ""
|
data["channel_header"] = strings.Repeat("a", 1050)
|
||||||
for i := 0; i < 1050; i++ {
|
if _, err := Client.UpdateChannelHeader(data); err == nil {
|
||||||
data["channel_description"] += "a"
|
t.Fatal("should have errored on bad channel header")
|
||||||
}
|
|
||||||
if _, err := Client.UpdateChannelDesc(data); err == nil {
|
|
||||||
t.Fatal("should have errored on bad channel desc")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
@@ -274,9 +282,68 @@ func TestUpdateChannelDesc(t *testing.T) {
|
|||||||
Client.LoginByEmail(team.Name, user2.Email, "pwd")
|
Client.LoginByEmail(team.Name, user2.Email, "pwd")
|
||||||
|
|
||||||
data["channel_id"] = channel1.Id
|
data["channel_id"] = channel1.Id
|
||||||
data["channel_description"] = "new desc"
|
data["channel_header"] = "new header"
|
||||||
if _, err := Client.UpdateChannelDesc(data); err == nil {
|
if _, err := Client.UpdateChannelHeader(data); err == nil {
|
||||||
t.Fatal("should have errored non-channel member trying to update desc")
|
t.Fatal("should have errored non-channel member trying to update header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateChannelPurpose(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||||
|
|
||||||
|
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
data := make(map[string]string)
|
||||||
|
data["channel_id"] = channel1.Id
|
||||||
|
data["channel_purpose"] = "new purpose"
|
||||||
|
|
||||||
|
var upChannel1 *model.Channel
|
||||||
|
if result, err := Client.UpdateChannelPurpose(data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
upChannel1 = result.Data.(*model.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if upChannel1.Purpose != data["channel_purpose"] {
|
||||||
|
t.Fatal("Failed to update purpose")
|
||||||
|
}
|
||||||
|
|
||||||
|
data["channel_id"] = "junk"
|
||||||
|
if _, err := Client.UpdateChannelPurpose(data); err == nil {
|
||||||
|
t.Fatal("should have errored on junk channel id")
|
||||||
|
}
|
||||||
|
|
||||||
|
data["channel_id"] = "12345678901234567890123456"
|
||||||
|
if _, err := Client.UpdateChannelPurpose(data); err == nil {
|
||||||
|
t.Fatal("should have errored on non-existent channel id")
|
||||||
|
}
|
||||||
|
|
||||||
|
data["channel_id"] = channel1.Id
|
||||||
|
data["channel_purpose"] = strings.Repeat("a", 150)
|
||||||
|
if _, err := Client.UpdateChannelPurpose(data); err == nil {
|
||||||
|
t.Fatal("should have errored on bad channel purpose")
|
||||||
|
}
|
||||||
|
|
||||||
|
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user2.Email, "pwd")
|
||||||
|
|
||||||
|
data["channel_id"] = channel1.Id
|
||||||
|
data["channel_purpose"] = "new purpose"
|
||||||
|
if _, err := Client.UpdateChannelPurpose(data); err == nil {
|
||||||
|
t.Fatal("should have errored non-channel member trying to update purpose")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[str
|
|||||||
Type: model.CHANNEL_OPEN,
|
Type: model.CHANNEL_OPEN,
|
||||||
DisplayName: sChannel.Name,
|
DisplayName: sChannel.Name,
|
||||||
Name: SlackConvertChannelName(sChannel.Name),
|
Name: SlackConvertChannelName(sChannel.Name),
|
||||||
Description: sChannel.Topic["value"],
|
Purpose: sChannel.Topic["value"],
|
||||||
}
|
}
|
||||||
mChannel := ImportChannel(&newChannel)
|
mChannel := ImportChannel(&newChannel)
|
||||||
if mChannel == nil {
|
if mChannel == nil {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ type Channel struct {
|
|||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Header string `json:"header"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
LastPostAt int64 `json:"last_post_at"`
|
LastPostAt int64 `json:"last_post_at"`
|
||||||
TotalMsgCount int64 `json:"total_msg_count"`
|
TotalMsgCount int64 `json:"total_msg_count"`
|
||||||
ExtraUpdateAt int64 `json:"extra_update_at"`
|
ExtraUpdateAt int64 `json:"extra_update_at"`
|
||||||
@@ -89,8 +90,12 @@ func (o *Channel) IsValid() *AppError {
|
|||||||
return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id)
|
return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(o.Description) > 1024 {
|
if len(o.Header) > 1024 {
|
||||||
return NewAppError("Channel.IsValid", "Invalid description", "id="+o.Id)
|
return NewAppError("Channel.IsValid", "Invalid header", "id="+o.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(o.Purpose) > 128 {
|
||||||
|
return NewAppError("Channel.IsValid", "Invalid purpose", "id="+o.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(o.CreatorId) > 26 {
|
if len(o.CreatorId) > 26 {
|
||||||
|
|||||||
@@ -67,6 +67,26 @@ func TestChannelIsValid(t *testing.T) {
|
|||||||
if err := o.IsValid(); err != nil {
|
if err := o.IsValid(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
o.Header = strings.Repeat("01234567890", 100)
|
||||||
|
if err := o.IsValid(); err == nil {
|
||||||
|
t.Fatal("should be invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Header = "1234"
|
||||||
|
if err := o.IsValid(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Purpose = strings.Repeat("01234567890", 20)
|
||||||
|
if err := o.IsValid(); err == nil {
|
||||||
|
t.Fatal("should be invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Purpose = "1234"
|
||||||
|
if err := o.IsValid(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelPreSave(t *testing.T) {
|
func TestChannelPreSave(t *testing.T) {
|
||||||
|
|||||||
@@ -452,8 +452,17 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) {
|
func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError) {
|
||||||
if r, err := c.DoApiPost("/channels/update_desc", MapToJson(data)); err != nil {
|
if r, err := c.DoApiPost("/channels/update_header", MapToJson(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiPost("/channels/update_purpose", MapToJson(data)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ func NewSqlChannelStore(sqlStore *SqlStore) ChannelStore {
|
|||||||
table.ColMap("DisplayName").SetMaxSize(64)
|
table.ColMap("DisplayName").SetMaxSize(64)
|
||||||
table.ColMap("Name").SetMaxSize(64)
|
table.ColMap("Name").SetMaxSize(64)
|
||||||
table.SetUniqueTogether("Name", "TeamId")
|
table.SetUniqueTogether("Name", "TeamId")
|
||||||
table.ColMap("Description").SetMaxSize(1024)
|
table.ColMap("Header").SetMaxSize(1024)
|
||||||
|
table.ColMap("Purpose").SetMaxSize(128)
|
||||||
table.ColMap("CreatorId").SetMaxSize(26)
|
table.ColMap("CreatorId").SetMaxSize(26)
|
||||||
|
|
||||||
tablem := db.AddTableWithName(model.ChannelMember{}, "ChannelMembers").SetKeys(false, "ChannelId", "UserId")
|
tablem := db.AddTableWithName(model.ChannelMember{}, "ChannelMembers").SetKeys(false, "ChannelId", "UserId")
|
||||||
@@ -83,6 +84,11 @@ func (s SqlChannelStore) UpgradeSchemaIfNeeded() {
|
|||||||
|
|
||||||
s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel")
|
s.RemoveColumnIfExists("ChannelMembers", "NotifyLevel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BEGIN REMOVE AFTER 1.2.0
|
||||||
|
s.RenameColumnIfExists("Channels", "Description", "Header", "varchar(1024)")
|
||||||
|
s.CreateColumnIfNotExists("Channels", "Purpose", "varchar(1024)", "varchar(1024)", "")
|
||||||
|
// END REMOVE AFTER 1.2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlChannelStore) CreateIndexesIfNotExists() {
|
func (s SqlChannelStore) CreateIndexesIfNotExists() {
|
||||||
|
|||||||
@@ -364,27 +364,26 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
|
func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
|
||||||
|
if !ss.DoesColumnExist(tableName, oldColumnName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// // XXX TODO FIXME this should be removed after 0.6.0
|
var err error
|
||||||
// if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||||
// return false
|
_, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType)
|
||||||
// }
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
|
_, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " RENAME COLUMN " + oldColumnName + " TO " + newColumnName)
|
||||||
|
}
|
||||||
|
|
||||||
// if !ss.DoesColumnExist(tableName, oldColumnName) {
|
if err != nil {
|
||||||
// return false
|
l4g.Critical("Failed to rename column %v", err)
|
||||||
// }
|
time.Sleep(time.Second)
|
||||||
|
panic("Failed to drop column " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType)
|
return true
|
||||||
|
}
|
||||||
// if err != nil {
|
|
||||||
// l4g.Critical("Failed to rename column %v", err)
|
|
||||||
// time.Sleep(time.Second)
|
|
||||||
// panic("Failed to drop column " + err.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (ss SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) {
|
func (ss SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) {
|
||||||
ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT)
|
ss.createIndexIfNotExists(indexName, tableName, columnName, INDEX_TYPE_DEFAULT)
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ export default class AccessHistoryModal extends React.Component {
|
|||||||
case '/channels/update':
|
case '/channels/update':
|
||||||
currentAuditDesc = 'Updated the ' + channelName + ' channel/group name';
|
currentAuditDesc = 'Updated the ' + channelName + ' channel/group name';
|
||||||
break;
|
break;
|
||||||
case '/channels/update_desc':
|
case '/channels/update_desc': // support the old path
|
||||||
currentAuditDesc = 'Updated the ' + channelName + ' channel/group description';
|
case '/channels/update_header':
|
||||||
|
currentAuditDesc = 'Updated the ' + channelName + ' channel/group header';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
let userIdField = [];
|
let userIdField = [];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const TextFormatting = require('../utils/text_formatting.jsx');
|
|||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
const MessageWrapper = require('./message_wrapper.jsx');
|
const MessageWrapper = require('./message_wrapper.jsx');
|
||||||
const PopoverListMembers = require('./popover_list_members.jsx');
|
const PopoverListMembers = require('./popover_list_members.jsx');
|
||||||
|
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
|
||||||
|
|
||||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||||
const Constants = require('../utils/constants.jsx');
|
const Constants = require('../utils/constants.jsx');
|
||||||
@@ -27,7 +28,9 @@ export default class ChannelHeader extends React.Component {
|
|||||||
this.handleLeave = this.handleLeave.bind(this);
|
this.handleLeave = this.handleLeave.bind(this);
|
||||||
this.searchMentions = this.searchMentions.bind(this);
|
this.searchMentions = this.searchMentions.bind(this);
|
||||||
|
|
||||||
this.state = this.getStateFromStores();
|
const state = this.getStateFromStores();
|
||||||
|
state.showEditChannelPurposeModal = false;
|
||||||
|
this.state = state;
|
||||||
}
|
}
|
||||||
getStateFromStores() {
|
getStateFromStores() {
|
||||||
return {
|
return {
|
||||||
@@ -110,11 +113,11 @@ export default class ChannelHeader extends React.Component {
|
|||||||
bSize='large'
|
bSize='large'
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
className='description'
|
className='description'
|
||||||
onMouseOver={() => this.refs.descriptionOverlay.show()}
|
onMouseOver={() => this.refs.headerOverlay.show()}
|
||||||
onMouseOut={() => this.refs.descriptionOverlay.hide()}
|
onMouseOut={() => this.refs.headerOverlay.hide()}
|
||||||
>
|
>
|
||||||
<MessageWrapper
|
<MessageWrapper
|
||||||
message={channel.description}
|
message={channel.header}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
@@ -144,7 +147,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
dropdownContents.push(
|
dropdownContents.push(
|
||||||
<li
|
<li
|
||||||
key='edit_description_direct'
|
key='edit_header_direct'
|
||||||
role='presentation'
|
role='presentation'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -152,11 +155,11 @@ export default class ChannelHeader extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
Set Channel Description...
|
Set Channel Header...
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
@@ -216,7 +219,7 @@ export default class ChannelHeader extends React.Component {
|
|||||||
|
|
||||||
dropdownContents.push(
|
dropdownContents.push(
|
||||||
<li
|
<li
|
||||||
key='set_channel_description'
|
key='set_channel_header'
|
||||||
role='presentation'
|
role='presentation'
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -224,11 +227,25 @@ export default class ChannelHeader extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
Set {channelTerm} Description...
|
Set {channelTerm} Header...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
dropdownContents.push(
|
||||||
|
<li
|
||||||
|
key='set_channel_purpose'
|
||||||
|
role='presentation'
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
onClick={() => this.setState({showEditChannelPurposeModal: true})}
|
||||||
|
>
|
||||||
|
Set {channelTerm} Purpose...
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
@@ -307,84 +324,91 @@ export default class ChannelHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className='channel-header alt'>
|
<div>
|
||||||
<tbody>
|
<table className='channel-header alt'>
|
||||||
<tr>
|
<tbody>
|
||||||
<th>
|
<tr>
|
||||||
<div className='channel-header__info'>
|
<th>
|
||||||
<div className='dropdown'>
|
<div className='channel-header__info'>
|
||||||
|
<div className='dropdown'>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className='dropdown-toggle theme'
|
||||||
|
type='button'
|
||||||
|
id='channel_header_dropdown'
|
||||||
|
data-toggle='dropdown'
|
||||||
|
aria-expanded='true'
|
||||||
|
>
|
||||||
|
<strong className='heading'>{channelTitle} </strong>
|
||||||
|
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
className='dropdown-menu'
|
||||||
|
role='menu'
|
||||||
|
aria-labelledby='channel_header_dropdown'
|
||||||
|
>
|
||||||
|
{dropdownContents}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<OverlayTrigger
|
||||||
|
trigger={['hover', 'focus']}
|
||||||
|
placement='bottom'
|
||||||
|
overlay={popoverContent}
|
||||||
|
ref='headerOverlay'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={TextFormatting.handleClick}
|
||||||
|
className='description'
|
||||||
|
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false})}}
|
||||||
|
/>
|
||||||
|
</OverlayTrigger>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<PopoverListMembers
|
||||||
|
members={this.state.users}
|
||||||
|
channelId={channel.id}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th className='search-bar__container'><NavbarSearchBox /></th>
|
||||||
|
<th>
|
||||||
|
<div className='dropdown channel-header__links'>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
className='dropdown-toggle theme'
|
className='dropdown-toggle theme'
|
||||||
type='button'
|
type='button'
|
||||||
id='channel_header_dropdown'
|
id='channel_header_right_dropdown'
|
||||||
data-toggle='dropdown'
|
data-toggle='dropdown'
|
||||||
aria-expanded='true'
|
aria-expanded='true'
|
||||||
>
|
>
|
||||||
<strong className='heading'>{channelTitle} </strong>
|
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
||||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
|
|
||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
className='dropdown-menu'
|
className='dropdown-menu dropdown-menu-right'
|
||||||
role='menu'
|
role='menu'
|
||||||
aria-labelledby='channel_header_dropdown'
|
aria-labelledby='channel_header_right_dropdown'
|
||||||
>
|
>
|
||||||
{dropdownContents}
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
onClick={this.searchMentions}
|
||||||
|
>
|
||||||
|
Recent Mentions
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<OverlayTrigger
|
</th>
|
||||||
trigger={['hover', 'focus']}
|
</tr>
|
||||||
placement='bottom'
|
</tbody>
|
||||||
overlay={popoverContent}
|
</table>
|
||||||
ref='descriptionOverlay'
|
<EditChannelPurposeModal
|
||||||
>
|
show={this.state.showEditChannelPurposeModal}
|
||||||
<div
|
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
|
||||||
onClick={TextFormatting.handleClick}
|
channel={channel}
|
||||||
className='description'
|
/>
|
||||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.description, {singleline: true, mentionHighlight: false})}}
|
</div>
|
||||||
/>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<PopoverListMembers
|
|
||||||
members={this.state.users}
|
|
||||||
channelId={channel.id}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
<th className='search-bar__container'><NavbarSearchBox /></th>
|
|
||||||
<th>
|
|
||||||
<div className='dropdown channel-header__links'>
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className='dropdown-toggle theme'
|
|
||||||
type='button'
|
|
||||||
id='channel_header_right_dropdown'
|
|
||||||
data-toggle='dropdown'
|
|
||||||
aria-expanded='true'
|
|
||||||
>
|
|
||||||
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
|
||||||
</a>
|
|
||||||
<ul
|
|
||||||
className='dropdown-menu dropdown-menu-right'
|
|
||||||
role='menu'
|
|
||||||
aria-labelledby='channel_header_right_dropdown'
|
|
||||||
>
|
|
||||||
<li role='presentation'>
|
|
||||||
<a
|
|
||||||
role='menuitem'
|
|
||||||
href='#'
|
|
||||||
onClick={this.searchMentions}
|
|
||||||
>
|
|
||||||
Recent Mentions
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default class EditChannelModal extends React.Component {
|
|||||||
this.onShow = this.onShow.bind(this);
|
this.onShow = this.onShow.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
description: '',
|
header: '',
|
||||||
title: '',
|
title: '',
|
||||||
channelId: '',
|
channelId: '',
|
||||||
serverError: ''
|
serverError: ''
|
||||||
@@ -28,32 +28,32 @@ export default class EditChannelModal extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.channel_description = this.state.description.trim();
|
data.channel_header = this.state.header.trim();
|
||||||
|
|
||||||
Client.updateChannelDesc(data,
|
Client.updateChannelHeader(data,
|
||||||
function handleUpdateSuccess() {
|
() => {
|
||||||
this.setState({serverError: ''});
|
this.setState({serverError: ''});
|
||||||
AsyncClient.getChannel(this.state.channelId);
|
AsyncClient.getChannel(this.state.channelId);
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
|
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
|
||||||
}.bind(this),
|
},
|
||||||
function handleUpdateError(err) {
|
(err) => {
|
||||||
if (err.message === 'Invalid channel_description parameter') {
|
if (err.message === 'Invalid channel_header parameter') {
|
||||||
this.setState({serverError: 'This description is too long, please enter a shorter one'});
|
this.setState({serverError: 'This channel header is too long, please enter a shorter one'});
|
||||||
} else {
|
} else {
|
||||||
this.setState({serverError: err.message});
|
this.setState({serverError: err.message});
|
||||||
}
|
}
|
||||||
}.bind(this)
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
handleUserInput(e) {
|
handleUserInput(e) {
|
||||||
this.setState({description: e.target.value});
|
this.setState({header: e.target.value});
|
||||||
}
|
}
|
||||||
handleClose() {
|
handleClose() {
|
||||||
this.setState({description: '', serverError: ''});
|
this.setState({header: '', serverError: ''});
|
||||||
}
|
}
|
||||||
onShow(e) {
|
onShow(e) {
|
||||||
const button = e.relatedTarget;
|
const button = e.relatedTarget;
|
||||||
this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
|
this.setState({header: $(button).attr('data-header'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
|
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
|
||||||
@@ -73,7 +73,7 @@ export default class EditChannelModal extends React.Component {
|
|||||||
className='modal-title'
|
className='modal-title'
|
||||||
ref='title'
|
ref='title'
|
||||||
>
|
>
|
||||||
Edit Description
|
Edit Header
|
||||||
</h4>
|
</h4>
|
||||||
);
|
);
|
||||||
if (this.state.title) {
|
if (this.state.title) {
|
||||||
@@ -82,7 +82,7 @@ export default class EditChannelModal extends React.Component {
|
|||||||
className='modal-title'
|
className='modal-title'
|
||||||
ref='title'
|
ref='title'
|
||||||
>
|
>
|
||||||
Edit Description for <span className='name'>{this.state.title}</span>
|
Edit Header for <span className='name'>{this.state.title}</span>
|
||||||
</h4>
|
</h4>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -113,9 +113,8 @@ export default class EditChannelModal extends React.Component {
|
|||||||
<textarea
|
<textarea
|
||||||
className='form-control no-resize'
|
className='form-control no-resize'
|
||||||
rows='6'
|
rows='6'
|
||||||
ref='channelDesc'
|
|
||||||
maxLength='1024'
|
maxLength='1024'
|
||||||
value={this.state.description}
|
value={this.state.header}
|
||||||
onChange={this.handleUserInput}
|
onChange={this.handleUserInput}
|
||||||
/>
|
/>
|
||||||
{serverError}
|
{serverError}
|
||||||
|
|||||||
118
web/react/components/edit_channel_purpose_modal.jsx
Обычный файл
118
web/react/components/edit_channel_purpose_modal.jsx
Обычный файл
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
const AsyncClient = require('../utils/async_client.jsx');
|
||||||
|
const Client = require('../utils/client.jsx');
|
||||||
|
const Modal = ReactBootstrap.Modal;
|
||||||
|
|
||||||
|
export default class EditChannelPurposeModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleHide = this.handleHide.bind(this);
|
||||||
|
this.handleSave = this.handleSave.bind(this);
|
||||||
|
|
||||||
|
this.state = {serverError: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHide() {
|
||||||
|
this.setState({serverError: ''});
|
||||||
|
|
||||||
|
if (this.props.onModalDismissed) {
|
||||||
|
this.props.onModalDismissed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave() {
|
||||||
|
if (!this.props.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
channel_id: this.props.channel.id,
|
||||||
|
channel_purpose: ReactDOM.findDOMNode(this.refs.purpose).value.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.updateChannelPurpose(data,
|
||||||
|
() => {
|
||||||
|
AsyncClient.getChannel(this.props.channel.id);
|
||||||
|
|
||||||
|
this.handleHide();
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
if (err.message === 'Invalid channel_purpose parameter') {
|
||||||
|
this.setState({serverError: 'This channel purpose is too long, please enter a shorter one'});
|
||||||
|
} else {
|
||||||
|
this.setState({serverError: err.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.props.show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverError = null;
|
||||||
|
if (this.state.serverError) {
|
||||||
|
serverError = (
|
||||||
|
<div className='form-group has-error'>
|
||||||
|
<br/>
|
||||||
|
<label className='control-label'>{this.state.serverError}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = <span>{'Edit Purpose'}</span>;
|
||||||
|
if (this.props.channel.display_name) {
|
||||||
|
title = <span>{'Edit Purpose for '}<span className='name'>{this.props.channel.display_name}</span></span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className='modal-edit-channel-purpose'
|
||||||
|
show={this.props.show}
|
||||||
|
onHide={this.handleHide}
|
||||||
|
>
|
||||||
|
<Modal.Header closeButton={true}>
|
||||||
|
<Modal.Title>
|
||||||
|
{title}
|
||||||
|
</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<textarea
|
||||||
|
ref='purpose'
|
||||||
|
className='form-control no-resize'
|
||||||
|
rows='6'
|
||||||
|
maxLength='128'
|
||||||
|
defaultValue={this.props.channel.purpose}
|
||||||
|
/>
|
||||||
|
{serverError}
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btn-default'
|
||||||
|
onClick={this.handleHide}
|
||||||
|
>
|
||||||
|
{'Cancel'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='btn btn-primary'
|
||||||
|
onClick={this.handleSave}
|
||||||
|
>
|
||||||
|
{'Save'}
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditChannelPurposeModal.propTypes = {
|
||||||
|
show: React.PropTypes.bool.isRequired,
|
||||||
|
channel: React.PropTypes.object,
|
||||||
|
onModalDismissed: React.PropTypes.func.isRequired
|
||||||
|
};
|
||||||
@@ -109,7 +109,7 @@ export default class MoreChannels extends React.Component {
|
|||||||
<tr key={channel.id}>
|
<tr key={channel.id}>
|
||||||
<td>
|
<td>
|
||||||
<p className='more-name'>{channel.display_name}</p>
|
<p className='more-name'>{channel.display_name}</p>
|
||||||
<p className='more-description'>{channel.description}</p>
|
<p className='more-purpose'>{channel.purpose}</p>
|
||||||
</td>
|
</td>
|
||||||
<td className='td--action'>
|
<td className='td--action'>
|
||||||
{joinButton}
|
{joinButton}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var ChannelStore = require('../stores/channel_store.jsx');
|
|||||||
var TeamStore = require('../stores/team_store.jsx');
|
var TeamStore = require('../stores/team_store.jsx');
|
||||||
var MessageWrapper = require('./message_wrapper.jsx');
|
var MessageWrapper = require('./message_wrapper.jsx');
|
||||||
var NotifyCounts = require('./notify_counts.jsx');
|
var NotifyCounts = require('./notify_counts.jsx');
|
||||||
|
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
|
||||||
const Utils = require('../utils/utils.jsx');
|
const Utils = require('../utils/utils.jsx');
|
||||||
|
|
||||||
var Constants = require('../utils/constants.jsx');
|
var Constants = require('../utils/constants.jsx');
|
||||||
@@ -26,7 +27,9 @@ export default class Navbar extends React.Component {
|
|||||||
this.createCollapseButtons = this.createCollapseButtons.bind(this);
|
this.createCollapseButtons = this.createCollapseButtons.bind(this);
|
||||||
this.createDropdown = this.createDropdown.bind(this);
|
this.createDropdown = this.createDropdown.bind(this);
|
||||||
|
|
||||||
this.state = this.getStateFromStores();
|
const state = this.getStateFromStores();
|
||||||
|
state.showEditChannelPurposeModal = false;
|
||||||
|
this.state = state;
|
||||||
}
|
}
|
||||||
getStateFromStores() {
|
getStateFromStores() {
|
||||||
return {
|
return {
|
||||||
@@ -106,22 +109,35 @@ export default class Navbar extends React.Component {
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
var setChannelDescriptionOption = (
|
var setChannelHeaderOption = (
|
||||||
<li role='presentation'>
|
<li role='presentation'>
|
||||||
<a
|
<a
|
||||||
role='menuitem'
|
role='menuitem'
|
||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
Set Channel Description...
|
Set Channel Header...
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var setChannelPurposeOption = null;
|
||||||
|
if (!isDirect) {
|
||||||
|
setChannelPurposeOption = (
|
||||||
|
<li role='presentation'>
|
||||||
|
<a
|
||||||
|
role='menuitem'
|
||||||
|
href='#'
|
||||||
|
onClick={() => this.setState({showEditChannelPurposeModal: true})}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var addMembersOption;
|
var addMembersOption;
|
||||||
var leaveChannelOption;
|
var leaveChannelOption;
|
||||||
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
||||||
@@ -249,7 +265,8 @@ export default class Navbar extends React.Component {
|
|||||||
{viewInfoOption}
|
{viewInfoOption}
|
||||||
{addMembersOption}
|
{addMembersOption}
|
||||||
{manageMembersOption}
|
{manageMembersOption}
|
||||||
{setChannelDescriptionOption}
|
{setChannelHeaderOption}
|
||||||
|
{setChannelPurposeOption}
|
||||||
{notificationPreferenceOption}
|
{notificationPreferenceOption}
|
||||||
{renameChannelOption}
|
{renameChannelOption}
|
||||||
{deleteChannelOption}
|
{deleteChannelOption}
|
||||||
@@ -335,10 +352,10 @@ export default class Navbar extends React.Component {
|
|||||||
<Popover
|
<Popover
|
||||||
bsStyle='info'
|
bsStyle='info'
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
id='description-popover'
|
id='header-popover'
|
||||||
>
|
>
|
||||||
<MessageWrapper
|
<MessageWrapper
|
||||||
message={channel.description}
|
message={channel.header}
|
||||||
options={{singleline: true, mentionHighlight: false}}
|
options={{singleline: true, mentionHighlight: false}}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -360,20 +377,20 @@ export default class Navbar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel.description.length === 0) {
|
if (channel.header.length === 0) {
|
||||||
popoverContent = (
|
popoverContent = (
|
||||||
<Popover
|
<Popover
|
||||||
bsStyle='info'
|
bsStyle='info'
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
id='description-popover'
|
id='header-popover'
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{'No channel description yet.'}
|
{'No channel header yet.'}
|
||||||
<br/>
|
<br/>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
@@ -392,17 +409,24 @@ export default class Navbar extends React.Component {
|
|||||||
var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
|
var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<div>
|
||||||
className='navbar navbar-default navbar-fixed-top'
|
<nav
|
||||||
role='navigation'
|
className='navbar navbar-default navbar-fixed-top'
|
||||||
>
|
role='navigation'
|
||||||
<div className='container-fluid theme'>
|
>
|
||||||
<div className='navbar-header'>
|
<div className='container-fluid theme'>
|
||||||
{collapseButtons}
|
<div className='navbar-header'>
|
||||||
{channelMenuDropdown}
|
{collapseButtons}
|
||||||
|
{channelMenuDropdown}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
<EditChannelPurposeModal
|
||||||
|
show={this.state.showEditChannelPurposeModal}
|
||||||
|
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
|
||||||
|
channel={channel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default class NewChannelFlow extends React.Component {
|
|||||||
flowState: SHOW_NEW_CHANNEL,
|
flowState: SHOW_NEW_CHANNEL,
|
||||||
channelDisplayName: '',
|
channelDisplayName: '',
|
||||||
channelName: '',
|
channelName: '',
|
||||||
channelDescription: '',
|
channelPurpose: '',
|
||||||
nameModified: false
|
nameModified: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ export default class NewChannelFlow extends React.Component {
|
|||||||
flowState: SHOW_NEW_CHANNEL,
|
flowState: SHOW_NEW_CHANNEL,
|
||||||
channelDisplayName: '',
|
channelDisplayName: '',
|
||||||
channelName: '',
|
channelName: '',
|
||||||
channelDescription: '',
|
channelPurpose: '',
|
||||||
nameModified: false
|
nameModified: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ export default class NewChannelFlow extends React.Component {
|
|||||||
|
|
||||||
const cu = UserStore.getCurrentUser();
|
const cu = UserStore.getCurrentUser();
|
||||||
channel.team_id = cu.team_id;
|
channel.team_id = cu.team_id;
|
||||||
channel.description = this.state.channelDescription;
|
channel.purpose = this.state.channelPurpose;
|
||||||
channel.type = this.state.channelType;
|
channel.type = this.state.channelType;
|
||||||
|
|
||||||
Client.createChannel(channel,
|
Client.createChannel(channel,
|
||||||
@@ -109,7 +109,7 @@ export default class NewChannelFlow extends React.Component {
|
|||||||
channelDataChanged(data) {
|
channelDataChanged(data) {
|
||||||
this.setState({
|
this.setState({
|
||||||
channelDisplayName: data.displayName,
|
channelDisplayName: data.displayName,
|
||||||
channelDescription: data.description
|
channelPurpose: data.purpose
|
||||||
});
|
});
|
||||||
if (!this.state.nameModified) {
|
if (!this.state.nameModified) {
|
||||||
this.setState({channelName: Utils.cleanUpUrlable(data.displayName.trim())});
|
this.setState({channelName: Utils.cleanUpUrlable(data.displayName.trim())});
|
||||||
@@ -119,7 +119,7 @@ export default class NewChannelFlow extends React.Component {
|
|||||||
const channelData = {
|
const channelData = {
|
||||||
name: this.state.channelName,
|
name: this.state.channelName,
|
||||||
displayName: this.state.channelDisplayName,
|
displayName: this.state.channelDisplayName,
|
||||||
description: this.state.channelDescription
|
purpose: this.state.channelPurpose
|
||||||
};
|
};
|
||||||
|
|
||||||
let showChannelModal = false;
|
let showChannelModal = false;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default class NewChannelModal extends React.Component {
|
|||||||
handleChange() {
|
handleChange() {
|
||||||
const newData = {
|
const newData = {
|
||||||
displayName: ReactDOM.findDOMNode(this.refs.display_name).value,
|
displayName: ReactDOM.findDOMNode(this.refs.display_name).value,
|
||||||
description: ReactDOM.findDOMNode(this.refs.channel_desc).value
|
purpose: ReactDOM.findDOMNode(this.refs.channel_purpose).value
|
||||||
};
|
};
|
||||||
this.props.onDataChanged(newData);
|
this.props.onDataChanged(newData);
|
||||||
}
|
}
|
||||||
@@ -136,22 +136,22 @@ export default class NewChannelModal extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className='form-group less'>
|
<div className='form-group less'>
|
||||||
<div className='col-sm-3'>
|
<div className='col-sm-3'>
|
||||||
<label className='form__label control-label'>{'Description'}</label>
|
<label className='form__label control-label'>{'Purpose'}</label>
|
||||||
<label className='form__label light'>{'(optional)'}</label>
|
<label className='form__label light'>{'(optional)'}</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='col-sm-9'>
|
<div className='col-sm-9'>
|
||||||
<textarea
|
<textarea
|
||||||
className='form-control no-resize'
|
className='form-control no-resize'
|
||||||
ref='channel_desc'
|
ref='channel_purpose'
|
||||||
rows='4'
|
rows='4'
|
||||||
placeholder='Description'
|
placeholder='Purpose'
|
||||||
maxLength='1024'
|
maxLength='128'
|
||||||
value={this.props.channelData.description}
|
value={this.props.channelData.purpose}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
tabIndex='2'
|
tabIndex='2'
|
||||||
/>
|
/>
|
||||||
<p className='input__help'>
|
<p className='input__help'>
|
||||||
{'Description helps others decide whether to join this channel.'}
|
{`Describe how this ${channelTerm} should be used.`}
|
||||||
</p>
|
</p>
|
||||||
{serverError}
|
{serverError}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -358,11 +358,11 @@ export default class PostList extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>{'Set a description'}
|
<i className='fa fa-pencil'></i>{'Set a header'}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -413,11 +413,11 @@ export default class PostList extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>{'Set a description'}
|
<i className='fa fa-pencil'></i>{'Set a header'}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className='intro-links'
|
className='intro-links'
|
||||||
@@ -479,11 +479,11 @@ export default class PostList extends React.Component {
|
|||||||
href='#'
|
href='#'
|
||||||
data-toggle='modal'
|
data-toggle='modal'
|
||||||
data-target='#edit_channel'
|
data-target='#edit_channel'
|
||||||
data-desc={channel.description}
|
data-header={channel.header}
|
||||||
data-title={channel.display_name}
|
data-title={channel.display_name}
|
||||||
data-channelid={channel.id}
|
data-channelid={channel.id}
|
||||||
>
|
>
|
||||||
<i className='fa fa-pencil'></i>{'Set a description'}
|
<i className='fa fa-pencil'></i>{'Set a header'}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className='intro-links'
|
className='intro-links'
|
||||||
|
|||||||
@@ -589,21 +589,38 @@ export function updateChannel(channel, success, error) {
|
|||||||
track('api', 'api_channels_update');
|
track('api', 'api_channels_update');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateChannelDesc(data, success, error) {
|
export function updateChannelHeader(data, success, error) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/v1/channels/update_desc',
|
url: '/api/v1/channels/update_header',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
success,
|
success,
|
||||||
error: function onError(xhr, status, err) {
|
error: function onError(xhr, status, err) {
|
||||||
var e = handleError('updateChannelDesc', xhr, status, err);
|
var e = handleError('updateChannelHeader', xhr, status, err);
|
||||||
error(e);
|
error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
track('api', 'api_channels_desc');
|
track('api', 'api_channels_header');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateChannelPurpose(data, success, error) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/v1/channels/update_purpose',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success,
|
||||||
|
error: function onError(xhr, status, err) {
|
||||||
|
var e = handleError('updateChannelPurpose', xhr, status, err);
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
track('api', 'api_channels_purpose');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateNotifyProps(data, success, error) {
|
export function updateNotifyProps(data, success, error) {
|
||||||
|
|||||||
@@ -376,7 +376,7 @@
|
|||||||
@include opacity(0.8);
|
@include opacity(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-description {
|
.more-purpose {
|
||||||
@include opacity(0.7);
|
@include opacity(0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user