Make progress on the landing page
This commit is contained in:
parent
6ed2bd0c02
commit
d7c512f1c8
|
@ -8587,16 +8587,6 @@ input[type=submit] {
|
|||
color: var(--forum-thread-read-link-color); }
|
||||
.thread.read .title {
|
||||
font-weight: 500; }
|
||||
.thread .goto {
|
||||
font-size: 200%;
|
||||
width: 30px; }
|
||||
.thread .goto a {
|
||||
display: block;
|
||||
padding: 0px 10px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
line-height: 100%;
|
||||
background-color: transparent; }
|
||||
.forum .thread .info th {
|
||||
width: 50px; }
|
||||
|
||||
|
@ -8621,6 +8611,17 @@ input[type=submit] {
|
|||
left: 0px;
|
||||
bottom: -10px; }
|
||||
|
||||
.goto {
|
||||
font-size: 200%;
|
||||
width: 30px; }
|
||||
.goto a {
|
||||
display: block;
|
||||
padding: 0px 10px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
line-height: 100%;
|
||||
background-color: transparent; }
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border-radius: 1000em;
|
||||
|
|
|
@ -169,7 +169,7 @@ func getColumnNamesAndPaths(destType reflect.Type, pathSoFar []int, prefix strin
|
|||
fieldType = destType.Elem()
|
||||
}
|
||||
|
||||
_, isRecognizedByPgtype := connInfo.DataTypeForValue(reflect.New(fieldType)) // if pgtype recognizes it, we don't need to dig in further for more `db` tags
|
||||
_, isRecognizedByPgtype := connInfo.DataTypeForValue(reflect.New(fieldType).Elem().Interface()) // if pgtype recognizes it, we don't need to dig in further for more `db` tags
|
||||
// NOTE: boy it would be nice if we didn't have to do reflect.New here, considering that pgtype is just doing reflection on the value anyway
|
||||
|
||||
if fieldType.Kind() == reflect.Struct && !isRecognizedByPgtype {
|
||||
|
|
|
@ -55,7 +55,7 @@ func StaticThemeUrl(path string, theme string, query []Q) string {
|
|||
}
|
||||
|
||||
func trim(path string) string {
|
||||
if path[0] == '/' {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
return path[1:]
|
||||
}
|
||||
return path
|
||||
|
|
|
@ -17,7 +17,7 @@ type Post struct {
|
|||
|
||||
Depth int `db:"depth"`
|
||||
Slug string `db:"slug"`
|
||||
AuthorName string `db:"author_name"`
|
||||
AuthorName string `db:"author_name"` // TODO: Drop this.
|
||||
PostDate time.Time `db:"postdate"`
|
||||
IP net.IPNet `db:"ip"`
|
||||
Sticky bool `db:"sticky"`
|
||||
|
|
|
@ -26,3 +26,11 @@ type Project struct {
|
|||
func (p *Project) IsHMN() bool {
|
||||
return p.ID == HMNProjectID
|
||||
}
|
||||
|
||||
func (p *Project) Subdomain() string {
|
||||
if p.IsHMN() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *p.Slug
|
||||
}
|
||||
|
|
|
@ -63,20 +63,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.goto {
|
||||
font-size: 200%;
|
||||
width: 30px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0px 10px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
line-height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.forum & .info th {
|
||||
width: 50px;
|
||||
}
|
||||
|
@ -113,6 +99,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.goto {
|
||||
font-size: 200%;
|
||||
width: 30px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0px 10px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
line-height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border-radius: 1000em;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package templates
|
||||
|
||||
import "git.handmade.network/hmn/hmn/src/models"
|
||||
import (
|
||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
)
|
||||
|
||||
func PostToTemplate(p *models.Post) Post {
|
||||
return Post{
|
||||
|
@ -12,7 +15,7 @@ func PostToTemplate(p *models.Post) Post {
|
|||
func ProjectToTemplate(p *models.Project) Project {
|
||||
return Project{
|
||||
Name: maybeString(p.Name),
|
||||
Subdomain: maybeString(p.Slug),
|
||||
Subdomain: p.Subdomain(),
|
||||
Color1: p.Color1,
|
||||
Color2: p.Color2,
|
||||
|
||||
|
@ -26,15 +29,27 @@ func ProjectToTemplate(p *models.Project) Project {
|
|||
}
|
||||
|
||||
func UserToTemplate(u *models.User) User {
|
||||
avatar := ""
|
||||
if u.Avatar != nil {
|
||||
avatar = hmnurl.StaticUrl(*u.Avatar, nil)
|
||||
}
|
||||
|
||||
name := u.Name
|
||||
if u.Name == "" {
|
||||
name = u.Username
|
||||
}
|
||||
|
||||
return User{
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
IsSuperuser: u.IsSuperuser,
|
||||
IsStaff: u.IsStaff,
|
||||
|
||||
Name: u.Name,
|
||||
Blurb: u.Blurb,
|
||||
Signature: u.Signature,
|
||||
Name: name,
|
||||
Blurb: u.Blurb,
|
||||
Signature: u.Signature,
|
||||
AvatarUrl: avatar, // TODO
|
||||
ProfileUrl: hmnurl.Url("m/"+u.Username, nil),
|
||||
|
||||
DarkTheme: u.DarkTheme,
|
||||
Timezone: u.Timezone,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{{/*
|
||||
This template is intended to display a single post or thread in the context of a forum, the feed, or a similar layout.
|
||||
|
||||
It should be called with PostListItemData.
|
||||
*/}}
|
||||
|
||||
<div data-tmpl="container" class="flex items-center ph3 pv2">
|
||||
<img class="avatar-icon mr2" src="{{ .User.AvatarUrl }}">
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="breadcrumbs">
|
||||
{{ range $i, $breadcrumb := .Breadcrumbs }}
|
||||
{{ if gt $i 0 }} » {{ end }}
|
||||
<a href="{{ $breadcrumb.Url }}">{{ $breadcrumb.Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="title nowrap truncate"><a href="{{ .Url }}">{{ .Title }}</a></div>
|
||||
<div class="details">
|
||||
<a class="user" href="{{ .User.ProfileUrl }}">{{ .User.Name }}</a> — <span class="datetime">{{ relativedate .Date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="goto">
|
||||
<a href="{{ .Url }}">»</a>
|
||||
</div>
|
||||
</div>
|
|
@ -66,15 +66,8 @@
|
|||
{% endif %}
|
||||
*/}}
|
||||
|
||||
{{ range $posts }}
|
||||
<div>
|
||||
{{ .Post.Preview }}
|
||||
</div>
|
||||
{{/*
|
||||
{% if forloop.counter0 < max_posts %}
|
||||
{% include "thread_list_entry.html" with thread=post.thread %}
|
||||
{% endif %}
|
||||
*/}}
|
||||
{{ range $post := $posts }}
|
||||
{{ template "post_list_item.html" $post }}
|
||||
{{ end }}
|
||||
{{/*
|
||||
{% with more=posts|length|add:-5|clamp_lower:0 %}
|
||||
|
|
|
@ -14,6 +14,13 @@ import (
|
|||
"github.com/teacat/noire"
|
||||
)
|
||||
|
||||
const (
|
||||
Dayish = time.Hour * 24
|
||||
Weekish = Dayish * 7
|
||||
Monthish = Dayish * 30
|
||||
Yearish = Dayish * 365
|
||||
)
|
||||
|
||||
//go:embed src
|
||||
var templateFs embed.FS
|
||||
var Templates map[string]*template.Template
|
||||
|
@ -96,6 +103,44 @@ var HMNTemplateFuncs = template.FuncMap{
|
|||
}
|
||||
return query.Encode()
|
||||
},
|
||||
"relativedate": func(t time.Time) string {
|
||||
// TODO: Support relative future dates
|
||||
|
||||
// NOTE(asaf): Months and years aren't exactly accurate, but good enough for now I guess.
|
||||
str := func(primary int, primaryName string, secondary int, secondaryName string) string {
|
||||
result := fmt.Sprintf("%d %s", primary, primaryName)
|
||||
if primary != 1 {
|
||||
result += "s"
|
||||
}
|
||||
if secondary > 0 {
|
||||
result += fmt.Sprintf(", %d %s", secondary, secondaryName)
|
||||
|
||||
if secondary != 1 {
|
||||
result += "s"
|
||||
}
|
||||
}
|
||||
|
||||
return result + " ago"
|
||||
}
|
||||
|
||||
delta := time.Now().Sub(t)
|
||||
|
||||
if delta < time.Minute {
|
||||
return "Less than a minute ago"
|
||||
} else if delta < time.Hour {
|
||||
return str(int(delta.Minutes()), "minute", 0, "")
|
||||
} else if delta < Dayish {
|
||||
return str(int(delta/time.Hour), "hour", int((delta%time.Hour)/time.Minute), "minute")
|
||||
} else if delta < Weekish {
|
||||
return str(int(delta/Dayish), "day", int((delta%Dayish)/time.Hour), "hour")
|
||||
} else if delta < Monthish {
|
||||
return str(int(delta/Weekish), "week", int((delta%Weekish)/Dayish), "day")
|
||||
} else if delta < Yearish {
|
||||
return str(int(delta/Monthish), "month", int((delta%Monthish)/Weekish), "week")
|
||||
} else {
|
||||
return str(int(delta/Yearish), "year", int((delta%Yearish)/Monthish), "month")
|
||||
}
|
||||
},
|
||||
"static": func(filepath string) string {
|
||||
return hmnurl.StaticUrl(filepath, []hmnurl.Q{{"v", cachebust}})
|
||||
},
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package templates
|
||||
|
||||
import "time"
|
||||
|
||||
type BaseData struct {
|
||||
Title string
|
||||
CanonicalLink string
|
||||
|
@ -12,7 +14,16 @@ type BaseData struct {
|
|||
User *User
|
||||
}
|
||||
|
||||
type Thread struct {
|
||||
Title string
|
||||
|
||||
Locked bool
|
||||
Sticky bool
|
||||
Moderated bool
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Author User
|
||||
Preview string
|
||||
ReadOnly bool
|
||||
|
||||
|
@ -39,10 +50,11 @@ type User struct {
|
|||
IsSuperuser bool
|
||||
IsStaff bool
|
||||
|
||||
Name string
|
||||
Blurb string
|
||||
Signature string
|
||||
// TODO: Avatar??
|
||||
Name string
|
||||
Blurb string
|
||||
Signature string
|
||||
AvatarUrl string
|
||||
ProfileUrl string
|
||||
|
||||
DarkTheme bool
|
||||
Timezone string
|
||||
|
@ -64,3 +76,17 @@ type BackgroundImage struct {
|
|||
Url string
|
||||
Size string // A valid CSS background-size value
|
||||
}
|
||||
|
||||
// Data from post_list_item.html
|
||||
type PostListItem struct {
|
||||
Title string
|
||||
Url string
|
||||
Breadcrumbs []Breadcrumb
|
||||
User User
|
||||
Date time.Time
|
||||
Unread bool
|
||||
}
|
||||
|
||||
type Breadcrumb struct {
|
||||
Name, Url string
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
)
|
||||
|
||||
func PostUrl(post models.Post, catType models.CategoryType, subdomain string) string {
|
||||
switch catType {
|
||||
// TODO: All the relevant post types. Maybe it doesn't make sense to lump them all together here.
|
||||
case models.CatTypeBlog:
|
||||
return hmnurl.ProjectUrl(fmt.Sprintf("blogs/p/%d/e/%d", *post.ThreadID, post.ID), nil, subdomain)
|
||||
case models.CatTypeForum:
|
||||
return hmnurl.ProjectUrl(fmt.Sprintf("forums/t/%d/p/%d", *post.ThreadID, post.ID), nil, subdomain)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
|
@ -19,13 +19,8 @@ type LandingTemplateData struct {
|
|||
|
||||
type LandingPageProject struct {
|
||||
Project templates.Project
|
||||
FeaturedPost *LandingPagePost
|
||||
Posts []LandingPagePost
|
||||
}
|
||||
|
||||
type LandingPagePost struct {
|
||||
Post templates.Post
|
||||
HasRead bool
|
||||
FeaturedPost *templates.PostListItem
|
||||
Posts []templates.PostListItem
|
||||
}
|
||||
|
||||
func Index(c *RequestContext) ResponseData {
|
||||
|
@ -64,9 +59,12 @@ func Index(c *RequestContext) ResponseData {
|
|||
proj := projRow.(*models.Project)
|
||||
|
||||
type ProjectPost struct {
|
||||
Post models.Post `db:"post"`
|
||||
ThreadLastReadTime *time.Time `db:"tlri.lastread"`
|
||||
CatLastReadTime *time.Time `db:"clri.lastread"`
|
||||
Post models.Post `db:"post"`
|
||||
Thread models.Thread `db:"thread"`
|
||||
Cat models.Category `db:"cat"`
|
||||
User models.User `db:"auth_user"`
|
||||
ThreadLastReadTime *time.Time `db:"tlri.lastread"`
|
||||
CatLastReadTime *time.Time `db:"clri.lastread"`
|
||||
}
|
||||
|
||||
projectPostIter, err := db.Query(c.Context(), c.Conn, ProjectPost{},
|
||||
|
@ -84,6 +82,7 @@ func Index(c *RequestContext) ResponseData {
|
|||
clri.category_id = cat.id
|
||||
AND clri.user_id = $1
|
||||
)
|
||||
LEFT OUTER JOIN auth_user ON post.author_id = auth_user.id
|
||||
WHERE
|
||||
cat.project_id = $2
|
||||
AND cat.kind IN ($3, $4, $5, $6)
|
||||
|
@ -117,9 +116,14 @@ func Index(c *RequestContext) ResponseData {
|
|||
hasRead = true
|
||||
}
|
||||
|
||||
landingPageProject.Posts = append(landingPageProject.Posts, LandingPagePost{
|
||||
Post: templates.PostToTemplate(&projectPost.Post),
|
||||
HasRead: hasRead,
|
||||
c.Logger.Debug().Time("post date", projectPost.Post.PostDate).Msg("")
|
||||
|
||||
landingPageProject.Posts = append(landingPageProject.Posts, templates.PostListItem{
|
||||
Title: projectPost.Thread.Title,
|
||||
Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), // TODO
|
||||
User: templates.UserToTemplate(&projectPost.User),
|
||||
Date: projectPost.Post.PostDate,
|
||||
Unread: !hasRead,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -246,5 +246,8 @@ func doRequest(rw http.ResponseWriter, c *RequestContext, h HMNHandler) {
|
|||
}
|
||||
}
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
io.Copy(rw, res.Body)
|
||||
|
||||
if res.Body != nil {
|
||||
io.Copy(rw, res.Body)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
|||
)
|
||||
|
||||
mainRoutes.GET("/", func(c *RequestContext) ResponseData {
|
||||
if c.CurrentProject.ID == models.HMNProjectID {
|
||||
if c.CurrentProject.IsHMN() {
|
||||
return Index(c)
|
||||
} else {
|
||||
// TODO: Return the project landing page
|
||||
|
|
Loading…
Reference in New Issue