mirror of https://github.com/jlelse/GoBlog
Improve templates and stuff
This commit is contained in:
parent
d13b0a5394
commit
f300c3d498
|
@ -150,5 +150,4 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter) {
|
|||
}
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(asBlog)
|
||||
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ type configCache struct {
|
|||
type configBlog struct {
|
||||
Path string `mapstructure:"path"`
|
||||
Lang string `mapstructure:"lang"`
|
||||
TimeLang string `mapstructure:"timelang"`
|
||||
Title string `mapstructure:"title"`
|
||||
Description string `mapstructure:"description"`
|
||||
Pagination int `mapstructure:"pagination"`
|
||||
|
@ -104,6 +105,7 @@ type configUser struct {
|
|||
Name string `mapstructure:"name"`
|
||||
Password string `mapstructure:"password"`
|
||||
Picture string `mapstructure:"picture"`
|
||||
Link string `mapstructure:"link"`
|
||||
}
|
||||
|
||||
type configHooks struct {
|
||||
|
|
|
@ -5,8 +5,9 @@ import "net/http"
|
|||
func serveCustomPage(blog *configBlog, page *customPage) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
render(w, page.Template, &renderData{
|
||||
Blog: blog,
|
||||
Data: page.Data,
|
||||
Blog: blog,
|
||||
Canonical: appConfig.Server.PublicAddress + page.Path,
|
||||
Data: page.Data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
server:
|
||||
logging: false
|
||||
debug: false
|
||||
port: 8080
|
||||
domain: example.com
|
||||
publicAddress: http://localhost:8080
|
||||
publicHttps: false
|
||||
letsEncryptMail: mail@example.com
|
||||
localHttps: false
|
||||
database:
|
||||
file: data/db.sqlite
|
||||
cache:
|
||||
enable: true
|
||||
expiration: 600
|
||||
blogs:
|
||||
main:
|
||||
path: /
|
||||
lang: en_US
|
||||
title: My blog
|
||||
description: This is my blog
|
||||
sections:
|
||||
- name: posts
|
||||
title: Posts
|
||||
description: "**Posts** on this blog"
|
||||
taxonomies:
|
||||
- name: tags
|
||||
title: Tags
|
||||
description: "**Tags** on this blog"
|
||||
menus:
|
||||
main:
|
||||
items:
|
||||
- title: Home
|
||||
link: /
|
||||
- title: Posts
|
||||
link: /posts
|
||||
photos:
|
||||
enable: true
|
||||
parameter: images
|
||||
path: /photos
|
||||
title: Photos
|
||||
description: "Photos on this blog"
|
||||
activitystreams:
|
||||
enable: true
|
||||
replyParameter: replylink
|
||||
imagesParameter: images
|
||||
user:
|
||||
nick: admin
|
||||
name: Admin
|
||||
password: secret
|
||||
hugo:
|
||||
frontmatter:
|
||||
- meta: title
|
||||
parameter: title
|
||||
- meta: tags
|
||||
parameter: tags
|
||||
micropub:
|
||||
categoryParam: tags
|
||||
replyParam: replylink
|
||||
likeParam: likelink
|
||||
bookmarkParam: link
|
||||
audioParam: audio
|
||||
photoParam: images
|
||||
photoDescriptionParam: imagealts
|
||||
pathRedirects:
|
||||
- from: "\\/index\\.xml"
|
||||
to: ".rss"
|
||||
- from: "\\/feed\\.json"
|
||||
to: ".json"
|
||||
- from: "\\/(feed|rss)\\/?$"
|
||||
to: ".rss"
|
24
feeds.go
24
feeds.go
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/gorilla/feeds"
|
||||
)
|
||||
|
||||
|
@ -15,6 +16,10 @@ const (
|
|||
rssFeed feedType = "rss"
|
||||
atomFeed feedType = "atom"
|
||||
jsonFeed feedType = "json"
|
||||
|
||||
feedAudioURL = "audio"
|
||||
feedAudioType = "audiomime"
|
||||
feedAudioLength = "audiolength"
|
||||
)
|
||||
|
||||
func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Request, posts []*post, title string, description string) {
|
||||
|
@ -30,14 +35,33 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
|
|||
Description: description,
|
||||
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + strings.TrimSuffix(r.URL.Path, "."+string(f))},
|
||||
Created: now,
|
||||
Author: &feeds.Author{
|
||||
Name: appConfig.User.Name,
|
||||
},
|
||||
Image: &feeds.Image{
|
||||
Url: appConfig.User.Picture,
|
||||
},
|
||||
}
|
||||
for _, p := range posts {
|
||||
created, _ := dateparse.ParseIn(p.Published, time.Local)
|
||||
updated, _ := dateparse.ParseIn(p.Updated, time.Local)
|
||||
var enc *feeds.Enclosure
|
||||
if p.firstParameter(feedAudioURL) != "" {
|
||||
enc = &feeds.Enclosure{
|
||||
Url: p.firstParameter(feedAudioURL),
|
||||
Type: p.firstParameter(feedAudioType),
|
||||
Length: p.firstParameter(feedAudioLength),
|
||||
}
|
||||
}
|
||||
feed.Add(&feeds.Item{
|
||||
Title: p.title(),
|
||||
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + p.Path},
|
||||
Description: p.summary(),
|
||||
Id: p.Path,
|
||||
Content: string(p.html()),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
Enclosure: enc,
|
||||
})
|
||||
}
|
||||
var feedStr string
|
||||
|
|
5
go.mod
5
go.mod
|
@ -36,9 +36,10 @@ require (
|
|||
github.com/yuin/goldmark v1.2.1
|
||||
github.com/yuin/goldmark-emoji v1.0.1
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 // indirect
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20201026133411-418715ba6fdd // indirect
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -342,8 +342,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
|
||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -374,14 +374,16 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201026133411-418715ba6fdd h1:+7OQgGrJBd80e8ASl94G3xIpokulXXzB/dikfre4ho0=
|
||||
golang.org/x/sys v0.0.0-20201026133411-418715ba6fdd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
44
http.go
44
http.go
|
@ -6,7 +6,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
|
@ -185,9 +184,10 @@ func buildHandler() (http.Handler, error) {
|
|||
for _, section := range blogConfig.Sections {
|
||||
if section.Name != "" {
|
||||
path := blogPath + "/" + section.Name
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path, serveSection(blog, path, section))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, serveSection(blog, path, section))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, serveSection(blog, path, section))
|
||||
handler := serveSection(blog, path, section)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, handler)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,17 +201,19 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
for _, tv := range values {
|
||||
vPath := path + "/" + urlize(tv)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath, serveTaxonomyValue(blog, vPath, taxonomy, tv))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, serveTaxonomyValue(blog, vPath, taxonomy, tv))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, serveTaxonomyValue(blog, vPath, taxonomy, tv))
|
||||
handler := serveTaxonomyValue(blog, vPath, taxonomy, tv)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Photos
|
||||
if blogConfig.Photos.Enabled {
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path, servePhotos(blog))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path+paginationPath, servePhotos(blog))
|
||||
handler := servePhotos(blog)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path+paginationPath, handler)
|
||||
}
|
||||
|
||||
// Blog
|
||||
|
@ -221,17 +223,18 @@ func buildHandler() (http.Handler, error) {
|
|||
} else {
|
||||
mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
|
||||
}
|
||||
r.With(mw...).Get(fullBlogPath, serveHome(blog, blogPath))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, serveHome(blog, blogPath))
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, serveHome(blog, blogPath))
|
||||
handler := serveHome(blog, blogPath)
|
||||
r.With(mw...).Get(fullBlogPath, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, handler)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, handler)
|
||||
|
||||
// Custom pages
|
||||
for _, cp := range blogConfig.CustomPages {
|
||||
serveFunc := serveCustomPage(blogConfig, cp)
|
||||
handler := serveCustomPage(blogConfig, cp)
|
||||
if cp.Cache {
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, serveFunc)
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, handler)
|
||||
} else {
|
||||
r.With(minifier.Middleware).Get(cp.Path, serveFunc)
|
||||
r.With(minifier.Middleware).Get(cp.Path, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,14 +271,3 @@ func (d *dynamicHandler) swapHandler(h http.Handler) {
|
|||
func (d *dynamicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
d.realHandler.Load().(http.Handler).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func slashTrimmedPath(r *http.Request) string {
|
||||
return trimSlash(r.URL.Path)
|
||||
}
|
||||
|
||||
func trimSlash(s string) string {
|
||||
if len(s) > 1 {
|
||||
s = strings.TrimSuffix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
7
posts.go
7
posts.go
|
@ -46,8 +46,13 @@ func servePost(w http.ResponseWriter, r *http.Request) {
|
|||
p.serveActivityStreams(w)
|
||||
return
|
||||
}
|
||||
canonical := p.firstParameter("original")
|
||||
if canonical == "" {
|
||||
canonical = appConfig.Server.PublicAddress + p.Path
|
||||
}
|
||||
render(w, templatePost, &renderData{
|
||||
blogString: p.Blog,
|
||||
Canonical: canonical,
|
||||
Data: p,
|
||||
})
|
||||
}
|
||||
|
@ -121,6 +126,7 @@ func serveTaxonomy(blog string, tax *taxonomy) func(w http.ResponseWriter, r *ht
|
|||
}
|
||||
render(w, templateTaxonomy, &renderData{
|
||||
blogString: blog,
|
||||
Canonical: appConfig.Server.PublicAddress + slashTrimmedPath(r),
|
||||
Data: struct {
|
||||
Taxonomy *taxonomy
|
||||
TaxonomyValues []string
|
||||
|
@ -214,6 +220,7 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
render(w, template, &renderData{
|
||||
blogString: ic.blog,
|
||||
Canonical: appConfig.Server.PublicAddress + slashTrimmedPath(r),
|
||||
Data: &indexTemplateData{
|
||||
Title: title,
|
||||
Description: description,
|
||||
|
|
24
postsDb.go
24
postsDb.go
|
@ -66,21 +66,6 @@ func (p *post) checkPost() error {
|
|||
p.Slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
||||
}
|
||||
published, _ := dateparse.ParseIn(p.Published, time.Local)
|
||||
pathVars := struct {
|
||||
BlogPath string
|
||||
Year int
|
||||
Month int
|
||||
Day int
|
||||
Slug string
|
||||
Section string
|
||||
}{
|
||||
BlogPath: appConfig.Blogs[p.Blog].Path,
|
||||
Year: published.Year(),
|
||||
Month: int(published.Month()),
|
||||
Day: published.Day(),
|
||||
Slug: p.Slug,
|
||||
Section: p.Section,
|
||||
}
|
||||
pathTmplString := appConfig.Blogs[p.Blog].Sections[p.Section].PathTemplate
|
||||
if pathTmplString == "" {
|
||||
return errors.New("path template empty")
|
||||
|
@ -90,7 +75,14 @@ func (p *post) checkPost() error {
|
|||
return errors.New("failed to parse location template")
|
||||
}
|
||||
var pathBuffer bytes.Buffer
|
||||
err = pathTmpl.Execute(&pathBuffer, pathVars)
|
||||
err = pathTmpl.Execute(&pathBuffer, map[string]interface{}{
|
||||
"BlogPath": appConfig.Blogs[p.Blog].Path,
|
||||
"Year": published.Year(),
|
||||
"Month": int(published.Month()),
|
||||
"Day": published.Day(),
|
||||
"Slug": p.Slug,
|
||||
"Section": p.Section,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("failed to execute location template")
|
||||
}
|
||||
|
|
63
render.go
63
render.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -39,6 +41,9 @@ func initRendering() error {
|
|||
"menu": func(blog *configBlog, id string) *menu {
|
||||
return blog.Menus[id]
|
||||
},
|
||||
"user": func() *configUser {
|
||||
return appConfig.User
|
||||
},
|
||||
"md": func(content string) template.HTML {
|
||||
htmlContent, err := renderMarkdown(content)
|
||||
if err != nil {
|
||||
|
@ -85,18 +90,61 @@ func initRendering() error {
|
|||
ml := monday.Locale(localeString)
|
||||
return monday.Format(d, monday.LongFormatsByLocale[ml], ml)
|
||||
},
|
||||
"now": func() string {
|
||||
return time.Now().String()
|
||||
},
|
||||
"asset": assetFile,
|
||||
"string": getTemplateStringVariant,
|
||||
"include": func(templateName string, blog *configBlog, data interface{}) (template.HTML, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := templates[templateName].ExecuteTemplate(buf, templateName, &renderData{
|
||||
Blog: blog,
|
||||
Data: data,
|
||||
})
|
||||
return template.HTML(buf.String()), err
|
||||
"include": func(templateName string, data ...interface{}) (template.HTML, error) {
|
||||
if len(data) == 1 {
|
||||
if rd, ok := data[0].(*renderData); ok {
|
||||
buf := new(bytes.Buffer)
|
||||
err := templates[templateName].ExecuteTemplate(buf, templateName, rd)
|
||||
return template.HTML(buf.String()), err
|
||||
}
|
||||
return "", errors.New("wrong argument")
|
||||
} else if len(data) == 2 {
|
||||
if blog, ok := data[0].(*configBlog); ok {
|
||||
buf := new(bytes.Buffer)
|
||||
err := templates[templateName].ExecuteTemplate(buf, templateName, &renderData{
|
||||
Blog: blog,
|
||||
Data: data[1],
|
||||
})
|
||||
return template.HTML(buf.String()), err
|
||||
}
|
||||
return "", errors.New("wrong arguments")
|
||||
}
|
||||
return "", errors.New("wrong argument count")
|
||||
},
|
||||
"default": func(dflt interface{}, given ...interface{}) interface{} {
|
||||
if len(given) == 0 {
|
||||
return dflt
|
||||
}
|
||||
g := reflect.ValueOf(given[0])
|
||||
if !g.IsValid() {
|
||||
return dflt
|
||||
}
|
||||
set := false
|
||||
switch g.Kind() {
|
||||
case reflect.Bool:
|
||||
set = true
|
||||
case reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||
set = g.Len() != 0
|
||||
case reflect.Int:
|
||||
set = g.Int() != 0
|
||||
default:
|
||||
set = !g.IsNil()
|
||||
}
|
||||
if set {
|
||||
return given[0]
|
||||
}
|
||||
return dflt
|
||||
},
|
||||
"urlize": urlize,
|
||||
"sort": sortedStrings,
|
||||
"absolute": func(path string) string {
|
||||
return appConfig.Server.PublicAddress + path
|
||||
},
|
||||
"blogRelative": func(blog *configBlog, path string) string {
|
||||
return blog.getRelativePath(path)
|
||||
},
|
||||
|
@ -138,6 +186,7 @@ func initRendering() error {
|
|||
|
||||
type renderData struct {
|
||||
blogString string
|
||||
Canonical string
|
||||
Blog *configBlog
|
||||
Data interface{}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
"use strict";
|
||||
|
||||
function getVoice() {
|
||||
if (window.speechSynthesis) {
|
||||
return window.speechSynthesis.getVoices().filter(voice => voice.lang.startsWith(document.querySelector('html').lang))[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function initSpeak() {
|
||||
if (window.speechSynthesis) {
|
||||
let speakBtn = document.querySelector('#speakBtn');
|
||||
speakBtn.style.display = '';
|
||||
speakBtn.onclick = function() { speak() };
|
||||
speakBtn.textContent = speakText;
|
||||
}
|
||||
}
|
||||
|
||||
function speak() {
|
||||
console.log("Start speaking")
|
||||
let speakBtn = document.querySelector('#speakBtn');
|
||||
speakBtn.onclick = function() { stopSpeak() };
|
||||
speakBtn.textContent = stopSpeakText;
|
||||
let textContent =
|
||||
((document.querySelector('article .p-name')) ? document.querySelector('article .p-name').innerText + "\n\n" : "")
|
||||
+ document.querySelector('article .e-content').innerText;
|
||||
let utterThis = new SpeechSynthesisUtterance(textContent);
|
||||
utterThis.voice = getVoice();
|
||||
utterThis.onerror = stopSpeak;
|
||||
utterThis.onend = stopSpeak;
|
||||
window.speechSynthesis.speak(utterThis);
|
||||
}
|
||||
|
||||
function stopSpeak() {
|
||||
console.log("Stop speaking")
|
||||
window.speechSynthesis.cancel();
|
||||
let speakBtn = document.querySelector('#speakBtn');
|
||||
speakBtn.onclick = function() { speak() };
|
||||
speakBtn.textContent = speakText;
|
||||
}
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
stopSpeak();
|
||||
}
|
||||
initSpeak();
|
|
@ -0,0 +1,10 @@
|
|||
{{ define "author" }}
|
||||
{{ with user }}
|
||||
<div class="p-author h-card hide">
|
||||
{{ with .Picture }}<data class="u-photo" value="{{ . }}"></data>{{ end }}
|
||||
{{ if .Name }}
|
||||
<a href="{{ .Link | default "/" }}" class="p-name u-url" rel="me">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -5,8 +5,11 @@
|
|||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv=x-ua-compatible content="IE=edge">
|
||||
<link rel="stylesheet" href="{{ asset "css/styles.css" }}">
|
||||
{{ with user.Picture }}<link rel="shortcut icon" href="{{ . }}">{{ end }}
|
||||
{{ with .Canonical }}<link rel="canonical" href="{{ . }}">{{ end }}
|
||||
{{ template "title" . }}
|
||||
{{ include "micropub" .Blog .Data }}
|
||||
{{ include "header" .Blog .Data }}
|
||||
{{ include "micropub" . }}
|
||||
{{ include "header" . }}
|
||||
{{ template "main" . }}
|
||||
{{ include "footer" . }}
|
||||
{{ end }}
|
|
@ -0,0 +1,12 @@
|
|||
{{ define "footer" }}
|
||||
<footer>
|
||||
{{ with menu .Blog "footer" }}
|
||||
{{ $first := true }}
|
||||
{{ range $i, $item := .Items }}
|
||||
{{ if ne $first true }} • {{ end }}<a
|
||||
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<p>© {{ dateformat now "2006" }} {{ .Blog.Title }}</p>
|
||||
</footer>
|
||||
{{ end }}
|
|
@ -2,6 +2,14 @@
|
|||
<header>
|
||||
<h1><a href="{{ blogRelative .Blog "/" }}" rel="home" title="{{ .Blog.Title }}">{{ .Blog.Title }}</a></h1>
|
||||
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
|
||||
{{ include "menu" .Blog .Data }}
|
||||
<nav>
|
||||
{{ with menu .Blog "main" }}
|
||||
{{ $first := true }}
|
||||
{{ range $i, $item := .Items }}
|
||||
{{ if ne $first true }} • {{ end }}<a
|
||||
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
||||
</header>
|
||||
{{ end }}
|
|
@ -1,11 +0,0 @@
|
|||
{{ define "menu" }}
|
||||
<nav>
|
||||
{{ with menu .Blog "main" }}
|
||||
{{ $first := true }}
|
||||
{{ range $i, $item := .Items }}
|
||||
{{ if ne $first true }} • {{ end }}<a
|
||||
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
||||
{{ end }}
|
|
@ -1,12 +1,11 @@
|
|||
{{ define "photosummary" }}
|
||||
<article>
|
||||
{{ with p .Data "title" }}<h2>{{ . }}</h2>{{ end }}
|
||||
{{ if .Data.Published }}<p>{{ longDate .Data.Published .Blog.Lang }}</p>{{ end }}
|
||||
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
||||
{{ md ( printf "![](%s)" $photo ) }}
|
||||
{{ end }}
|
||||
<p>{{ summary .Data }}</p>
|
||||
<a href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a>
|
||||
<article class="h-entry border-bottom">
|
||||
{{ with p .Data "title" }}<h2>{{ . }}</h2>{{ end }}
|
||||
{{ include "postmeta" . }}
|
||||
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
||||
{{ md ( printf "![](%s)" $photo ) }}
|
||||
{{ end }}
|
||||
<p class="p-summary">{{ summary .Data }}</p>
|
||||
<p>{{ if (hasp .Data "images") }}🖼️ {{ end }}<a class="u-url" href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a></p>
|
||||
</article>
|
||||
<hr>
|
||||
{{ end }}
|
|
@ -1,34 +1,29 @@
|
|||
{{ define "title" }}
|
||||
<title>{{ with p .Data "title" }}{{ . }} - {{end}}{{ .Blog.Title }}</title>
|
||||
{{ include "postheadmeta" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<main class=h-entry>
|
||||
<article>
|
||||
{{ with title .Data }}<h1 class=p-name>{{ . }}</h1>{{ end }}
|
||||
{{ if .Data.Published }}
|
||||
<p>{{ string .Blog.Lang "publishedon" }} {{ longDate .Data.Published .Blog.Lang }}</p>{{ end }}
|
||||
{{ if .Data.Updated }}
|
||||
<p>{{ string .Blog.Lang "updatedon" }} {{ longDate .Data.Updated .Blog.Lang }}</p>{{ end }}
|
||||
{{ if .Data.Content }}
|
||||
<div class=e-content>{{ content .Data }}</div>
|
||||
{{ end }}
|
||||
</article>
|
||||
{{ $post := .Data }}
|
||||
{{ $blog := .Blog }}
|
||||
{{ range $i, $tax := $blog.Taxonomies }}
|
||||
{{ $tvs := ps $post $tax.Name }}
|
||||
{{ if gt (len $tvs) 0 }}
|
||||
<p>In <b>{{ $tax.Title }}</b>:
|
||||
{{ range $j, $tv := $tvs }}
|
||||
<a href="{{ blogRelative $blog ( printf "/%s/%s" $tax.Name (urlize $tv) ) }}">{{ $tv }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
|
||||
{{ with title .Data }}<h1 class=p-name>{{ . }}</h1>{{ end }}
|
||||
{{ include "postmeta" . }}
|
||||
{{ include "postactions" . }}
|
||||
{{ if .Data.Content }}
|
||||
<div class=e-content>
|
||||
{{ with p .Data "audio" }}
|
||||
<audio controls preload="metadata" class="fw"><source src="{{ . }}"/></audio>
|
||||
{{ end }}
|
||||
{{ content .Data }}
|
||||
{{ with p .Data "link" }}
|
||||
<p><a class="u-bookmark-of" href="{{ . }}" target="_blank" rel="noopener">{{ . }}</a></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ range $i, $t := (translations .Data) }}
|
||||
<p><a href="{{ $t.Path }}">{{ (blog $t.Blog).Title }}</a></p>
|
||||
{{ end }}
|
||||
{{ include "posttax" . }}
|
||||
</article>
|
||||
{{ include "author" . }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{{ define "postactions" }}
|
||||
<div class="p flex" id="post-actions">
|
||||
<a href="https://www.addtoany.com/share#url={{ absolute .Data.Path }}{{ with title .Data }}&title={{ . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button invert">{{ string .Blog.Lang "share" }}</a>
|
||||
<button id="speakBtn" class="invert" style="display: none;"></button>
|
||||
<script>const speakText = "{{ string .Blog.Lang "speak" }}";const stopSpeakText = "{{ string .Blog.Lang "stopspeak" }}";</script>
|
||||
<script defer src="{{ asset "js/speak.js" }}"></script>
|
||||
</div>
|
||||
{{ end }}
|
|
@ -0,0 +1,13 @@
|
|||
{{ define "postheadmeta" }}
|
||||
<meta name="description" content="{{ with summary .Data }}{{ . }}{{ end }}">
|
||||
{{ $ISO8601 := "2006-01-02T15:04:05-07:00" }}
|
||||
{{ if .Data.Published }}
|
||||
<meta itemprop="datePublished" content="{{ dateformat .Data.Published $ISO8601 }}">
|
||||
{{ end }}
|
||||
{{ if .Data.Updated }}
|
||||
<meta itemprop="dateModified" content="{{ dateformat .Data.Updated $ISO8601 }}">
|
||||
{{ end }}
|
||||
{{ range $key, $image := ps .Data "images" }}
|
||||
<meta itemprop="image" content="{{ $image }}">
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -0,0 +1,17 @@
|
|||
{{ define "postmeta" }}
|
||||
<div class="p">
|
||||
{{ $section := (index .Blog.Sections .Data.Section) }}
|
||||
{{ if .Data.Published }}<div>{{ string .Blog.Lang "publishedon" }} <time class="dt-published" datetime="{{ dateformat .Data.Published "2006-01-02T15:04:05Z07:00"}}">{{ longDate .Data.Published .Blog.TimeLang }}</time>{{ if $section }} in <a href="{{ blogRelative .Blog $section.Name }}">{{ $section.Title }}</a>{{ end }}</div>{{ end }}
|
||||
{{ if .Data.Updated }}<div>{{ string .Blog.Lang "updatedon" }} <time class="dt-updated" datetime="{{ dateformat .Data.Updated "2006-01-02T15:04:05Z07:00"}}">{{ longDate .Data.Updated .Blog.TimeLang }}</time></div>{{ end }}
|
||||
{{ if p .Data "replylink" }}
|
||||
<div>{{ string .Blog.Lang "replyto" }}: <a class="u-in-reply-to" href="{{ p .Data "replylink" }}" target="_blank" rel="noopener">{{ p .Data "replytitle" | default (p .Data "replylink") }}</a></div>
|
||||
{{ end }}
|
||||
{{ if p .Data "likelink" }}
|
||||
<div>{{ string .Blog.Lang "likeof" }}: <a class="u-like-of" href="{{ p .Data "likelink" }}" target="_blank" rel="noopener">{{ p .Data "liketitle" | default (p .Data "likelink") }}</a></div>
|
||||
{{ end }}
|
||||
{{ $translations := (translations .Data) }}
|
||||
{{ if gt (len $translations) 0 }}
|
||||
<div>{{ string .Blog.Lang "translations" }}: {{ $delimiter := "" }}{{ range $i, $t := $translations }}{{ $delimiter }}<a href="{{ $t.Path }}">{{ title $t }}</a>{{ $delimiter = ", " }}{{ end }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
|
@ -0,0 +1,14 @@
|
|||
{{ define "posttax" }}
|
||||
{{ $post := .Data }}
|
||||
{{ $blog := .Blog }}
|
||||
{{ range $i, $tax := $blog.Taxonomies }}
|
||||
{{ $tvs := ps $post $tax.Name }}
|
||||
{{ if gt (len $tvs) 0 }}
|
||||
<p><b>{{ $tax.Title }}</b>:
|
||||
{{ range $j, $tv := $tvs }}
|
||||
<a class="p-category" rel="tag" href="{{ blogRelative $blog ( printf "/%s/%s" $tax.Name (urlize $tv) ) }}">{{ $tv }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
|
@ -0,0 +1,11 @@
|
|||
publishedon: "Veröffentlicht am"
|
||||
updatedon: "Aktualisiert am"
|
||||
next: "Weiter"
|
||||
prev: "Zurück"
|
||||
view: "Anschauen"
|
||||
replyto: "Antwort an"
|
||||
likeof: "Gefällt mir von"
|
||||
translations: "Übersetzungen"
|
||||
share: "Teilen"
|
||||
speak: "Lies mir bitte vor."
|
||||
stopspeak: "Hör auf zu sprechen!"
|
|
@ -1,5 +0,0 @@
|
|||
publishedon: "Veröffentlicht am"
|
||||
updatedon: "Aktualisiert am"
|
||||
next: "Weiter"
|
||||
prev: "Zurück"
|
||||
view: "Anschauen"
|
|
@ -6,3 +6,9 @@ view: "View"
|
|||
authenticate: "Authenticate"
|
||||
scopes: "Scopes"
|
||||
indieauth: "IndieAuth"
|
||||
replyto: "Reply to"
|
||||
likeof: "Like of"
|
||||
translations: "Translations"
|
||||
share: "Share"
|
||||
speak: "Read to me, please."
|
||||
stopspeak: "Stop speaking!"
|
|
@ -1,13 +1,13 @@
|
|||
{{ define "summary" }}
|
||||
<article class="h-entry border-bottom">
|
||||
{{ if p .Data "title" }}
|
||||
{{ if p .Data "title" }}
|
||||
<h2 class="p-name">
|
||||
<a class="u-url" href="{{ .Data.Path }}">
|
||||
{{ p .Data "title" }}
|
||||
</a>
|
||||
</h2>
|
||||
{{ end }}
|
||||
{{ include "summarymeta" .Blog .Data }}
|
||||
{{ end }}
|
||||
{{ include "postmeta" . }}
|
||||
<p class="p-summary">{{ summary .Data }}</p>
|
||||
<p>{{ if (hasp .Data "images") }}🖼️ {{ end }}<a class="u-url" href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a></p>
|
||||
</article>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{{ define "summarymeta" }}
|
||||
<div class="p">
|
||||
{{ $section := (index .Blog.Sections .Data.Section) }}
|
||||
{{ if .Data.Published }}
|
||||
<div><time class="dt-published" datetime="{{ dateformat .Data.Published "2006-01-02T15:04:05Z07:00" }}">{{ longDate .Data.Published .Blog.Lang }}</time>{{ if $section }} in <a href="{{ blogRelative .Blog $section.Name }}">{{ $section.Title }}</a>{{ end }}</div>
|
||||
{{ end }}
|
||||
{{ if .Data.Updated }}
|
||||
<div>{{ string .Blog.Lang "updatedon" }} <time class="dt-updated" datetime="{{ dateformat .Data.Updated "2006-01-02T15:04:05Z07:00" }}">{{ longDate .Data.Updated .Blog.Lang }}</time></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
12
utils.go
12
utils.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -35,3 +36,14 @@ func generateRandomString(chars int) string {
|
|||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func slashTrimmedPath(r *http.Request) string {
|
||||
return trimSlash(r.URL.Path)
|
||||
}
|
||||
|
||||
func trimSlash(s string) string {
|
||||
if len(s) > 1 {
|
||||
s = strings.TrimSuffix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue