Get forum threads mostly implemented
Still several TODOs in the handler and templates
This commit is contained in:
parent
b217cd5592
commit
71a46ba5a1
|
@ -0,0 +1,59 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(DeleteBadUnreadInfo{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteBadUnreadInfo struct{}
|
||||||
|
|
||||||
|
func (m DeleteBadUnreadInfo) Version() types.MigrationVersion {
|
||||||
|
return types.MigrationVersion(time.Date(2021, 5, 4, 0, 12, 58, 0, time.UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DeleteBadUnreadInfo) Name() string {
|
||||||
|
return "DeleteBadUnreadInfo"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DeleteBadUnreadInfo) Description() string {
|
||||||
|
return "Delete invalid tlri and clri"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DeleteBadUnreadInfo) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
threadNullsResult, err := tx.Exec(ctx, `
|
||||||
|
DELETE FROM handmade_threadlastreadinfo
|
||||||
|
WHERE
|
||||||
|
lastread IS NULL
|
||||||
|
OR thread_id IS NULL;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to delete thread entries with null fields")
|
||||||
|
}
|
||||||
|
fmt.Printf("Deleted %d thread entries with null fields\n", threadNullsResult.RowsAffected())
|
||||||
|
|
||||||
|
catNullsResult, err := tx.Exec(ctx, `
|
||||||
|
DELETE FROM handmade_categorylastreadinfo
|
||||||
|
WHERE
|
||||||
|
lastread IS NULL
|
||||||
|
OR category_id IS NULL;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to delete category entries with null fields")
|
||||||
|
}
|
||||||
|
fmt.Printf("Deleted %d category entries with null fields\n", catNullsResult.RowsAffected())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m DeleteBadUnreadInfo) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
panic("Implement me")
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/migration/types"
|
||||||
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerMigration(AddUnreadInfoConstraints{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddUnreadInfoConstraints struct{}
|
||||||
|
|
||||||
|
func (m AddUnreadInfoConstraints) Version() types.MigrationVersion {
|
||||||
|
return types.MigrationVersion(time.Date(2021, 5, 4, 0, 29, 52, 0, time.UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m AddUnreadInfoConstraints) Name() string {
|
||||||
|
return "AddUnreadInfoConstraints"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m AddUnreadInfoConstraints) Description() string {
|
||||||
|
return "Add more constraints to the unread info tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m AddUnreadInfoConstraints) Up(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
_, err := tx.Exec(ctx, `
|
||||||
|
ALTER TABLE handmade_threadlastreadinfo
|
||||||
|
ALTER lastread SET NOT NULL,
|
||||||
|
ALTER thread_id SET NOT NULL,
|
||||||
|
DROP category_id,
|
||||||
|
ADD UNIQUE (thread_id, user_id);
|
||||||
|
|
||||||
|
ALTER TABLE handmade_categorylastreadinfo
|
||||||
|
ALTER lastread SET NOT NULL,
|
||||||
|
ALTER category_id SET NOT NULL,
|
||||||
|
ADD UNIQUE (category_id, user_id);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return oops.New(err, "failed to add constraints")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m AddUnreadInfoConstraints) Down(ctx context.Context, tx pgx.Tx) error {
|
||||||
|
panic("Implement me")
|
||||||
|
}
|
|
@ -1,25 +1,37 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PostToTemplate(p *models.Post, author models.User) Post {
|
func PostToTemplate(p *models.Post, author *models.User) Post {
|
||||||
|
var authorUser *User
|
||||||
|
if author != nil {
|
||||||
|
authorTmpl := UserToTemplate(author)
|
||||||
|
authorUser = &authorTmpl
|
||||||
|
}
|
||||||
|
|
||||||
return Post{
|
return Post{
|
||||||
Author: UserToTemplate(&author),
|
ID: p.ID,
|
||||||
|
Url: "nope", // TODO
|
||||||
|
|
||||||
Preview: p.Preview,
|
Preview: p.Preview,
|
||||||
ReadOnly: p.ReadOnly,
|
ReadOnly: p.ReadOnly,
|
||||||
|
|
||||||
|
Author: authorUser,
|
||||||
// No content. Do it yourself if you care.
|
// No content. Do it yourself if you care.
|
||||||
|
PostDate: p.PostDate,
|
||||||
|
|
||||||
IP: p.IP.String(),
|
IP: p.IP.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostToTemplateWithContent(p *models.Post, author models.User, content string) Post {
|
func PostToTemplateWithContent(p *models.Post, author *models.User, content string) Post {
|
||||||
post := PostToTemplate(p, author)
|
post := PostToTemplate(p, author)
|
||||||
post.Content = content
|
post.Content = template.HTML(content)
|
||||||
|
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
@ -40,6 +52,14 @@ func ProjectToTemplate(p *models.Project) Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ThreadToTemplate(t *models.Thread) Thread {
|
||||||
|
return Thread{
|
||||||
|
Title: t.Title,
|
||||||
|
Locked: t.Locked,
|
||||||
|
Sticky: t.Sticky,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UserToTemplate(u *models.User) User {
|
func UserToTemplate(u *models.User) User {
|
||||||
// TODO: Handle deleted users. Maybe not here, but if not, at call sites of this function.
|
// TODO: Handle deleted users. Maybe not here, but if not, at call sites of this function.
|
||||||
|
|
||||||
|
@ -54,6 +74,7 @@ func UserToTemplate(u *models.User) User {
|
||||||
}
|
}
|
||||||
|
|
||||||
return User{
|
return User{
|
||||||
|
ID: u.ID,
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
Email: u.Email,
|
Email: u.Email,
|
||||||
IsSuperuser: u.IsSuperuser,
|
IsSuperuser: u.IsSuperuser,
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="content-block">
|
||||||
|
{{ range .Posts }}
|
||||||
|
<div class="post background-even pa3 bbcode"> {{/* TODO: Dynamically switch between bbcode and markdown */}}
|
||||||
|
<div class="fl w-100 w-25-l pt3 pa3-l flex tc-l">
|
||||||
|
{{ if .Author }}
|
||||||
|
<div class="fl w-20 mw3 dn-l w3">
|
||||||
|
<!-- Mobile avatar -->
|
||||||
|
<div class="aspect-ratio--1x1 contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
|
||||||
|
</div>
|
||||||
|
<div class="w-100-l pl3 pl0-l flex flex-column items-center-l">
|
||||||
|
<div>
|
||||||
|
<a class="username" href="{{ .Author.ProfileUrl }}" target="_blank">{{ .Author.Username }}</a> {{/* TODO: Text scale stuff? Seems unnecessary. */}}
|
||||||
|
<!-- Mobile badges -->
|
||||||
|
<div class="di dn-l ph1">
|
||||||
|
{{ if .Author.IsStaff }}
|
||||||
|
<div class="badge staff"></div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ if and .Author.Name (ne .Author.Name .Author.Username) }}
|
||||||
|
<div class="c--dim f7"> {{ .Author.Name }} </div>
|
||||||
|
{{ end }}
|
||||||
|
<!-- Large avatar -->
|
||||||
|
<div class="dn db-l w-60 pv2">
|
||||||
|
<div class="aspect-ratio--1x1 contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
|
||||||
|
</div>
|
||||||
|
{{/* TODO: Aggregate user data
|
||||||
|
<div class="c--dim f7">
|
||||||
|
{{ post.author.posts }} posts
|
||||||
|
{% if post.author.public_projects.values|length > 0 %}
|
||||||
|
/ {{ post.author.public_projects.values|length }} project{%if post.author.public_projects.values|length > 1 %}s{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div> */}}
|
||||||
|
<!-- Large badges -->
|
||||||
|
<div class="dn db-l pv2">
|
||||||
|
{{ if .Author.IsStaff }}
|
||||||
|
<div class="badge staff"></div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="i c--dim f7">
|
||||||
|
{{ if .Author.Blurb }}
|
||||||
|
{{ .Author.Blurb }} {{/* TODO: Linebreaks? */}}
|
||||||
|
{{ else if .Author.Bio }}
|
||||||
|
{{ .Author.Bio }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="username">Deleted member</div>
|
||||||
|
<div class="avatar" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="fl w-100 w-75-l pv3 pa3-l">
|
||||||
|
<div class="w-100 flex-l flex-row-reverse-l">
|
||||||
|
<div class="inline-flex flex-row-reverse pl3-l pb3 items-center">
|
||||||
|
<span class="postid">
|
||||||
|
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
|
||||||
|
</span>
|
||||||
|
{{ if $.User }}
|
||||||
|
<div class="flex pr3">
|
||||||
|
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
|
||||||
|
<a class="delete action button" href="{{ .Url }}/delete" title="Delete">✖</a>
|
||||||
|
<a class="edit action button" href="{{ .Url }}/edit" title="Edit">✎</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ if or (not $.Thread.Locked) $.User.IsStaff }}
|
||||||
|
{{ if $.Thread.Locked }}
|
||||||
|
WARNING: locked thread - use power responsibly!
|
||||||
|
{{ end }}
|
||||||
|
<a class="reply action button" href="{{ .Url }}/reply" title="Reply">↪</a>
|
||||||
|
<a class="quote action button" href="{{ .Url }}/quote" title="Quote">❝</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="w-100 pb3">
|
||||||
|
<div class="b" role="heading" aria-level="2">{{ $.Thread.Title }}</div>
|
||||||
|
<span>{{ relativedate .PostDate }} ago</span>
|
||||||
|
{{ if .Editor }}
|
||||||
|
<span class="pl3">
|
||||||
|
Edited by
|
||||||
|
<a class="name" href="{{ .Editor.ProfileUrl }}" target="_blank">{{ coalesce .Editor.Name .Editor.Username }}</a>
|
||||||
|
{{ if and $.User.IsStaff .EditIP }}<span class="ip">[{{ .EditIP }}]</span>{{ end }}
|
||||||
|
on <span class="editdate">{{ .EditDate }}</span>
|
||||||
|
{{ with .EditReason }}
|
||||||
|
Reason: {{ . }}
|
||||||
|
{{ end }}
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ if $.User }}
|
||||||
|
{{ if $.User.IsStaff }}
|
||||||
|
<span>[{{ .IP }}]</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contents overflow-x-auto">
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
{{/* {% if post.author.signature|length %}
|
||||||
|
<div class="signature"><hr />
|
||||||
|
{{ post.author.signature|bbdecode|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %} */}}
|
||||||
|
</div>
|
||||||
|
<div class="cb"></div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
|
@ -43,7 +43,7 @@ a:hover, button:hover, .button:hover, input[type=button]:hover, input[type=submi
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-list-bg-odd:nth-of-type(odd) {
|
.post-list-bg-odd:nth-of-type(odd) {
|
||||||
background-color: {{ .PostListBgColor }};
|
background-color: {{ .PostBgColor }};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -96,7 +96,7 @@ all of this CSS.
|
||||||
{% endif %} */
|
{% endif %} */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background-even-background: {{ eq .Theme "dark" | ternary (lightness 0.15 $c) (lightness 0.95 $c) | color2css }};
|
--background-even-background: {{ .PostBgColor }};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assets */
|
/* Assets */
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"html/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type BaseData struct {
|
type BaseData struct {
|
||||||
Title string
|
Title string
|
||||||
|
@ -23,11 +26,20 @@ type Thread struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Author User
|
ID int
|
||||||
|
Url string
|
||||||
|
|
||||||
Preview string
|
Preview string
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
|
|
||||||
Content string
|
Author *User
|
||||||
|
Content template.HTML
|
||||||
|
PostDate time.Time
|
||||||
|
|
||||||
|
Editor *User
|
||||||
|
EditDate time.Time
|
||||||
|
EditIP string
|
||||||
|
EditReason string
|
||||||
|
|
||||||
IP string
|
IP string
|
||||||
}
|
}
|
||||||
|
@ -47,6 +59,7 @@ type Project struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
ID int
|
||||||
Username string
|
Username string
|
||||||
Email string
|
Email string
|
||||||
IsSuperuser bool
|
IsSuperuser bool
|
||||||
|
@ -54,6 +67,7 @@ type User struct {
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
Blurb string
|
Blurb string
|
||||||
|
Bio string
|
||||||
Signature string
|
Signature string
|
||||||
AvatarUrl string
|
AvatarUrl string
|
||||||
ProfileUrl string
|
ProfileUrl string
|
||||||
|
|
|
@ -2,6 +2,7 @@ package website
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -284,6 +285,100 @@ func ForumCategory(c *RequestContext) ResponseData {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type forumThreadData struct {
|
||||||
|
templates.BaseData
|
||||||
|
Thread templates.Thread
|
||||||
|
Posts []templates.Post
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForumThread(c *RequestContext) ResponseData {
|
||||||
|
const postsPerPage = 15
|
||||||
|
|
||||||
|
threadId, err := strconv.Atoi(c.PathParams["threadid"])
|
||||||
|
if err != nil {
|
||||||
|
return FourOhFour(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Perf.StartBlock("SQL", "Fetch current thread")
|
||||||
|
type threadQueryResult struct {
|
||||||
|
Thread models.Thread `db:"thread"`
|
||||||
|
}
|
||||||
|
irow, err := db.QueryOne(c.Context(), c.Conn, threadQueryResult{},
|
||||||
|
`
|
||||||
|
SELECT $columns
|
||||||
|
FROM handmade_thread AS thread
|
||||||
|
WHERE thread.id = $1
|
||||||
|
`,
|
||||||
|
threadId,
|
||||||
|
)
|
||||||
|
c.Perf.EndBlock()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, db.ErrNoMatchingRows) {
|
||||||
|
return FourOhFour(c)
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread := irow.(*threadQueryResult).Thread
|
||||||
|
|
||||||
|
categoryUrls := GetProjectCategoryUrls(c.Context(), c.Conn, c.CurrentProject.ID)
|
||||||
|
|
||||||
|
page, numPages, ok := getPageInfo(c.PathParams["page"], 100, postsPerPage) // TODO: Not 100
|
||||||
|
if !ok {
|
||||||
|
urlNoPage := ThreadUrl(thread, models.CatKindForum, categoryUrls[thread.CategoryID])
|
||||||
|
return c.Redirect(urlNoPage, http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
_ = numPages // TODO
|
||||||
|
|
||||||
|
c.Perf.StartBlock("SQL", "Fetch posts")
|
||||||
|
type postsQueryResult struct {
|
||||||
|
Post models.Post `db:"post"`
|
||||||
|
Content string `db:"ver.text_parsed"`
|
||||||
|
Author *models.User `db:"author"`
|
||||||
|
}
|
||||||
|
itPosts, err := db.Query(c.Context(), c.Conn, postsQueryResult{},
|
||||||
|
`
|
||||||
|
SELECT $columns
|
||||||
|
FROM
|
||||||
|
handmade_post AS post
|
||||||
|
JOIN handmade_postversion AS ver ON post.current_id = ver.id
|
||||||
|
LEFT JOIN auth_user AS author ON post.author_id = author.id
|
||||||
|
WHERE
|
||||||
|
post.thread_id = $1
|
||||||
|
ORDER BY postdate
|
||||||
|
LIMIT $2 OFFSET $3
|
||||||
|
`,
|
||||||
|
thread.ID,
|
||||||
|
postsPerPage,
|
||||||
|
(page-1)*postsPerPage,
|
||||||
|
)
|
||||||
|
c.Perf.EndBlock()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer itPosts.Close()
|
||||||
|
|
||||||
|
var posts []templates.Post
|
||||||
|
for _, irow := range itPosts.ToSlice() {
|
||||||
|
row := irow.(*postsQueryResult)
|
||||||
|
posts = append(posts, templates.PostToTemplateWithContent(&row.Post, row.Author, row.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
baseData := getBaseData(c)
|
||||||
|
|
||||||
|
var res ResponseData
|
||||||
|
err = res.WriteTemplate("forum_thread.html", forumThreadData{
|
||||||
|
BaseData: baseData,
|
||||||
|
Thread: templates.ThreadToTemplate(&thread),
|
||||||
|
Posts: posts,
|
||||||
|
}, c.Perf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func fetchCatIdFromSlugs(ctx context.Context, conn *pgxpool.Pool, catSlugs []string, projectId int) int {
|
func fetchCatIdFromSlugs(ctx context.Context, conn *pgxpool.Pool, catSlugs []string, projectId int) int {
|
||||||
if len(catSlugs) == 1 {
|
if len(catSlugs) == 1 {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package website
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPageInfo(
|
||||||
|
pageParam string,
|
||||||
|
totalItems int,
|
||||||
|
itemsPerPage int,
|
||||||
|
) (
|
||||||
|
page int,
|
||||||
|
totalPages int,
|
||||||
|
ok bool,
|
||||||
|
) {
|
||||||
|
totalPages = int(math.Ceil(float64(totalItems) / float64(itemsPerPage)))
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
page = 1
|
||||||
|
if pageParam != "" {
|
||||||
|
if pageParsed, err := strconv.Atoi(pageParam); err == nil {
|
||||||
|
page = pageParsed
|
||||||
|
} else {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if page < 1 || totalPages < page {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -73,8 +73,9 @@ 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(/[^\d]+?)*)(/(?P<page>\d+))?$`, ForumCategory)
|
mainRoutes.GET(`^/(?P<cats>forums(/[^\d]+?)*)/t/(?P<threadid>\d+)(/(?P<page>\d+))?$`, ForumThread)
|
||||||
// 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(`^/(?P<cats>forums(/[^\d]+?)*)(/(?P<page>\d+))?$`, ForumCategory)
|
||||||
|
|
||||||
mainRoutes.GET("^/assets/project.css$", ProjectCSS)
|
mainRoutes.GET("^/assets/project.css$", ProjectCSS)
|
||||||
|
|
||||||
|
@ -145,12 +146,12 @@ func ProjectCSS(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
templateData := struct {
|
templateData := struct {
|
||||||
templates.BaseData
|
templates.BaseData
|
||||||
Color string
|
Color string
|
||||||
PostListBgColor string
|
PostBgColor string
|
||||||
}{
|
}{
|
||||||
BaseData: baseData,
|
BaseData: baseData,
|
||||||
Color: color,
|
Color: color,
|
||||||
PostListBgColor: bgColor.HTML(),
|
PostBgColor: bgColor.HTML(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
|
|
Loading…
Reference in New Issue