Implement posts index page

main
Brian Picciano 2 years ago
parent 5a99778187
commit 1242be7cfe
  1. 4
      srv/src/api/api.go
  2. 29
      srv/src/api/assets.go
  3. 37
      srv/src/api/posts.go
  4. 8
      srv/src/api/tpl.go
  5. 2
      srv/src/api/tpl/assets.html
  6. 2
      srv/src/api/tpl/index.html
  7. 8
      srv/src/api/tpl/post.html
  8. 61
      srv/src/api/tpl/posts.html
  9. 6
      srv/src/post/post.go
  10. 4
      static/src/assets/main.css

@ -212,7 +212,9 @@ func (a *api) handler() http.Handler {
{
v2Mux := http.NewServeMux()
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",
apiutil.MethodMux(map[string]http.Handler{
"GET": a.getPostAssetHandler(),

@ -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 {
renderHandler := a.renderPostAssetsIndexHandler()
renderIndexHandler := a.renderPostAssetsIndexHandler()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := filepath.Base(r.URL.Path)
if id == "/" {
renderHandler.ServeHTTP(rw, r)
renderIndexHandler.ServeHTTP(rw, r)
return
}

@ -18,11 +18,17 @@ import (
func (a *api) renderPostHandler() http.Handler {
tpl := a.mustParseBasedTpl("post.html")
renderIndexHandler := a.renderPostsIndexHandler()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := strings.TrimSuffix(filepath.Base(r.URL.Path), ".html")
if id == "/" {
renderIndexHandler.ServeHTTP(rw, r)
return
}
storedPost, err := a.params.PostStore.GetByID(id)
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) {
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 {
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
}
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)

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

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

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

@ -19,10 +19,10 @@
<p class="light"><em>
This post is part of a series:<br/>
{{ 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 }}
{{ 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 }}
</em></p>
{{ end }}
@ -35,10 +35,10 @@
<p class="light"><em>
If you liked this post, consider checking out other posts in the series:<br/>
{{ 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 }}
{{ 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 }}
</em></p>
{{ end }}

@ -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" . }}

@ -36,12 +36,6 @@ type Post struct {
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
// some extra fields as a result.
type StoredPost struct {

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

Loading…
Cancel
Save