Added theme to context and added empty-avatar support

This commit is contained in:
Asaf Gartner 2021-05-25 16:12:20 +03:00
parent e5beb209c0
commit 9c19484333
14 changed files with 66 additions and 49 deletions

View File

@ -72,14 +72,6 @@ func ProjectUrlWithFragment(path string, query []Q, slug string, fragment string
return url.String() return url.String()
} }
func StaticUrl(path string, query []Q) string {
return Url(StaticPath+"/"+trim(path), query)
}
func StaticThemeUrl(path string, theme string, query []Q) string {
return Url(StaticThemePath+"/"+theme+"/"+trim(path), query)
}
func trim(path string) string { func trim(path string) string {
if len(path) > 0 && path[0] == '/' { if len(path) > 0 && path[0] == '/' {
return path[1:] return path[1:]

View File

@ -329,14 +329,17 @@ func TestProjectCSS(t *testing.T) {
} }
func TestPublic(t *testing.T) { func TestPublic(t *testing.T) {
AssertRegexMatch(t, BuildPublic("test"), RegexPublic, nil) AssertRegexMatch(t, BuildPublic("test", false), RegexPublic, nil)
AssertRegexMatch(t, BuildPublic("/test"), RegexPublic, nil) AssertRegexMatch(t, BuildPublic("/test", true), RegexPublic, nil)
AssertRegexMatch(t, BuildPublic("/test/"), RegexPublic, nil) AssertRegexMatch(t, BuildPublic("/test/", false), RegexPublic, nil)
AssertRegexMatch(t, BuildPublic("/test/thing/image.png"), RegexPublic, nil) AssertRegexMatch(t, BuildPublic("/test/thing/image.png", true), RegexPublic, nil)
assert.Panics(t, func() { BuildPublic("") }) assert.Panics(t, func() { BuildPublic("", false) })
assert.Panics(t, func() { BuildPublic("/") }) assert.Panics(t, func() { BuildPublic("/", false) })
assert.Panics(t, func() { BuildPublic("/thing//image.png") }) assert.Panics(t, func() { BuildPublic("/thing//image.png", false) })
assert.Panics(t, func() { BuildPublic("/thing/ /image.png") }) assert.Panics(t, func() { BuildPublic("/thing/ /image.png", false) })
assert.Panics(t, func() { BuildPublic("/thing/image.png?hello", false) })
AssertRegexMatch(t, BuildTheme("test.css", "light", true), RegexPublic, nil)
} }
func TestMarkRead(t *testing.T) { func TestMarkRead(t *testing.T) {

View File

@ -1,6 +1,7 @@
package hmnurl package hmnurl
import ( import (
"fmt"
"net/url" "net/url"
"regexp" "regexp"
"strconv" "strconv"
@ -577,11 +578,14 @@ func BuildProjectCSS(color string) string {
var RegexPublic = regexp.MustCompile("^/public/.+$") var RegexPublic = regexp.MustCompile("^/public/.+$")
func BuildPublic(filepath string) string { func BuildPublic(filepath string, cachebust bool) string {
filepath = strings.Trim(filepath, "/") filepath = strings.Trim(filepath, "/")
if len(strings.TrimSpace(filepath)) == 0 { if len(strings.TrimSpace(filepath)) == 0 {
panic(oops.New(nil, "Attempted to build a /public url with no path")) panic(oops.New(nil, "Attempted to build a /public url with no path"))
} }
if strings.Contains(filepath, "?") {
panic(oops.New(nil, "Public url failpath must not contain query params"))
}
var builder strings.Builder var builder strings.Builder
builder.WriteString("/public") builder.WriteString("/public")
pathParts := strings.Split(filepath, "/") pathParts := strings.Split(filepath, "/")
@ -593,7 +597,18 @@ func BuildPublic(filepath string) string {
builder.WriteRune('/') builder.WriteRune('/')
builder.WriteString(part) builder.WriteString(part)
} }
return Url(builder.String(), nil) var query []Q
if cachebust {
query = []Q{{"v", cacheBust}}
}
return Url(builder.String(), query)
}
func BuildTheme(filepath string, theme string, cachebust bool) string {
if len(theme) == 0 {
panic(oops.New(nil, "Theme can't be blank"))
}
return BuildPublic(fmt.Sprintf("themes/%s/%s", theme, strings.Trim(filepath, "/")), cachebust)
} }
/* /*

View File

@ -8,10 +8,10 @@ import (
"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, currentTheme string) Post {
var authorUser *User var authorUser *User
if author != nil { if author != nil {
authorTmpl := UserToTemplate(author) authorTmpl := UserToTemplate(author, currentTheme)
authorUser = &authorTmpl authorUser = &authorTmpl
} }
@ -31,11 +31,11 @@ func PostToTemplate(p *models.Post, author *models.User) Post {
} }
} }
func (p *Post) AddContentVersion(ver models.PostVersion, editor *models.User) { func (p *Post) AddContentVersion(ver models.PostVersion, editor *models.User, currentTheme string) {
p.Content = template.HTML(ver.TextParsed) p.Content = template.HTML(ver.TextParsed)
if editor != nil { if editor != nil {
editorTmpl := UserToTemplate(editor) editorTmpl := UserToTemplate(editor, currentTheme)
p.Editor = &editorTmpl p.Editor = &editorTmpl
p.EditDate = ver.EditDate p.EditDate = ver.EditDate
p.EditIP = maybeIp(ver.EditIP) p.EditIP = maybeIp(ver.EditIP)
@ -76,12 +76,14 @@ func ThreadToTemplate(t *models.Thread) Thread {
} }
} }
func UserToTemplate(u *models.User) User { func UserToTemplate(u *models.User, currentTheme string) 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.
avatar := "" avatar := ""
if u.Avatar != nil { if u.Avatar != nil && len(*u.Avatar) > 0 {
avatar = hmnurl.StaticUrl(*u.Avatar, nil) avatar = hmnurl.BuildPublic(*u.Avatar, false)
} else {
avatar = hmnurl.BuildTheme("empty-avatar.svg", currentTheme, true)
} }
name := u.Name name := u.Name
@ -99,7 +101,7 @@ func UserToTemplate(u *models.User) User {
Name: name, Name: name,
Blurb: u.Blurb, Blurb: u.Blurb,
Signature: u.Signature, Signature: u.Signature,
AvatarUrl: avatar, // TODO AvatarUrl: avatar,
ProfileUrl: hmnurl.Url("m/"+u.Username, nil), ProfileUrl: hmnurl.Url("m/"+u.Username, nil),
DarkTheme: u.DarkTheme, DarkTheme: u.DarkTheme,

View File

@ -86,7 +86,9 @@
<span class="pl3"> <span class="pl3">
Edited by Edited by
<a class="name" href="{{ .Editor.ProfileUrl }}" target="_blank">{{ coalesce .Editor.Name .Editor.Username }}</a> <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 }} {{ if $.User }}
{{ if and $.User.IsStaff .EditIP }}<span class="ip">[{{ .EditIP }}]</span>{{ end }}
{{ end }}
on {{ timehtml (absolutedate .EditDate) .EditDate }} on {{ timehtml (absolutedate .EditDate) .EditDate }}
{{ with .EditReason }} {{ with .EditReason }}
Reason: {{ . }} Reason: {{ . }}

View File

@ -149,28 +149,21 @@ var HMNTemplateFuncs = template.FuncMap{
} }
}, },
"static": func(filepath string) string { "static": func(filepath string) string {
return hmnurl.StaticUrl(filepath, []hmnurl.Q{{"v", cachebust}}) return hmnurl.BuildPublic(filepath, true)
}, },
"staticnobust": func(filepath string) string { "staticnobust": func(filepath string) string {
return hmnurl.StaticUrl(filepath, nil) return hmnurl.BuildPublic(filepath, false)
}, },
"statictheme": func(theme string, filepath string) string { "statictheme": func(theme string, filepath string) string {
return hmnurl.StaticThemeUrl(filepath, theme, []hmnurl.Q{{"v", cachebust}}) return hmnurl.BuildTheme(filepath, theme, true)
}, },
"staticthemenobust": func(theme string, filepath string) string { "staticthemenobust": func(theme string, filepath string) string {
return hmnurl.StaticThemeUrl(filepath, theme, nil) return hmnurl.BuildTheme(filepath, theme, false)
}, },
"timehtml": func(formatted string, t time.Time) template.HTML { "timehtml": func(formatted string, t time.Time) template.HTML {
iso := t.Format(time.RFC3339) iso := t.Format(time.RFC3339)
return template.HTML(fmt.Sprintf(`<time datetime="%s">%s</time>`, iso, formatted)) return template.HTML(fmt.Sprintf(`<time datetime="%s">%s</time>`, iso, formatted))
}, },
"url": func(url string) string {
return hmnurl.Url(url, nil)
},
"urlq": func(url string, query string) string {
absUrl := hmnurl.Url(url, nil)
return fmt.Sprintf("%s?%s", absUrl, query)
},
} }
type ErrInvalidHexColor struct { type ErrInvalidHexColor struct {

View File

@ -148,6 +148,7 @@ func Feed(c *RequestContext) ResponseData {
postResult.LibraryResource, postResult.LibraryResource,
!hasRead, !hasRead,
true, true,
c.Theme,
)) ))
} }
c.Perf.EndBlock() c.Perf.EndBlock()

View File

@ -141,9 +141,9 @@ func ForumCategory(c *RequestContext) ResponseData {
return templates.ThreadListItem{ return templates.ThreadListItem{
Title: row.Thread.Title, Title: row.Thread.Title,
Url: hmnurl.BuildForumThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(row.Thread.CategoryID), row.Thread.ID, row.Thread.Title, 1), Url: hmnurl.BuildForumThread(c.CurrentProject.Slug, lineageBuilder.GetSubforumLineageSlugs(row.Thread.CategoryID), row.Thread.ID, row.Thread.Title, 1),
FirstUser: templates.UserToTemplate(row.FirstUser), FirstUser: templates.UserToTemplate(row.FirstUser, c.Theme),
FirstDate: row.FirstPost.PostDate, FirstDate: row.FirstPost.PostDate,
LastUser: templates.UserToTemplate(row.LastUser), LastUser: templates.UserToTemplate(row.LastUser, c.Theme),
LastDate: row.LastPost.PostDate, LastDate: row.LastPost.PostDate,
Unread: !hasRead, Unread: !hasRead,
@ -403,8 +403,8 @@ func ForumThread(c *RequestContext) ResponseData {
for _, irow := range itPosts.ToSlice() { for _, irow := range itPosts.ToSlice() {
row := irow.(*postsQueryResult) row := irow.(*postsQueryResult)
post := templates.PostToTemplate(&row.Post, row.Author) post := templates.PostToTemplate(&row.Post, row.Author, c.Theme)
post.AddContentVersion(row.Ver, row.Editor) post.AddContentVersion(row.Ver, row.Editor, c.Theme)
post.AddUrls(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, post.ID) post.AddUrls(c.CurrentProject.Slug, currentSubforumSlugs, thread.ID, post.ID)
posts = append(posts, post) posts = append(posts, post)

View File

@ -183,7 +183,7 @@ func Index(c *RequestContext) ResponseData {
landingPageProject.FeaturedPost = &LandingPageFeaturedPost{ landingPageProject.FeaturedPost = &LandingPageFeaturedPost{
Title: projectPost.Thread.Title, Title: projectPost.Thread.Title,
Url: hmnurl.BuildBlogPost(proj.Slug, projectPost.Thread.ID, projectPost.Post.ID), Url: hmnurl.BuildBlogPost(proj.Slug, projectPost.Thread.ID, projectPost.Post.ID),
User: templates.UserToTemplate(&projectPost.User), User: templates.UserToTemplate(&projectPost.User, c.Theme),
Date: projectPost.Post.PostDate, Date: projectPost.Post.PostDate,
Unread: !hasRead, Unread: !hasRead,
Content: template.HTML(content), Content: template.HTML(content),
@ -200,6 +200,7 @@ func Index(c *RequestContext) ResponseData {
projectPost.LibraryResource, projectPost.LibraryResource,
!hasRead, !hasRead,
false, false,
c.Theme,
), ),
) )
} }
@ -293,7 +294,7 @@ func Index(c *RequestContext) ResponseData {
NewsPost: LandingPageFeaturedPost{ NewsPost: LandingPageFeaturedPost{
Title: newsPostResult.Thread.Title, Title: newsPostResult.Thread.Title,
Url: hmnurl.BuildBlogPost(models.HMNProjectSlug, newsPostResult.Thread.ID, newsPostResult.Post.ID), Url: hmnurl.BuildBlogPost(models.HMNProjectSlug, newsPostResult.Thread.ID, newsPostResult.Post.ID),
User: templates.UserToTemplate(&newsPostResult.User), User: templates.UserToTemplate(&newsPostResult.User, c.Theme),
Date: newsPostResult.Post.PostDate, Date: newsPostResult.Post.PostDate,
Unread: true, // TODO Unread: true, // TODO
Content: template.HTML(newsPostResult.PostVersion.TextParsed), Content: template.HTML(newsPostResult.PostVersion.TextParsed),

View File

@ -30,11 +30,11 @@ func UrlForGenericPost(post *models.Post, subforums []string, threadTitle string
} }
// NOTE(asaf): THIS DOESN'T HANDLE WIKI EDIT ITEMS. Wiki edits are PostTextVersions, not Posts. // NOTE(asaf): THIS DOESN'T HANDLE WIKI EDIT ITEMS. Wiki edits are PostTextVersions, not Posts.
func MakePostListItem(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, thread *models.Thread, post *models.Post, user *models.User, libraryResource *models.LibraryResource, unread bool, includeBreadcrumbs bool) templates.PostListItem { func MakePostListItem(lineageBuilder *models.CategoryLineageBuilder, project *models.Project, thread *models.Thread, post *models.Post, user *models.User, libraryResource *models.LibraryResource, unread bool, includeBreadcrumbs bool, currentTheme string) templates.PostListItem {
var result templates.PostListItem var result templates.PostListItem
result.Title = thread.Title result.Title = thread.Title
result.User = templates.UserToTemplate(user) result.User = templates.UserToTemplate(user, currentTheme)
result.Date = post.PostDate result.Date = post.PostDate
result.Unread = unread result.Unread = unread
libraryResourceId := 0 libraryResourceId := 0

View File

@ -121,6 +121,7 @@ type RequestContext struct {
Conn *pgxpool.Pool Conn *pgxpool.Pool
CurrentProject *models.Project CurrentProject *models.Project
CurrentUser *models.User CurrentUser *models.User
Theme string
Perf *perf.RequestPerf Perf *perf.RequestPerf
} }

View File

@ -133,7 +133,7 @@ func getBaseData(c *RequestContext) templates.BaseData {
Project: templates.ProjectToTemplate(c.CurrentProject), Project: templates.ProjectToTemplate(c.CurrentProject),
LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()), LoginPageUrl: hmnurl.BuildLoginPage(c.FullUrl()),
User: templateUser, User: templateUser,
Theme: "light", Theme: c.Theme,
ProjectCSSUrl: hmnurl.BuildProjectCSS(c.CurrentProject.Color1), ProjectCSSUrl: hmnurl.BuildProjectCSS(c.CurrentProject.Color1),
Header: templates.Header{ Header: templates.Header{
AdminUrl: hmnurl.BuildHomepage(), // TODO(asaf) AdminUrl: hmnurl.BuildHomepage(), // TODO(asaf)
@ -279,6 +279,13 @@ func LoadCommonWebsiteData(c *RequestContext) (bool, ResponseData) {
// http.ErrNoCookie is the only error Cookie ever returns, so no further handling to do here. // http.ErrNoCookie is the only error Cookie ever returns, so no further handling to do here.
} }
theme := "light"
if c.CurrentUser != nil && c.CurrentUser.DarkTheme {
theme = "dark"
}
c.Theme = theme
return true, ResponseData{} return true, ResponseData{}
} }