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.
|
// BlogRoutes attaches the blog routes to the app.
|
||||||
func (b *Blog) BlogRoutes(r *mux.Router) {
|
func (b *Blog) BlogRoutes(r *mux.Router) {
|
||||||
// Public routes
|
// Public routes
|
||||||
r.HandleFunc("/blog", b.BlogIndex)
|
r.HandleFunc("/blog", b.IndexHandler)
|
||||||
r.HandleFunc("/archive", b.BlogArchive)
|
r.HandleFunc("/archive", b.BlogArchive)
|
||||||
r.HandleFunc("/tagged/{tag}", b.Tagged)
|
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.
|
// IndexHandler renders the main index page of the blog.
|
||||||
func (b *Blog) BlogIndex(w http.ResponseWriter, r *http.Request) {
|
func (b *Blog) IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
b.PartialIndex(w, r, "", "")
|
b.CommonIndexHandler(w, r, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tagged lets you browse blog posts by category.
|
// 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.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.
|
// Drafts renders an index view of only draft posts. Login required.
|
||||||
func (b *Blog) Drafts(w http.ResponseWriter, r *http.Request) {
|
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.
|
// PrivatePosts renders an index view of only private posts. Login required.
|
||||||
func (b *Blog) PrivatePosts(w http.ResponseWriter, r *http.Request) {
|
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.
|
// CommonIndexHandler handles common logic for blog index views.
|
||||||
func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
func (b *Blog) CommonIndexHandler(w http.ResponseWriter, r *http.Request, tag, privacy string) {
|
||||||
tag, privacy string) {
|
// Page title.
|
||||||
v := NewVars()
|
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.
|
// Get the blog index.
|
||||||
idx, _ := posts.GetIndex()
|
idx, _ := posts.GetIndex()
|
||||||
|
|
||||||
|
@ -144,8 +162,7 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pool) == 0 {
|
if len(pool) == 0 {
|
||||||
b.NotFound(w, r, "No blog posts were found.")
|
return template.HTML("No blog posts were found.")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(sort.Reverse(posts.ByUpdated(pool)))
|
sort.Sort(sort.Reverse(posts.ByUpdated(pool)))
|
||||||
|
@ -160,16 +177,16 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
||||||
stop := offset + perPage
|
stop := offset + perPage
|
||||||
|
|
||||||
// Handle pagination.
|
// Handle pagination.
|
||||||
v.Data["Page"] = page
|
var previousPage, nextPage int
|
||||||
if page > 1 {
|
if page > 1 {
|
||||||
v.Data["PreviousPage"] = page - 1
|
previousPage = page - 1
|
||||||
} else {
|
} else {
|
||||||
v.Data["PreviousPage"] = 0
|
previousPage = 0
|
||||||
}
|
}
|
||||||
if offset+perPage < len(pool) {
|
if offset+perPage < len(pool) {
|
||||||
v.Data["NextPage"] = page + 1
|
nextPage = page + 1
|
||||||
} else {
|
} else {
|
||||||
v.Data["NextPage"] = 0
|
nextPage = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var view []PostMeta
|
var view []PostMeta
|
||||||
|
@ -218,8 +235,16 @@ func (b *Blog) PartialIndex(w http.ResponseWriter, r *http.Request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Data["View"] = view
|
// Render the blog index partial.
|
||||||
b.RenderTemplate(w, r, "blog/index", v)
|
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.
|
// 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
|
// viewPost is the underlying implementation of the handler to view a blog
|
||||||
// post, so that it can be called from non-http.HandlerFunc contexts.
|
// 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 {
|
func (b *Blog) viewPost(w http.ResponseWriter, r *http.Request, fragment string) error {
|
||||||
post, err := posts.LoadFragment(fragment)
|
post, err := posts.LoadFragment(fragment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -323,19 +350,6 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa
|
||||||
rendered = template.HTML(p.Body)
|
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{
|
meta := PostMeta{
|
||||||
Post: p,
|
Post: p,
|
||||||
Rendered: rendered,
|
Rendered: rendered,
|
||||||
|
@ -345,10 +359,9 @@ func (b *Blog) RenderPost(p *posts.Post, indexView bool, numComments int) templa
|
||||||
NumComments: numComments,
|
NumComments: numComments,
|
||||||
}
|
}
|
||||||
output := bytes.Buffer{}
|
output := bytes.Buffer{}
|
||||||
err = t.Execute(&output, meta)
|
err = b.RenderPartialTemplate(&output, "blog/entry.partial", meta, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
return template.HTML(fmt.Sprintf("[template error in blog/entry.partial: %s]", err.Error()))
|
||||||
return template.HTML("[error executing template in blog/entry.partial]")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return template.HTML(output.String())
|
return template.HTML(output.String())
|
||||||
|
|
|
@ -19,12 +19,6 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the root URI with the blog index.
|
|
||||||
if path == "/" {
|
|
||||||
b.BlogIndex(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restrict special paths.
|
// Restrict special paths.
|
||||||
if strings.HasPrefix(strings.ToLower(path), "/.") {
|
if strings.HasPrefix(strings.ToLower(path), "/.") {
|
||||||
b.Forbidden(w, r)
|
b.Forbidden(w, r)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
// variables in. It auto-loads global template variables (site name, etc.)
|
// variables in. It auto-loads global template variables (site name, etc.)
|
||||||
// when the template is rendered.
|
// when the template is rendered.
|
||||||
type Vars struct {
|
type Vars struct {
|
||||||
// Global template variables.
|
// Global, "constant" template variables.
|
||||||
SetupNeeded bool
|
SetupNeeded bool
|
||||||
Title string
|
Title string
|
||||||
Path string
|
Path string
|
||||||
|
@ -24,6 +25,9 @@ type Vars struct {
|
||||||
CSRF string
|
CSRF string
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
|
|
||||||
|
// Configuration variables
|
||||||
|
NoLayout bool // don't wrap in .layout.html, just render the template
|
||||||
|
|
||||||
// Common template variables.
|
// Common template variables.
|
||||||
Message string
|
Message string
|
||||||
Flashes []string
|
Flashes []string
|
||||||
|
@ -47,7 +51,7 @@ func NewVars(data ...map[interface{}]interface{}) *Vars {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDefaults combines template variables with default, globally available 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.
|
// Get the site settings.
|
||||||
s, err := settings.Load()
|
s, err := settings.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -64,41 +68,46 @@ func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := b.CurrentUser(r)
|
user, err := b.CurrentUser(r)
|
||||||
v.CurrentUser = user
|
v.CurrentUser = user
|
||||||
v.LoggedIn = err == nil
|
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.
|
// RenderPartialTemplate handles rendering a Go template to a writer, without
|
||||||
type TemplateVars interface {
|
// doing anything extra to the vars or dealing with net/http. This is ideal for
|
||||||
LoadDefaults(*Blog, http.ResponseWriter, *http.Request)
|
// 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.
|
// Find the file path to the 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.
|
|
||||||
filepath, err := b.ResolvePath(path)
|
filepath, err := b.ResolvePath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderTemplate(%s): file not found", path)
|
log.Error("RenderTemplate(%s): file not found", path)
|
||||||
return err
|
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.
|
// The comment entry partial.
|
||||||
commentEntry, err := b.ResolvePath("comments/entry.partial")
|
commentEntry, err := b.ResolvePath("comments/entry.partial")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -106,39 +115,76 @@ func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useful template functions.
|
// Template functions.
|
||||||
t := template.New(filepath.Absolute).Funcs(template.FuncMap{
|
funcmap := template.FuncMap{
|
||||||
"StringsJoin": strings.Join,
|
"StringsJoin": strings.Join,
|
||||||
"Now": time.Now,
|
"Now": time.Now,
|
||||||
|
"RenderIndex": b.RenderIndex,
|
||||||
"RenderPost": b.RenderPost,
|
"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 {
|
"RenderComments": func(subject string, ids ...string) template.HTML {
|
||||||
session := b.Session(r)
|
session := b.Session(r)
|
||||||
csrf := b.GenerateCSRFToken(w, r, session)
|
csrf := b.GenerateCSRFToken(w, r, session)
|
||||||
return b.RenderComments(session, csrf, r.URL.Path, subject, ids...)
|
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")
|
log.Debug("Parsed template")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,46 +1,8 @@
|
||||||
{{ define "title" }}Welcome{{ end }}
|
{{ define "title" }}{{ .Data.Title }}{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
|
||||||
<div class="row">
|
<h1>{{ .Data.Title }}</h1>
|
||||||
<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>
|
|
||||||
|
|
||||||
{{ range .Data.View }}
|
{{ RenderIndex .Request .Data.Tag .Data.Privacy }}
|
||||||
{{ $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>
|
|
||||||
|
|
||||||
{{ end }}
|
{{ 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 "title" }}Welcome{{ end }}
|
||||||
{{ define "content" }}
|
{{ 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 }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user