Added theme to context and added empty-avatar support
This commit is contained in:
parent
e5beb209c0
commit
9c19484333
|
@ -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:]
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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: {{ . }}
|
||||||
|
|
|
@ -26,4 +26,4 @@ It should be called with PostListItem.
|
||||||
<div class="goto">
|
<div class="goto">
|
||||||
<a href="{{ .Url }}">»</a>
|
<a href="{{ .Url }}">»</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,4 +33,4 @@ It should be called with ThreadListItem.
|
||||||
<div class="goto">
|
<div class="goto">
|
||||||
<a href="{{ .Url }}">»</a>
|
<a href="{{ .Url }}">»</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue