Implement posts index page

This commit is contained in:
Brian Picciano 2022-05-20 08:36:52 -06:00
parent 5a99778187
commit 1242be7cfe
10 changed files with 138 additions and 23 deletions

View File

@ -212,7 +212,9 @@ func (a *api) handler() http.Handler {
{ {
v2Mux := http.NewServeMux() v2Mux := http.NewServeMux()
v2Mux.Handle("/follow.html", a.renderDumbTplHandler("follow.html")) v2Mux.Handle("/follow.html", a.renderDumbTplHandler("follow.html"))
v2Mux.Handle("/posts/", a.renderPostHandler()) v2Mux.Handle("/posts/", http.StripPrefix("/posts",
a.renderPostHandler(),
))
v2Mux.Handle("/assets/", http.StripPrefix("/assets", v2Mux.Handle("/assets/", http.StripPrefix("/assets",
apiutil.MethodMux(map[string]http.Handler{ apiutil.MethodMux(map[string]http.Handler{
"GET": a.getPostAssetHandler(), "GET": a.getPostAssetHandler(),

View File

@ -50,16 +50,41 @@ func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error {
} }
} }
func (a *api) renderPostAssetsIndexHandler() http.Handler {
tpl := a.mustParseBasedTpl("assets.html")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ids, err := a.params.PostAssetStore.List()
if err != nil {
apiutil.InternalServerError(
rw, r, fmt.Errorf("getting list of asset ids: %w", err),
)
return
}
tplPayload := struct {
IDs []string
}{
IDs: ids,
}
executeTemplate(rw, r, tpl, tplPayload)
})
}
func (a *api) getPostAssetHandler() http.Handler { func (a *api) getPostAssetHandler() http.Handler {
renderHandler := a.renderPostAssetsIndexHandler() renderIndexHandler := a.renderPostAssetsIndexHandler()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := filepath.Base(r.URL.Path) id := filepath.Base(r.URL.Path)
if id == "/" { if id == "/" {
renderHandler.ServeHTTP(rw, r) renderIndexHandler.ServeHTTP(rw, r)
return return
} }

View File

@ -18,11 +18,17 @@ import (
func (a *api) renderPostHandler() http.Handler { func (a *api) renderPostHandler() http.Handler {
tpl := a.mustParseBasedTpl("post.html") tpl := a.mustParseBasedTpl("post.html")
renderIndexHandler := a.renderPostsIndexHandler()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html") id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html")
if id == "/" {
renderIndexHandler.ServeHTTP(rw, r)
return
}
storedPost, err := a.params.PostStore.GetByID(id) storedPost, err := a.params.PostStore.GetByID(id)
if errors.Is(err, post.ErrPostNotFound) { if errors.Is(err, post.ErrPostNotFound) {
@ -88,25 +94,44 @@ func (a *api) renderPostHandler() http.Handler {
}) })
} }
func (a *api) renderPostAssetsIndexHandler() http.Handler { func (a *api) renderPostsIndexHandler() http.Handler {
tpl := a.mustParseBasedTpl("assets.html") tpl := a.mustParseBasedTpl("posts.html")
const pageCount = 20
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ids, err := a.params.PostAssetStore.List() page, err := apiutil.StrToInt(r.FormValue("p"), 0)
if err != nil {
apiutil.BadRequest(
rw, r, fmt.Errorf("invalid page number: %w", err),
)
return
}
posts, hasMore, err := a.params.PostStore.WithOrderDesc().Get(page, pageCount)
if err != nil { if err != nil {
apiutil.InternalServerError( apiutil.InternalServerError(
rw, r, fmt.Errorf("getting list of asset ids: %w", err), rw, r, fmt.Errorf("fetching page %d of posts: %w", page, err),
) )
return return
} }
tplPayload := struct { tplPayload := struct {
IDs []string Posts []post.StoredPost
PrevPage, NextPage int
}{ }{
IDs: ids, Posts: posts,
PrevPage: -1,
NextPage: -1,
}
if page > 0 {
tplPayload.PrevPage = page - 1
}
if hasMore {
tplPayload.NextPage = page + 1
} }
executeTemplate(rw, r, tpl, tplPayload) executeTemplate(rw, r, tpl, tplPayload)

View File

@ -42,8 +42,12 @@ func (a *api) mustParseTpl(name string) *template.Template {
tpl := template.New("").Funcs(template.FuncMap{ tpl := template.New("").Funcs(template.FuncMap{
"BlogURL": blogURL, "BlogURL": blogURL,
"AssetURL": func(path string) string { "AssetURL": func(id string) string {
path = filepath.Join("assets", path) path := filepath.Join("assets", id)
return blogURL(path)
},
"PostURL": func(id string) string {
path := filepath.Join("posts", id)
return blogURL(path) return blogURL(path)
}, },
}) })

View File

@ -33,7 +33,7 @@
<td><a href="{{ AssetURL . }}" target="_blank">{{ . }}</a></td> <td><a href="{{ AssetURL . }}" target="_blank">{{ . }}</a></td>
<td> <td>
<form <form
action="{{ BlogURL "assets/" }}{{ . }}?method=delete" action="{{ AssetURL . }}?method=delete"
method="POST" method="POST"
style="margin-bottom: 0;" style="margin-bottom: 0;"
> >

View File

@ -5,7 +5,7 @@
{{ range .Payload.Posts }} {{ range .Payload.Posts }}
<li> <li>
<h2> <h2>
<a href="posts/{{ .HTTPPath }}">{{ .Title }}</a> <a href="{{ PostURL .ID }}">{{ .Title }}</a>
</h2> </h2>
<span>{{ .PublishedAt.Format "2006-01-02" }}</span> <span>{{ .PublishedAt.Format "2006-01-02" }}</span>
{{ if not .LastUpdatedAt.IsZero }} {{ if not .LastUpdatedAt.IsZero }}

View File

@ -19,10 +19,10 @@
<p class="light"><em> <p class="light"><em>
This post is part of a series:<br/> This post is part of a series:<br/>
{{ if .Payload.SeriesPrevious }} {{ if .Payload.SeriesPrevious }}
Previously: <a href="{{ .Payload.SeriesPrevious.HTTPPath }}">{{ .Payload.SeriesPrevious.Title }}</a></br> Previously: <a href="{{ PostURL .Payload.SeriesPrevious.ID }}">{{ .Payload.SeriesPrevious.Title }}</a></br>
{{ end }} {{ end }}
{{ if .Payload.SeriesNext }} {{ if .Payload.SeriesNext }}
Next: <a href="{{ .Payload.SeriesNext.HTTPPath }}">{{ .Payload.SeriesNext.Title }}</a></br> Next: <a href="{{ PostURL .Payload.SeriesNext.ID }}">{{ .Payload.SeriesNext.Title }}</a></br>
{{ end }} {{ end }}
</em></p> </em></p>
{{ end }} {{ end }}
@ -35,10 +35,10 @@
<p class="light"><em> <p class="light"><em>
If you liked this post, consider checking out other posts in the series:<br/> If you liked this post, consider checking out other posts in the series:<br/>
{{ if .Payload.SeriesPrevious }} {{ if .Payload.SeriesPrevious }}
Previously: <a href="{{ .Payload.SeriesPrevious.HTTPPath }}">{{ .Payload.SeriesPrevious.Title }}</a></br> Previously: <a href="{{ PostURL .Payload.SeriesPrevious.ID }}">{{ .Payload.SeriesPrevious.Title }}</a></br>
{{ end }} {{ end }}
{{ if .Payload.SeriesNext }} {{ if .Payload.SeriesNext }}
Next: <a href="{{ .Payload.SeriesNext.HTTPPath }}">{{ .Payload.SeriesNext.Title }}</a></br> Next: <a href="{{ PostURL .Payload.SeriesNext.ID }}">{{ .Payload.SeriesNext.Title }}</a></br>
{{ end }} {{ end }}
</em></p> </em></p>
{{ end }} {{ end }}

View File

@ -0,0 +1,61 @@
{{ define "posts-nextprev" }}
{{ if or (ge .Payload.PrevPage 0) (ge .Payload.NextPage 0) }}
<div id="page-turner">
{{ if ge .Payload.PrevPage 0 }}
<a style="float: left;" href="?p={{ .Payload.PrevPage}}">Newer</a>
{{ end }}
{{ if ge .Payload.NextPage 0 }}
<a style="float:right;" href="?p={{ .Payload.NextPage}}">Older</a>
{{ end }}
</div>
{{ end }}
{{ end }}
{{ define "body" }}
{{ $csrfFormInput := .CSRFFormInput }}
<p style="text-align: center;">
<a href="{{ BlogURL "posts/" }}?method=new">
<button>New Post</button>
</a>
</p>
{{ template "posts-nextprev" . }}
<table style="margin-top: 2rem;">
{{ range .Payload.Posts }}
<tr>
<td>{{ .PublishedAt }}</td>
<td><a href="{{ PostURL .ID }}" target="_blank">{{ .Title }}</a></td>
<td>
<a href="{{ PostURL .ID }}?method=edit">
<button>Edit</button>
</a>
</td>
<td>
<form
action="{{ PostURL .ID }}?method=delete"
method="POST"
>
{{ $csrfFormInput }}
<input type="submit" value="Delete" />
</form>
</td>
</tr>
{{ end }}
</table>
{{ template "posts-nextprev" . }}
{{ end }}
{{ template "base.html" . }}

View File

@ -36,12 +36,6 @@ type Post struct {
Body string Body string
} }
// HTTPPath returns the relative URL path of the StoredPost, when querying it
// over HTTP.
func (p Post) HTTPPath() string {
return fmt.Sprintf("%s.html", p.ID)
}
// StoredPost is a Post which has been stored in a Store, and has been given // StoredPost is a Post which has been stored in a Store, and has been given
// some extra fields as a result. // some extra fields as a result.
type StoredPost struct { type StoredPost struct {

View File

@ -15,6 +15,10 @@ ul {
list-style: circle; list-style: circle;
} }
form {
margin-bottom: 0;
}
.light { .light {
color: #666; color: #666;
} }