Implement posts index page
This commit is contained in:
parent
5a99778187
commit
1242be7cfe
@ -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(),
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -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;"
|
||||||
>
|
>
|
||||||
|
@ -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 }}
|
||||||
|
@ -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 }}
|
||||||
|
61
srv/src/api/tpl/posts.html
Normal file
61
srv/src/api/tpl/posts.html
Normal 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" . }}
|
@ -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 {
|
||||||
|
@ -15,6 +15,10 @@ ul {
|
|||||||
list-style: circle;
|
list-style: circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.light {
|
.light {
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user