Messages: Threaded view for better user experience
This commit is contained in:
parent
f986c6f0a5
commit
cadb759ff4
|
@ -24,14 +24,17 @@ func Inbox() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Default is inbox, what about sentbox?
|
||||
var showSent = r.FormValue("box") == "sent"
|
||||
// What view are we looking at? Threads (default), Inbox, or Outbox?
|
||||
var box = r.FormValue("box")
|
||||
if box != "inbox" && box != "sent" {
|
||||
box = "threads"
|
||||
}
|
||||
|
||||
// Are we reading a specific message?
|
||||
var (
|
||||
viewThread []*models.Message
|
||||
threadPager *models.Pagination
|
||||
composeToUsername string
|
||||
composeToUser *models.User
|
||||
msgId int
|
||||
)
|
||||
if uri := ReadURLRegexp.FindStringSubmatch(r.URL.Path); uri != nil {
|
||||
|
@ -58,7 +61,7 @@ func Inbox() http.HandlerFunc {
|
|||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't get sender of that message: %s", err)
|
||||
}
|
||||
composeToUsername = sender.Username
|
||||
composeToUser = sender
|
||||
|
||||
// Get the full chat thread (paginated).
|
||||
threadPager = &models.Pagination{
|
||||
|
@ -86,6 +89,7 @@ func Inbox() http.HandlerFunc {
|
|||
}
|
||||
|
||||
// Get the inbox list of messages.
|
||||
var messages []*models.Message
|
||||
pager := &models.Pagination{
|
||||
Page: 1,
|
||||
PerPage: config.PageSizeInboxList,
|
||||
|
@ -95,9 +99,20 @@ func Inbox() http.HandlerFunc {
|
|||
// On the main inbox view, ?page= params page thru the message list, not a thread.
|
||||
pager.ParsePage(r)
|
||||
}
|
||||
messages, err := models.GetMessages(currentUser, showSent, pager)
|
||||
if err != nil {
|
||||
|
||||
// Viewing the threads, or a specific inbox/sent box?
|
||||
if box == "threads" {
|
||||
if result, err := models.GetMessageThreads(currentUser, pager); err != nil {
|
||||
session.FlashError(w, r, "Couldn't get your messages from DB: %s", err)
|
||||
} else {
|
||||
messages = result
|
||||
}
|
||||
} else {
|
||||
if result, err := models.GetMessages(currentUser, box == "sent", pager); err != nil {
|
||||
session.FlashError(w, r, "Couldn't get your messages from DB: %s", err)
|
||||
} else {
|
||||
messages = result
|
||||
}
|
||||
}
|
||||
|
||||
// How many unreads?
|
||||
|
@ -111,11 +126,9 @@ func Inbox() http.HandlerFunc {
|
|||
for _, m := range messages {
|
||||
userIDs = append(userIDs, m.SourceUserID, m.TargetUserID)
|
||||
}
|
||||
if viewThread != nil {
|
||||
for _, m := range viewThread {
|
||||
userIDs = append(userIDs, m.SourceUserID, m.TargetUserID)
|
||||
}
|
||||
}
|
||||
userMap, err := models.MapUsers(currentUser, userIDs)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't map users: %s", err)
|
||||
|
@ -126,10 +139,10 @@ func Inbox() http.HandlerFunc {
|
|||
"UserMap": userMap,
|
||||
"Unread": unread,
|
||||
"Pager": pager,
|
||||
"IsSentBox": showSent,
|
||||
"Box": box,
|
||||
"ViewThread": viewThread, // nil on inbox page
|
||||
"ThreadPager": threadPager,
|
||||
"ReplyTo": composeToUsername,
|
||||
"ReplyTo": composeToUser,
|
||||
"MessageID": msgId,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
|
|
|
@ -23,7 +23,7 @@ func GetMessage(id uint64) (*Message, error) {
|
|||
return m, result.Error
|
||||
}
|
||||
|
||||
// GetMessages for a user.
|
||||
// GetMessages for a user, e-mail style for the inbox or sent box view.
|
||||
func GetMessages(user *User, sent bool, pager *Pagination) ([]*Message, error) {
|
||||
var (
|
||||
m = []*Message{}
|
||||
|
@ -70,6 +70,73 @@ func GetMessages(user *User, sent bool, pager *Pagination) ([]*Message, error) {
|
|||
return m, result.Error
|
||||
}
|
||||
|
||||
// GetMessageThreads for a user: combined inbox/sent view grouped by username.
|
||||
func GetMessageThreads(user *User, pager *Pagination) ([]*Message, error) {
|
||||
var (
|
||||
m = []*Message{}
|
||||
blockedUserIDs = BlockedUserIDs(user)
|
||||
where = []string{}
|
||||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
where = append(where, "target_user_id = ?")
|
||||
placeholders = append(placeholders, user.ID)
|
||||
|
||||
if len(blockedUserIDs) > 0 {
|
||||
where = append(where, "source_user_id NOT IN ?")
|
||||
placeholders = append(placeholders, blockedUserIDs)
|
||||
}
|
||||
|
||||
// Don't show messages from banned or disabled accounts.
|
||||
where = append(where, `
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE users.id IN (messages.target_user_id, messages.source_user_id)
|
||||
AND users.status <> 'active'
|
||||
)
|
||||
`)
|
||||
|
||||
type newest struct {
|
||||
ID uint64
|
||||
SourceUserID uint64
|
||||
TargetUserID uint64
|
||||
}
|
||||
var scan []newest
|
||||
|
||||
// Get the newest message IDs grouped by username for everyone we are chatting with.
|
||||
query := DB.Model(&Message{}).Select(
|
||||
"max(id) AS id",
|
||||
"source_user_id",
|
||||
"target_user_id",
|
||||
).Where(
|
||||
strings.Join(where, " AND "),
|
||||
placeholders...,
|
||||
).Group(
|
||||
"source_user_id, target_user_id",
|
||||
).Order("id desc").Scan(&scan)
|
||||
if query.Error != nil {
|
||||
return nil, query.Error
|
||||
}
|
||||
|
||||
pager.Total = int64(len(scan))
|
||||
|
||||
// Get the details from these message IDs.
|
||||
var messageIDs = []uint64{}
|
||||
for _, row := range scan {
|
||||
messageIDs = append(messageIDs, row.ID)
|
||||
}
|
||||
query = DB.Where(
|
||||
"id IN ?",
|
||||
messageIDs,
|
||||
).Order(pager.Sort)
|
||||
|
||||
query.Model(&Message{}).Count(&pager.Total)
|
||||
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&m)
|
||||
|
||||
return m, result.Error
|
||||
}
|
||||
|
||||
// GetMessageThread returns paginated message history between two people.
|
||||
func GetMessageThread(sourceUserID, targetUserID uint64, pager *Pagination) ([]*Message, error) {
|
||||
var m = []*Message{}
|
||||
|
|
|
@ -8,7 +8,14 @@
|
|||
<i class="fa fa-envelope mr-2"></i>
|
||||
Messages
|
||||
</h1>
|
||||
<h2 class="subtitle">{{if .IsSentBox}}Sent{{else}}Inbox{{end}}</h2>
|
||||
<h2 class="subtitle">
|
||||
{{if eq .Box "threads"}}
|
||||
Threads
|
||||
{{else if eq .Box "sent"}}
|
||||
Sent
|
||||
{{else}}
|
||||
Inbox
|
||||
{{end}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -23,16 +30,29 @@
|
|||
<div class="card">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">
|
||||
{{if .ViewThread}}Conversation with {{.ReplyTo}}{{else}}Inbox{{end}}
|
||||
{{if .ViewThread}}Conversation with {{.ReplyTo.Username}}{{else}}Inbox{{end}}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{{if .ViewThread}}
|
||||
<div class="card-content">
|
||||
<div class="block">
|
||||
<div class="columns is-mobile is-gapless">
|
||||
<div class="column is-narrow">
|
||||
<strong>To:</strong>
|
||||
</div>
|
||||
<div class="column is-narrow mx-2">
|
||||
{{template "avatar-24x24" .ReplyTo}}
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="/u/{{.ReplyTo.Username}}">{{.ReplyTo.Username}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<form action="/messages/compose" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="to" value="{{.ReplyTo}}">
|
||||
<input type="hidden" name="to" value="{{.ReplyTo.Username}}">
|
||||
<input type="hidden" name="from" value="inbox">
|
||||
<textarea class="textarea" cols="80" rows="4"
|
||||
name="message"
|
||||
|
@ -170,28 +190,56 @@
|
|||
<div class="column is-one-third">
|
||||
<div class="card block">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">Messages</p>
|
||||
<p class="card-header-title has-text-light">
|
||||
{{if eq .Box "threads"}}
|
||||
Conversations
|
||||
{{else if eq .Box "inbox"}}
|
||||
All Inbox Messages
|
||||
{{else}}
|
||||
Sent Messages
|
||||
{{end}}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="tabs is-toggle is-fullwidth">
|
||||
<ul>
|
||||
<li{{if not .IsSentBox}} class="is-active"{{end}}>
|
||||
<a href="/messages">Inbox</a>
|
||||
<li{{if eq .Box "threads"}} class="is-active"{{end}}>
|
||||
<a href="/messages">Threads</a>
|
||||
</li>
|
||||
<li{{if .IsSentBox}} class="is-active"{{end}}>
|
||||
<li{{if eq .Box "inbox"}} class="is-active"{{end}}>
|
||||
<a href="/messages?box=inbox">Inbox</a>
|
||||
</li>
|
||||
<li{{if eq .Box "sent"}} class="is-active"{{end}}>
|
||||
<a href="/messages?box=sent">Sent</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{if eq .Box "threads"}}
|
||||
<div class="block is-size-7">
|
||||
Showing {{.Pager.Total}} conversation threads with others (ordered by most
|
||||
recent, grouped by sender name). <strong>Note:</strong> messages you
|
||||
have Sent but which have not been replied to will only appear on the
|
||||
<a href="/messages?box=sent">"Sent"</a> tab.
|
||||
</div>
|
||||
{{else if eq .Box "inbox"}}
|
||||
<div class="block is-size-7">
|
||||
Showing <strong>all</strong> {{.Pager.Total}} inbound messages to you, ordered
|
||||
by most recent.
|
||||
</div>
|
||||
{{else if eq .Box "sent"}}
|
||||
<div class="block is-size-7">
|
||||
Showing {{.Pager.Total}} messages sent by you to other people.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<ul class="menu-list block">
|
||||
{{$IsSentBox := .IsSentBox}}
|
||||
{{range .Messages}}
|
||||
<li>
|
||||
<a href="/messages/read/{{.ID}}">
|
||||
<div>
|
||||
{{if $IsSentBox}}
|
||||
{{if eq $Root.Box "sent"}}
|
||||
{{$User := $UserMap.Get .TargetUserID}}
|
||||
<strong>Sent to {{$User.Username}}</strong>
|
||||
{{else}}
|
||||
|
@ -199,7 +247,7 @@
|
|||
<strong>From {{$User.Username}}</strong>
|
||||
{{end}}
|
||||
{{if not .Read}}
|
||||
<span class="tag is-success">{{if $IsSentBox}}UNREAD{{else}}NEW{{end}}</span>
|
||||
<span class="tag is-success">{{if eq $Root.Box "sent"}}UNREAD{{else}}NEW{{end}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="my-1">
|
||||
|
@ -217,8 +265,8 @@
|
|||
|
||||
<!-- Pager footer -->
|
||||
<div class="block">
|
||||
<div>
|
||||
Found <strong>{{.Pager.Total}}</strong> message{{Pluralize64 .Pager.Total}}
|
||||
<div class="mb-4">
|
||||
Found <strong>{{.Pager.Total}}</strong> {{if eq .Box "threads"}}conversation{{else}}message{{end}}{{Pluralize64 .Pager.Total}}
|
||||
(page {{.Pager.Page}} of {{.Pager.Pages}}).
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user