Make the blog index includeable from site index
This commit is contained in:
parent
cd575ffb1e
commit
88a9908c19
85
core/blog.go
85
core/blog.go
|
@ -38,7 +38,7 @@ type Archive struct {
|
|||
// BlogRoutes attaches the blog routes to the app.
|
||||
func (b *Blog) BlogRoutes(r *mux.Router) {
|
||||
// Public routes
|
||||
r.HandleFunc("/blog", b.BlogIndex)
|
||||
r.HandleFunc("/blog", b.IndexHandler)
|
||||
r.HandleFunc("/archive", b.BlogArchive)
|
||||
r.HandleFunc("/tagged/{tag}", b.Tagged)
|
||||
|
||||
|
@ -65,9 +65,9 @@ func (b *Blog) BlogRoutes(r *mux.Router) {
|
|||
))
|
||||
}
|
||||
|
||||
// BlogIndex renders the main index page of the blog.
|
||||
func (b *Blog) BlogIndex(w http.ResponseWriter, r *http.Request) {
|
||||
b.PartialIndex(w, r, "", "")
|
||||
// IndexHandler renders the main index page of the blog.
|
||||
func (b *Blog) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
b.CommonIndexHandler(w, r, "", "")
|
||||
}
|
||||
|
||||
// Tagged lets you browse blog posts by category.
|
||||
|
@ -78,24 +78,42 @@ func (b *Blog) Tagged(w http.ResponseWriter, r *http.Request) {
|
|||
b.BadRequest(w, r, "Missing category in URL")
|
||||
}
|
||||
|
||||
b.PartialIndex(w, r, tag, "")
|
||||
b.CommonIndexHandler(w, r, tag, "")
|
||||
}
|
||||
|
||||
// Drafts renders an index view of only draft posts. Login required.
|
||||
func (b *Blog) Drafts(w http.ResponseWriter, r *http.Request) {
|
||||
b.PartialIndex(w, r, "", DRAFT)
|
||||
b.CommonIndexHandler(w, r, "", DRAFT)
|
||||
}
|
||||
|
||||
// PrivatePosts renders an index view of only private posts. Login required.
|
||||
func (b *Blog) PrivatePosts(w http.ResponseWriter, r *http.Request) {
|
||||
b.PartialIndex(w, r, "", PRIVATE)
|
||||
b.CommonIndexHandler(w, r, "", PRIVATE)
|
||||
}
|
||||
|
||||
// PartialIndex handles common logic for blog index views.
|
||||
func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
||||
tag, privacy string) {
|
||||
v := NewVars()
|
||||
// CommonIndexHandler handles common logic for blog index views.
|
||||
func (b *Blog) CommonIndexHandler(w http.ResponseWriter, r *http.Request, tag, privacy string) {
|
||||
// Page title.
|
||||
var title string
|
||||
if privacy == DRAFT {
|
||||
title = "Draft Posts"
|
||||
} else if privacy == PRIVATE {
|
||||
title = "Private Posts"
|
||||
} else if tag != "" {
|
||||
title = "Tagged as: " + tag
|
||||
} else {
|
||||
title = "Blog"
|
||||
}
|
||||
|
||||
b.RenderTemplate(w, r, "blog/index", NewVars(map[interface{}]interface{}{
|
||||
"Title": title,
|
||||
"Tag": tag,
|
||||
"Privacy": privacy,
|
||||
}))
|
||||
}
|
||||
|
||||
// RenderIndex renders and returns the blog index partial.
|
||||
func (b *Blog) RenderIndex(r *http.Request, tag, privacy string) template.HTML {
|
||||
// Get the blog index.
|
||||
idx, _ := posts.GetIndex()
|
||||
|
||||
|
@ -144,8 +162,7 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
|||
}
|
||||
|
||||
if len(pool) == 0 {
|
||||
b.NotFound(w, r, "No blog posts were found.")
|
||||
return
|
||||
return template.HTML("No blog posts were found.")
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(posts.ByUpdated(pool)))
|
||||
|
@ -160,16 +177,16 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
|||
stop := offset + perPage
|
||||
|
||||
// Handle pagination.
|
||||
v.Data["Page"] = page
|
||||
var previousPage, nextPage int
|
||||
if page > 1 {
|
||||
v.Data["PreviousPage"] = page - 1
|
||||
previousPage = page - 1
|
||||
} else {
|
||||
v.Data["PreviousPage"] = 0
|
||||
previousPage = 0
|
||||
}
|
||||
if offset+perPage < len(pool) {
|
||||
v.Data["NextPage"] = page + 1
|
||||
nextPage = page + 1
|
||||
} else {
|
||||
v.Data["NextPage"] = 0
|
||||
nextPage = 0
|
||||
}
|
||||
|
||||
var view []PostMeta
|
||||
|
@ -218,8 +235,16 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
|||
})
|
||||
}
|
||||
|
||||
v.Data["View"] = view
|
||||
b.RenderTemplate(w, r, "blog/index", v)
|
||||
// Render the blog index partial.
|
||||
var output bytes.Buffer
|
||||
v := map[string]interface{}{
|
||||
"PreviousPage": previousPage,
|
||||
"NextPage": nextPage,
|
||||
"View": view,
|
||||
}
|
||||
b.RenderPartialTemplate(&output, "blog/index.partial", v, false, nil)
|
||||
|
||||
return template.HTML(output.String())
|
||||
}
|
||||
|
||||
// BlogArchive summarizes all blog entries in an archive view.
|
||||
|
@ -271,6 +296,8 @@ func (b *Blog) BlogArchive(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// viewPost is the underlying implementation of the handler to view a blog
|
||||
// post, so that it can be called from non-http.HandlerFunc contexts.
|
||||
// Specifically, from the catch-all page handler to allow blog URL fragments
|
||||
// to map to their post.
|
||||
func (b *Blog) viewPost(w http.ResponseWriter, r *http.Request, fragment string) error {
|
||||
post, err := posts.LoadFragment(fragment)
|
||||
if err != nil {
|
||||
|
@ -323,19 +350,6 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa
|
|||
rendered = template.HTML(p.Body)
|
||||
}
|
||||
|
||||
// Get the template snippet.
|
||||
filepath, err := b.ResolvePath("blog/entry.partial")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return template.HTML("[error: missing blog/entry.partial]")
|
||||
}
|
||||
t := template.New("entry.partial.gohtml")
|
||||
t, err = t.ParseFiles(filepath.Absolute)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse entry.partial: %s", err.Error())
|
||||
return template.HTML("[error parsing template in blog/entry.partial]")
|
||||
}
|
||||
|
||||
meta := PostMeta{
|
||||
Post: p,
|
||||
Rendered: rendered,
|
||||
|
@ -345,10 +359,9 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa
|
|||
NumComments: numComments,
|
||||
}
|
||||
output := bytes.Buffer{}
|
||||
err = t.Execute(&output, meta)
|
||||
err = b.RenderPartialTemplate(&output, "blog/entry.partial", meta, false, nil)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return template.HTML("[error executing template in blog/entry.partial]")
|
||||
return template.HTML(fmt.Sprintf("[template error in blog/entry.partial: %s]", err.Error()))
|
||||
}
|
||||
|
||||
return template.HTML(output.String())
|
||||
|
|
|
@ -19,12 +19,6 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Handle the root URI with the blog index.
|
||||
if path == "/" {
|
||||
b.BlogIndex(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Restrict special paths.
|
||||
if strings.HasPrefix(strings.ToLower(path), "/.") {
|
||||
b.Forbidden(w, r)
|
||||
|
|
|
@ -2,6 +2,7 @@ package core
|
|||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -15,7 +16,7 @@ import (
|
|||
// variables in. It auto-loads global template variables (site name, etc.)
|
||||
// when the template is rendered.
|
||||
type Vars struct {
|
||||
// Global template variables.
|
||||
// Global, "constant" template variables.
|
||||
SetupNeeded bool
|
||||
Title string
|
||||
Path string
|
||||
|
@ -24,6 +25,9 @@ type Vars struct {
|
|||
CSRF string
|
||||
Request *http.Request
|
||||
|
||||
// Configuration variables
|
||||
NoLayout bool // don't wrap in .layout.html, just render the template
|
||||
|
||||
// Common template variables.
|
||||
Message string
|
||||
Flashes []string
|
||||
|
@ -47,7 +51,7 @@ func NewVars(data ...map[interface{}]interface{}) *Vars {
|
|||
}
|
||||
|
||||
// LoadDefaults combines template variables with default, globally available vars.
|
||||
func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) {
|
||||
func (v *Vars) LoadDefaults(b *Blog, r *http.Request) {
|
||||
// Get the site settings.
|
||||
s, err := settings.Load()
|
||||
if err != nil {
|
||||
|
@ -64,41 +68,46 @@ func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) {
|
|||
user, err := b.CurrentUser(r)
|
||||
v.CurrentUser = user
|
||||
v.LoggedIn = err == nil
|
||||
|
||||
// Add any flashed messages from the endpoint controllers.
|
||||
session := b.Session(r)
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
for _, flash := range flashes {
|
||||
_ = flash
|
||||
v.Flashes = append(v.Flashes, flash.(string))
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
v.CSRF = b.GenerateCSRFToken(w, r, session)
|
||||
}
|
||||
// // TemplateVars is an interface that describes the template variable struct.
|
||||
// type TemplateVars interface {
|
||||
// LoadDefaults(*Blog, *http.Request)
|
||||
// }
|
||||
|
||||
// TemplateVars is an interface that describes the template variable struct.
|
||||
type TemplateVars interface {
|
||||
LoadDefaults(*Blog, http.ResponseWriter, *http.Request)
|
||||
}
|
||||
// RenderPartialTemplate handles rendering a Go template to a writer, without
|
||||
// doing anything extra to the vars or dealing with net/http. This is ideal for
|
||||
// rendering partials, such as comment partials.
|
||||
//
|
||||
// This will wrap the template in `.layout.gohtml` by default. To render just
|
||||
// a bare template on its own, i.e. for partial templates, create a Vars struct
|
||||
// with `Vars{NoIndex: true}`
|
||||
func (b *Blog) RenderPartialTemplate(w io.Writer, path string, v interface{}, withLayout bool, functions map[string]interface{}) error {
|
||||
var (
|
||||
layout Filepath
|
||||
templateName string
|
||||
err error
|
||||
)
|
||||
|
||||
// RenderTemplate responds with an HTML template.
|
||||
func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path string, vars TemplateVars) error {
|
||||
// Get the layout template.
|
||||
layout, err := b.ResolvePath(".layout")
|
||||
if err != nil {
|
||||
log.Error("RenderTemplate(%s): layout template not found", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// And the template in question.
|
||||
// Find the file path to the template.
|
||||
filepath, err := b.ResolvePath(path)
|
||||
if err != nil {
|
||||
log.Error("RenderTemplate(%s): file not found", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the layout template.
|
||||
if withLayout {
|
||||
templateName = "layout"
|
||||
layout, err = b.ResolvePath(".layout")
|
||||
if err != nil {
|
||||
log.Error("RenderTemplate(%s): layout template not found", path)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
templateName = filepath.Basename
|
||||
}
|
||||
|
||||
// The comment entry partial.
|
||||
commentEntry, err := b.ResolvePath("comments/entry.partial")
|
||||
if err != nil {
|
||||
|
@ -106,39 +115,76 @@ func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path strin
|
|||
return err
|
||||
}
|
||||
|
||||
// Useful template functions.
|
||||
t := template.New(filepath.Absolute).Funcs(template.FuncMap{
|
||||
// Template functions.
|
||||
funcmap := template.FuncMap{
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
"RenderIndex": b.RenderIndex,
|
||||
"RenderPost": b.RenderPost,
|
||||
}
|
||||
if functions != nil {
|
||||
for name, fn := range functions {
|
||||
funcmap[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Useful template functions.
|
||||
t := template.New(filepath.Absolute).Funcs(funcmap)
|
||||
|
||||
// Parse the template files. The layout comes first because it's the wrapper
|
||||
// and allows the filepath template to set the page title.
|
||||
var templates []string
|
||||
if withLayout {
|
||||
templates = append(templates, layout.Absolute)
|
||||
}
|
||||
t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(w, templateName, v)
|
||||
if err != nil {
|
||||
log.Error("Template parsing error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderTemplate responds with an HTML template.
|
||||
//
|
||||
// The vars will be massaged a bit to load the global defaults (such as the
|
||||
// website title and user login status), the user's session may be updated with
|
||||
// new CSRF token, and other such things. If you just want to render a template
|
||||
// without all that nonsense, use RenderPartialTemplate.
|
||||
func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path string, vars *Vars) error {
|
||||
// Inject globally available variables.
|
||||
if vars == nil {
|
||||
vars = &Vars{}
|
||||
}
|
||||
vars.LoadDefaults(b, r)
|
||||
|
||||
// Add any flashed messages from the endpoint controllers.
|
||||
session := b.Session(r)
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
for _, flash := range flashes {
|
||||
_ = flash
|
||||
vars.Flashes = append(vars.Flashes, flash.(string))
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
vars.CSRF = b.GenerateCSRFToken(w, r, session)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
||||
b.RenderPartialTemplate(w, path, vars, true, template.FuncMap{
|
||||
"RenderComments": func(subject string, ids ...string) template.HTML {
|
||||
session := b.Session(r)
|
||||
csrf := b.GenerateCSRFToken(w, r, session)
|
||||
return b.RenderComments(session, csrf, r.URL.Path, subject, ids...)
|
||||
},
|
||||
})
|
||||
|
||||
// Parse the template files. The layout comes first because it's the wrapper
|
||||
// and allows the filepath template to set the page title.
|
||||
t, err = t.ParseFiles(layout.Absolute, commentEntry.Absolute, filepath.Absolute)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Inject globally available variables.
|
||||
if vars == nil {
|
||||
vars = &Vars{}
|
||||
}
|
||||
vars.LoadDefaults(b, w, r)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
||||
err = t.ExecuteTemplate(w, "layout", vars)
|
||||
if err != nil {
|
||||
log.Error("Template parsing error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Parsed template")
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,46 +1,8 @@
|
|||
{{ define "title" }}Welcome{{ end }}
|
||||
{{ define "title" }}{{ .Data.Title }}{{ end }}
|
||||
{{ define "content" }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col text-right">
|
||||
<ul class="list-inline">
|
||||
{{ if .Data.PreviousPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .Data.PreviousPage }}">Earlier</a></li>
|
||||
{{ end }}
|
||||
{{ if .Data.NextPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .Data.NextPage }}">Older</a></li>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<h1>{{ .Data.Title }}</h1>
|
||||
|
||||
{{ range .Data.View }}
|
||||
{{ $p := .Post }}
|
||||
{{ RenderPost $p true .NumComments }}
|
||||
|
||||
{{ if and $.LoggedIn $.CurrentUser.Admin }}
|
||||
<div class="mb-4">
|
||||
<small>
|
||||
<strong>Admin Actions:</strong>
|
||||
[
|
||||
<a href="/blog/edit?id={{ $p.ID }}">Edit</a> |
|
||||
<a href="/blog/delete?id={{ $p.ID }}">Delete</a>
|
||||
]
|
||||
</small>
|
||||
</div>
|
||||
{{ end }}
|
||||
<hr>
|
||||
{{ end }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col text-right">
|
||||
<ul class="list-inline">
|
||||
{{ if .Data.PreviousPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .Data.PreviousPage }}">Earlier</a></li>
|
||||
{{ end }}
|
||||
{{ if .Data.NextPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .Data.NextPage }}">Older</a></li>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ RenderIndex .Request .Data.Tag .Data.Privacy }}
|
||||
|
||||
{{ end }}
|
||||
|
|
41
root/blog/index.partial.gohtml
Normal file
41
root/blog/index.partial.gohtml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<div class="row">
|
||||
<div class="col text-right">
|
||||
<ul class="list-inline">
|
||||
{{ if .PreviousPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .PreviousPage }}">Earlier</a></li>
|
||||
{{ end }}
|
||||
{{ if .NextPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .NextPage }}">Older</a></li>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ range .View }}
|
||||
{{ $p := .Post }}
|
||||
{{ RenderPost $p true .NumComments }}
|
||||
|
||||
{{ if and $.LoggedIn $.CurrentUser.Admin }}
|
||||
<div class="mb-4">
|
||||
<small>
|
||||
<strong>Admin Actions:</strong>
|
||||
[
|
||||
<a href="/blog/edit?id={{ $p.ID }}">Edit</a> |
|
||||
<a href="/blog/delete?id={{ $p.ID }}">Delete</a>
|
||||
]
|
||||
</small>
|
||||
</div>
|
||||
{{ end }}
|
||||
<hr>
|
||||
{{ end }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col text-right">
|
||||
<ul class="list-inline">
|
||||
{{ if .PreviousPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .PreviousPage }}">Earlier</a></li>
|
||||
{{ end }}
|
||||
{{ if .NextPage }}
|
||||
<li class="list-inline-item"><a href="?page={{ .NextPage }}">Older</a></li>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,11 @@
|
|||
{{ define "title" }}Welcome{{ end }}
|
||||
{{ define "content" }}
|
||||
<h1>Index</h1>
|
||||
<h1>Welcome to "Blog!"</h1>
|
||||
|
||||
<p>
|
||||
This is your index page. You can edit it and put whatever you want here.
|
||||
By default, the blog index is also embedded on the website's index page.
|
||||
</p>
|
||||
|
||||
{{ RenderIndex .Request "" "" }}
|
||||
{{ end }}
|
||||
|
|
Loading…
Reference in New Issue
Block a user