Render subcategories of forum categories

This commit is contained in:
Ben Visness 2021-05-03 18:59:43 -05:00
parent a0155bfc5e
commit b217cd5592
3 changed files with 157 additions and 58 deletions

View File

@ -2,21 +2,46 @@
{{ define "content" }} {{ define "content" }}
<div class="content-block"> <div class="content-block">
{{ range .Subcategories }}
<div class="pv3">
<h2 class="ma0 ph3 pb2">
<a href="{{ .Url }}">
{{ .Name }} &raquo;<br/>
</a>
</h2>
{{ range .Threads }}
{{ template "thread_list_item.html" . }}
{{ end }}
{{ $more := sub .TotalThreads 3 }}
{{ if gt $more 0 }}
<div class="ph3 pv1">
<a class="title" href="{{ .Url }}">{{ $more }} more &raquo;</a>
</div>
{{ end }}
</div>
{{ end }}
<div class="optionbar"> <div class="optionbar">
<div class="options"> {{ template "forum_category_options" . }}
</div>
{{ range .Threads }}
{{ template "thread_list_item.html" . }}
{{ end }}
<div class="optionbar bottom">
{{ template "forum_category_options" . }}
</div>
</div>
{{ end }}
{{ define "forum_category_options" }}
<div class="options">
{{ if .User }} {{ if .User }}
<a class="button new-thread" href="{{ printf "%s/t/new" .CategoryUrl }}"><span class="big">+</span> New Thread</a> <a class="button new-thread" href="{{ printf "%s/t/new" .CategoryUrl }}"><span class="big">+</span> New Thread</a>
<a class="button" href="{{ printf "%s/markread" .CategoryUrl }}"><span class="big">&#x2713;</span> Mark threads here as read</a> <a class="button" href="{{ printf "%s/markread" .CategoryUrl }}"><span class="big">&#x2713;</span> Mark threads here as read</a>
{{ else }} {{ else }}
<a class="button" href="{% url 'member_login' subdomain=request.subdomain %}">Log in to post a new thread</a> <a class="button" href="{% url 'member_login' subdomain=request.subdomain %}">Log in to post a new thread</a>
{{ end }} {{ end }}
</div> </div>
<div class="options"> <div class="options">
{{ template "pagination.html" .Pagination }} {{ template "pagination.html" .Pagination }}
</div>
</div>
{{ range .Threads }}
{{ template "thread_list_item.html" . }}
{{ end }}
</div> </div>
{{ end }} {{ end }}

View File

@ -9,14 +9,12 @@ import (
"strings" "strings"
"time" "time"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/templates"
"github.com/jackc/pgx/v4/pgxpool"
"git.handmade.network/hmn/hmn/src/db" "git.handmade.network/hmn/hmn/src/db"
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/models" "git.handmade.network/hmn/hmn/src/models"
"git.handmade.network/hmn/hmn/src/oops" "git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/templates"
"github.com/jackc/pgx/v4/pgxpool"
) )
type forumCategoryData struct { type forumCategoryData struct {
@ -25,6 +23,14 @@ type forumCategoryData struct {
CategoryUrl string CategoryUrl string
Threads []templates.ThreadListItem Threads []templates.ThreadListItem
Pagination templates.Pagination Pagination templates.Pagination
Subcategories []forumSubcategoryData
}
type forumSubcategoryData struct {
Name string
Url string
Threads []templates.ThreadListItem
TotalThreads int
} }
func ForumCategory(c *RequestContext) ResponseData { func ForumCategory(c *RequestContext) ResponseData {
@ -35,6 +41,7 @@ func ForumCategory(c *RequestContext) ResponseData {
currentCatId := fetchCatIdFromSlugs(c.Context(), c.Conn, catSlugs, c.CurrentProject.ID) currentCatId := fetchCatIdFromSlugs(c.Context(), c.Conn, catSlugs, c.CurrentProject.ID)
categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID) categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID)
c.Perf.StartBlock("SQL", "Fetch count of page threads")
numThreads, err := db.QueryInt(c.Context(), c.Conn, numThreads, err := db.QueryInt(c.Context(), c.Conn,
` `
SELECT COUNT(*) SELECT COUNT(*)
@ -48,6 +55,7 @@ func ForumCategory(c *RequestContext) ResponseData {
if err != nil { if err != nil {
panic(oops.New(err, "failed to get count of threads")) panic(oops.New(err, "failed to get count of threads"))
} }
c.Perf.EndBlock()
numPages := int(math.Ceil(float64(numThreads) / threadsPerPage)) numPages := int(math.Ceil(float64(numThreads) / threadsPerPage))
@ -71,7 +79,8 @@ func ForumCategory(c *RequestContext) ResponseData {
currentUserId = &c.CurrentUser.ID currentUserId = &c.CurrentUser.ID
} }
type mainPostsQueryResult struct { c.Perf.StartBlock("SQL", "Fetch page threads")
type threadQueryResult struct {
Thread models.Thread `db:"thread"` Thread models.Thread `db:"thread"`
FirstPost models.Post `db:"firstpost"` FirstPost models.Post `db:"firstpost"`
LastPost models.Post `db:"lastpost"` LastPost models.Post `db:"lastpost"`
@ -80,7 +89,7 @@ func ForumCategory(c *RequestContext) ResponseData {
ThreadLastReadTime *time.Time `db:"tlri.lastread"` ThreadLastReadTime *time.Time `db:"tlri.lastread"`
CatLastReadTime *time.Time `db:"clri.lastread"` CatLastReadTime *time.Time `db:"clri.lastread"`
} }
itMainThreads, err := db.Query(c.Context(), c.Conn, mainPostsQueryResult{}, itMainThreads, err := db.Query(c.Context(), c.Conn, threadQueryResult{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -111,12 +120,10 @@ func ForumCategory(c *RequestContext) ResponseData {
if err != nil { if err != nil {
panic(oops.New(err, "failed to fetch threads")) panic(oops.New(err, "failed to fetch threads"))
} }
c.Perf.EndBlock()
defer itMainThreads.Close() defer itMainThreads.Close()
var threads []templates.ThreadListItem makeThreadListItem := func(row *threadQueryResult) templates.ThreadListItem {
for _, irow := range itMainThreads.ToSlice() {
row := irow.(*mainPostsQueryResult)
hasRead := false hasRead := false
if row.ThreadLastReadTime != nil && row.ThreadLastReadTime.After(row.LastPost.PostDate) { if row.ThreadLastReadTime != nil && row.ThreadLastReadTime.After(row.LastPost.PostDate) {
hasRead = true hasRead = true
@ -124,7 +131,7 @@ func ForumCategory(c *RequestContext) ResponseData {
hasRead = true hasRead = true
} }
threads = append(threads, templates.ThreadListItem{ return templates.ThreadListItem{
Title: row.Thread.Title, Title: row.Thread.Title,
Url: ThreadUrl(row.Thread, models.CatKindForum, categoryUrls[currentCatId]), Url: ThreadUrl(row.Thread, models.CatKindForum, categoryUrls[currentCatId]),
@ -134,45 +141,111 @@ func ForumCategory(c *RequestContext) ResponseData {
LastDate: row.LastPost.PostDate, LastDate: row.LastPost.PostDate,
Unread: !hasRead, Unread: !hasRead,
}) }
}
var threads []templates.ThreadListItem
for _, irow := range itMainThreads.ToSlice() {
row := irow.(*threadQueryResult)
threads = append(threads, makeThreadListItem(row))
} }
// --------------------- // ---------------------
// Subcategory things // Subcategory things
// --------------------- // ---------------------
//c.Perf.StartBlock("SQL", "Fetch subcategories") var subcats []forumSubcategoryData
//type queryResult struct { if page == 1 {
// Cat models.Category `db:"cat"` c.Perf.StartBlock("SQL", "Fetch subcategories")
//} type subcatQueryResult struct {
//itSubcats, err := db.Query(c.Context(), c.Conn, queryResult{}, Cat models.Category `db:"cat"`
// ` }
// WITH current AS ( itSubcats, err := db.Query(c.Context(), c.Conn, subcatQueryResult{},
// SELECT id `
// FROM handmade_category SELECT $columns
// WHERE FROM
// slug = $1 handmade_category AS cat
// AND kind = $2 WHERE
// AND project_id = $3 cat.parent_id = $1
// ) `,
// SELECT $columns currentCatId,
// FROM )
// handmade_category AS cat, if err != nil {
// current panic(oops.New(err, "failed to fetch subcategories"))
// WHERE }
// cat.id = current.id defer itSubcats.Close()
// OR cat.parent_id = current.id c.Perf.EndBlock()
// `,
// catSlug,
// models.CatKindForum,
// c.CurrentProject.ID,
//)
//if err != nil {
// panic(oops.New(err, "failed to fetch subcategories"))
//}
//c.Perf.EndBlock()
//_ = itSubcats // TODO: Actually query subcategory post data for _, irow := range itSubcats.ToSlice() {
catRow := irow.(*subcatQueryResult)
c.Perf.StartBlock("SQL", "Fetch count of subcategory threads")
numThreads, err := db.QueryInt(c.Context(), c.Conn,
`
SELECT COUNT(*)
FROM handmade_thread AS thread
WHERE
thread.category_id = $1
AND NOT thread.deleted
`,
catRow.Cat.ID,
)
if err != nil {
panic(oops.New(err, "failed to get count of threads"))
}
c.Perf.EndBlock()
c.Perf.StartBlock("SQL", "Fetch subcategory threads")
itThreads, err := db.Query(c.Context(), c.Conn, threadQueryResult{},
`
SELECT $columns
FROM
handmade_thread AS thread
JOIN handmade_post AS firstpost ON thread.first_id = firstpost.id
JOIN handmade_post AS lastpost ON thread.last_id = lastpost.id
LEFT JOIN auth_user AS firstuser ON firstpost.author_id = firstuser.id
LEFT JOIN auth_user AS lastuser ON lastpost.author_id = lastuser.id
LEFT JOIN handmade_threadlastreadinfo AS tlri ON (
tlri.thread_id = thread.id
AND tlri.user_id = $2
)
LEFT JOIN handmade_categorylastreadinfo AS clri ON (
clri.category_id = $1
AND clri.user_id = $2
)
WHERE
thread.category_id = $1
AND NOT thread.deleted
ORDER BY lastpost.postdate DESC
LIMIT 3
`,
catRow.Cat.ID,
currentUserId,
)
if err != nil {
panic(err)
}
defer itThreads.Close()
c.Perf.EndBlock()
var threads []templates.ThreadListItem
for _, irow := range itThreads.ToSlice() {
threadRow := irow.(*threadQueryResult)
threads = append(threads, makeThreadListItem(threadRow))
}
subcats = append(subcats, forumSubcategoryData{
Name: *catRow.Cat.Name,
Url: categoryUrls[catRow.Cat.ID],
Threads: threads,
TotalThreads: numThreads,
})
}
}
// ---------------------
// Template assembly
// ---------------------
baseData := getBaseData(c) baseData := getBaseData(c)
baseData.Title = *c.CurrentProject.Name + " Forums" baseData.Title = *c.CurrentProject.Name + " Forums"
@ -202,6 +275,7 @@ func ForumCategory(c *RequestContext) ResponseData {
NextUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page+1), NextUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page+1),
PreviousUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page-1), PreviousUrl: fmt.Sprintf("%s/%d", categoryUrls[currentCatId], page-1),
}, },
Subcategories: subcats,
}, c.Perf) }, c.Perf)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -73,7 +73,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool, perfCollector *perf.PerfCollector) htt
}) })
mainRoutes.GET(`^/feed(/(?P<page>.+)?)?$`, Feed) mainRoutes.GET(`^/feed(/(?P<page>.+)?)?$`, Feed)
mainRoutes.GET(`^/(?P<cats>forums(/[a-zA-Z]+?)*)(/(?P<page>\d+))?$`, ForumCategory) mainRoutes.GET(`^/(?P<cats>forums(/[^\d]+?)*)(/(?P<page>\d+))?$`, ForumCategory)
// mainRoutes.GET(`^/(?P<cats>forums(/cat)*)/t/(?P<threadid>\d+)/p/(?P<postid>\d+)$`, ForumPost) // mainRoutes.GET(`^/(?P<cats>forums(/cat)*)/t/(?P<threadid>\d+)/p/(?P<postid>\d+)$`, ForumPost)
mainRoutes.GET("^/assets/project.css$", ProjectCSS) mainRoutes.GET("^/assets/project.css$", ProjectCSS)