Show avatars for 1-1 conversations in the convo list.

Don't show your own name in conversation titles.
Don't record the same participant in a conversation twice.
This commit is contained in:
Azareal 2020-02-12 19:11:27 +10:00
parent 9f0333ddc4
commit 01b7a18ea9
7 changed files with 97 additions and 13 deletions

View File

@ -54,6 +54,7 @@ func init() {
type Conversation struct { type Conversation struct {
ID int ID int
Link string
CreatedBy int CreatedBy int
CreatedAt time.Time CreatedAt time.Time
LastReplyBy int LastReplyBy int
@ -133,6 +134,10 @@ func (co *Conversation) Create() (int, error) {
return int(lastID), err return int(lastID), err
} }
func BuildConvoURL(coid int) string {
return "/user/convo/" + strconv.Itoa(coid)
}
type ConversationExtra struct { type ConversationExtra struct {
*Conversation *Conversation
Users []*User Users []*User
@ -141,7 +146,7 @@ type ConversationExtra struct {
type ConversationStore interface { type ConversationStore interface {
Get(id int) (*Conversation, error) Get(id int) (*Conversation, error)
GetUser(uid, offset int) (cos []*Conversation, err error) GetUser(uid, offset int) (cos []*Conversation, err error)
GetUserExtra(uid int, offset int) (cos []*ConversationExtra, err error) GetUserExtra(uid, offset int) (cos []*ConversationExtra, err error)
GetUserCount(uid int) (count int) GetUserCount(uid int) (count int)
Delete(id int) error Delete(id int) error
Count() (count int) Count() (count int)
@ -330,7 +335,7 @@ func (s *DefaultConversationStore) Create(content string, createdBy int, partici
if len(participants) == 0 { if len(participants) == 0 {
return 0, errors.New("no participants set") return 0, errors.New("no participants set")
} }
res, err := s.create.Exec(createdBy,createdBy) res, err := s.create.Exec(createdBy, createdBy)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -346,6 +351,9 @@ func (s *DefaultConversationStore) Create(content string, createdBy int, partici
} }
for _, p := range participants { for _, p := range participants {
if p == createdBy {
continue
}
_, err := s.addParticipant.Exec(p, lastID) _, err := s.addParticipant.Exec(p, lastID)
if err != nil { if err != nil {
return 0, err return 0, err

View File

@ -283,9 +283,15 @@ type ResetPage struct {
MFA bool MFA bool
} }
type ConvoListRow struct {
*ConversationExtra
ShortUsers []*User
OneOnOne bool
}
type ConvoListPage struct { type ConvoListPage struct {
*Header *Header
Convos []*ConversationExtra Convos []ConvoListRow
Paginator Paginator
} }

View File

@ -350,13 +350,17 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
t.AddStd("account", "c.Account", accountPage) t.AddStd("account", "c.Account", accountPage)
parti := []*User{&user} parti := []*User{&user}
convo := &Conversation{1, user.ID, time.Now(), 0, time.Now()} convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, &user, "", 4, true}} convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, &user, "", 4, true}}
convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}} convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
t.AddStd("convo", "c.ConvoViewPage", convoPage) t.AddStd("convo", "c.ConvoViewPage", convoPage)
convos := []*ConversationExtra{&ConversationExtra{&Conversation{}, []*User{&user}}} convos := []*ConversationExtra{&ConversationExtra{&Conversation{}, []*User{&user}}}
convoListPage := ConvoListPage{header, convos, Paginator{[]int{1}, 1, 1}} var cRows []ConvoListRow
for _, convo := range convos {
cRows = append(cRows, ConvoListRow{convo, convo.Users, false})
}
convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
t.AddStd("convos", "c.ConvoListPage", convoListPage) t.AddStd("convos", "c.ConvoListPage", convoListPage)
basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID} basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID}
@ -557,7 +561,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
t.AddStd("topic_c_poll_input", "c.TopicCPollInput", TopicCPollInput{Index: 0}) t.AddStd("topic_c_poll_input", "c.TopicCPollInput", TopicCPollInput{Index: 0})
parti := []*User{&user} parti := []*User{&user}
convo := &Conversation{1, user.ID, time.Now(), 0, time.Now()} convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, &user, "", 4, true}} convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, &user, "", 4, true}}
convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}} convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
t.AddStd("convo", "c.ConvoViewPage", convoPage) t.AddStd("convo", "c.ConvoViewPage", convoPage)

View File

@ -30,7 +30,24 @@ func Convos(w http.ResponseWriter, r *http.Request, user c.User, h *c.Header) c.
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
pi := c.Account{h, "dashboard", "convos", c.ConvoListPage{h, convos, c.Paginator{pageList, page, lastPage}}} var cRows []c.ConvoListRow
for _, convo := range convos {
var parti []*c.User
notMe := false
for _, u := range convo.Users {
if u.ID == user.ID {
continue
}
parti = append(parti, u)
notMe = true
}
if !notMe {
parti = convo.Users
}
cRows = append(cRows, c.ConvoListRow{convo, parti, len(parti) == 1})
}
pi := c.Account{h, "dashboard", "convos", c.ConvoListPage{h, cRows, c.Paginator{pageList, page, lastPage}}}
return renderTemplate("account", w, r, h, pi) return renderTemplate("account", w, r, h, pi)
} }
@ -138,16 +155,29 @@ func ConvosCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, user)
} }
recps := c.SanitiseSingleLine(r.PostFormValue("recp")) sRecps := c.SanitiseSingleLine(r.PostFormValue("recp"))
body := c.PreparseMessage(r.PostFormValue("body")) body := c.PreparseMessage(r.PostFormValue("body"))
rlist := []int{} rlist := []int{}
// De-dupe recipients
var recps []string
unames := make(map[string]struct{})
for _, recp := range strings.Split(sRecps, ",") {
recp = strings.TrimSpace(recp)
_, exists := unames[recp]
if !exists {
recps = append(recps, recp)
unames[recp] = struct{}{}
}
}
max := 10 // max number of recipients that can be added at once max := 10 // max number of recipients that can be added at once
for i, recp := range strings.Split(recps, ",") { for i, recp := range recps {
if i >= max { if i >= max {
break break
} }
u, err := c.Users.GetByName(strings.TrimSpace(recp)) u, err := c.Users.GetByName(recp)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("One of the recipients doesn't exist", w, r, user) return c.LocalError("One of the recipients doesn't exist", w, r, user)
} else if err != nil { } else if err != nil {

View File

@ -9,7 +9,7 @@
<form action="/user/convos/create/submit/?s={{.CurrentUser.Session}}" method="post"> <form action="/user/convos/create/submit/?s={{.CurrentUser.Session}}" method="post">
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem formlabel"><a>{{lang "create_convo_recp"}}</a></div> <div class="formitem formlabel"><a>{{lang "create_convo_recp"}}</a></div>
<div class="formitem"><input name="recp" type="text" /></div> <div class="formitem"><input name="recp" type="text"/></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><textarea name="body"></textarea></div> <div class="formitem"><textarea name="body"></textarea></div>
@ -27,7 +27,8 @@
{{range .Convos}} {{range .Convos}}
<div class="rowitem"> <div class="rowitem">
<span class="to_left"> <span class="to_left">
<a href="/user/convo/{{.ID}}">{{range .Users}}<span class="convos_item_user">{{.Name}}</span>&nbsp;{{end}}</a></span></a> {{if .OneOnOne}}{{range .ShortUsers}}<img class="bgsub" src="{{.MicroAvatar}}" height=48 width=48/>{{end}}{{end}}
<a href="/user/convo/{{.ID}}">{{range .ShortUsers}}<span class="convos_item_user">{{.Name}}</span>&nbsp;{{end}}</a></span></a>
</span> </span>
<span title="{{abstime .LastReplyAt}}" class="to_right">{{reltime .LastReplyAt}}</span> <span title="{{abstime .LastReplyAt}}" class="to_right">{{reltime .LastReplyAt}}</span>
<div style="clear:both;"></div> <div style="clear:both;"></div>

View File

@ -1,6 +1,24 @@
.rowhead .rowitem { .rowhead .rowitem, .convos_list .rowitem {
display: flex; display: flex;
} }
.convos_list .to_left {
display: flex;
}
.convos_list .rowitem img {
width: 26px;
height: 26px;
margin-right: 8px;
border-radius: 24px;
}
.convos_list .rowitem a {
/*margin-top: auto;
margin-bottom: auto;*/
margin-top: 4px;
}
.convos_list .to_right {
margin-top: auto;
margin-bottom: auto;
}
.convos_item_user:not(:last-child):after { .convos_item_user:not(:last-child):after {
content: ","; content: ",";
} }

View File

@ -8,6 +8,23 @@
.close_form { .close_form {
margin-left: 8px; margin-left: 8px;
} }
.convos_list .to_left {
display: flex;
}
.convos_list .rowitem img {
width: 24px;
height: 24px;
margin-right: 8px;
border-radius: 24px;
}
.convos_list .rowitem a {
/*margin-top: auto;
margin-bottom: auto;*/
}
.convos_list .to_right {
margin-top: auto;
margin-bottom: auto;
}
.convos_item_user:not(:last-child):after { .convos_item_user:not(:last-child):after {
content: ","; content: ",";
} }