diff --git a/public/style.css b/public/style.css
index ba65c40..5b691bb 100644
--- a/public/style.css
+++ b/public/style.css
@@ -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;
diff --git a/src/db/db.go b/src/db/db.go
index 3407b1f..c79a7e8 100644
--- a/src/db/db.go
+++ b/src/db/db.go
@@ -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 {
diff --git a/src/hmnurl/hmnurl.go b/src/hmnurl/hmnurl.go
index 166c268..634baf9 100644
--- a/src/hmnurl/hmnurl.go
+++ b/src/hmnurl/hmnurl.go
@@ -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
diff --git a/src/models/post.go b/src/models/post.go
index c88d403..660507a 100644
--- a/src/models/post.go
+++ b/src/models/post.go
@@ -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"`
diff --git a/src/models/project.go b/src/models/project.go
index f6d4a6f..63bb075 100644
--- a/src/models/project.go
+++ b/src/models/project.go
@@ -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
+}
diff --git a/src/rawdata/scss/_forum.scss b/src/rawdata/scss/_forum.scss
index 2bfbd83..899daf9 100644
--- a/src/rawdata/scss/_forum.scss
+++ b/src/rawdata/scss/_forum.scss
@@ -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;
diff --git a/src/templates/mapping.go b/src/templates/mapping.go
index e13d398..d5e9596 100644
--- a/src/templates/mapping.go
+++ b/src/templates/mapping.go
@@ -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,
diff --git a/src/templates/src/include/post_list_item.html b/src/templates/src/include/post_list_item.html
new file mode 100644
index 0000000..cde112b
--- /dev/null
+++ b/src/templates/src/include/post_list_item.html
@@ -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.
+*/}}
+
+
+
+
+
+ {{ range $i, $breadcrumb := .Breadcrumbs }}
+ {{ if gt $i 0 }} ยป {{ end }}
+
{{ $breadcrumb.Name }}
+ {{ end }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/templates/src/index.html b/src/templates/src/index.html
index 2cb4e0a..475021f 100644
--- a/src/templates/src/index.html
+++ b/src/templates/src/index.html
@@ -66,15 +66,8 @@
{% endif %}
*/}}
- {{ range $posts }}
-
- {{ .Post.Preview }}
-
- {{/*
- {% 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 %}
diff --git a/src/templates/templates.go b/src/templates/templates.go
index 5017a2c..f5e6885 100644
--- a/src/templates/templates.go
+++ b/src/templates/templates.go
@@ -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}})
},
diff --git a/src/templates/types.go b/src/templates/types.go
index 735ff1b..7ce2544 100644
--- a/src/templates/types.go
+++ b/src/templates/types.go
@@ -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
+}
diff --git a/src/templates/urls.go b/src/templates/urls.go
new file mode 100644
index 0000000..2e252bf
--- /dev/null
+++ b/src/templates/urls.go
@@ -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 ""
+}
diff --git a/src/website/landing.go b/src/website/landing.go
index 6a948ad..42dd170 100644
--- a/src/website/landing.go
+++ b/src/website/landing.go
@@ -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,
})
}
diff --git a/src/website/requesthandling.go b/src/website/requesthandling.go
index 1f48b7d..b0028a3 100644
--- a/src/website/requesthandling.go
+++ b/src/website/requesthandling.go
@@ -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)
+ }
}
diff --git a/src/website/routes.go b/src/website/routes.go
index 1adf725..e3c05e0 100644
--- a/src/website/routes.go
+++ b/src/website/routes.go
@@ -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