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)
|
_ = json.NewEncoder(w).Encode(asBlog)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ type configCache struct {
|
||||||
type configBlog struct {
|
type configBlog struct {
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
Lang string `mapstructure:"lang"`
|
Lang string `mapstructure:"lang"`
|
||||||
|
TimeLang string `mapstructure:"timelang"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
Pagination int `mapstructure:"pagination"`
|
Pagination int `mapstructure:"pagination"`
|
||||||
|
@ -104,6 +105,7 @@ type configUser struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
Picture string `mapstructure:"picture"`
|
Picture string `mapstructure:"picture"`
|
||||||
|
Link string `mapstructure:"link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type configHooks struct {
|
type configHooks struct {
|
||||||
|
|
|
@ -5,8 +5,9 @@ import "net/http"
|
||||||
func serveCustomPage(blog *configBlog, page *customPage) func(w http.ResponseWriter, r *http.Request) {
|
func serveCustomPage(blog *configBlog, page *customPage) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
render(w, page.Template, &renderData{
|
render(w, page.Template, &renderData{
|
||||||
Blog: blog,
|
Blog: blog,
|
||||||
Data: page.Data,
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/araddon/dateparse"
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,6 +16,10 @@ const (
|
||||||
rssFeed feedType = "rss"
|
rssFeed feedType = "rss"
|
||||||
atomFeed feedType = "atom"
|
atomFeed feedType = "atom"
|
||||||
jsonFeed feedType = "json"
|
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) {
|
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,
|
Description: description,
|
||||||
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + strings.TrimSuffix(r.URL.Path, "."+string(f))},
|
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + strings.TrimSuffix(r.URL.Path, "."+string(f))},
|
||||||
Created: now,
|
Created: now,
|
||||||
|
Author: &feeds.Author{
|
||||||
|
Name: appConfig.User.Name,
|
||||||
|
},
|
||||||
|
Image: &feeds.Image{
|
||||||
|
Url: appConfig.User.Picture,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, p := range posts {
|
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{
|
feed.Add(&feeds.Item{
|
||||||
Title: p.title(),
|
Title: p.title(),
|
||||||
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + p.Path},
|
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + p.Path},
|
||||||
Description: p.summary(),
|
Description: p.summary(),
|
||||||
Id: p.Path,
|
Id: p.Path,
|
||||||
Content: string(p.html()),
|
Content: string(p.html()),
|
||||||
|
Created: created,
|
||||||
|
Updated: updated,
|
||||||
|
Enclosure: enc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var feedStr string
|
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 v1.2.1
|
||||||
github.com/yuin/goldmark-emoji v1.0.1
|
github.com/yuin/goldmark-emoji v1.0.1
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
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/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/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.3.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-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 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-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-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||||
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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 h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-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-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
|
||||||
golang.org/x/sys v0.0.0-20201026133411-418715ba6fdd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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.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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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-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/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=
|
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"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
@ -185,9 +184,10 @@ func buildHandler() (http.Handler, error) {
|
||||||
for _, section := range blogConfig.Sections {
|
for _, section := range blogConfig.Sections {
|
||||||
if section.Name != "" {
|
if section.Name != "" {
|
||||||
path := blogPath + "/" + section.Name
|
path := blogPath + "/" + section.Name
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(path, serveSection(blog, path, section))
|
handler := serveSection(blog, path, section)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, serveSection(blog, path, section))
|
r.With(cacheMiddleware, minifier.Middleware).Get(path, handler)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, serveSection(blog, path, section))
|
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 {
|
for _, tv := range values {
|
||||||
vPath := path + "/" + urlize(tv)
|
vPath := path + "/" + urlize(tv)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath, serveTaxonomyValue(blog, vPath, taxonomy, tv))
|
handler := 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, handler)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, serveTaxonomyValue(blog, vPath, taxonomy, tv))
|
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, handler)
|
||||||
|
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Photos
|
// Photos
|
||||||
if blogConfig.Photos.Enabled {
|
if blogConfig.Photos.Enabled {
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path, servePhotos(blog))
|
handler := servePhotos(blog)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+blogConfig.Photos.Path+paginationPath, 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
|
// Blog
|
||||||
|
@ -221,17 +223,18 @@ func buildHandler() (http.Handler, error) {
|
||||||
} else {
|
} else {
|
||||||
mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
|
mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
|
||||||
}
|
}
|
||||||
r.With(mw...).Get(fullBlogPath, serveHome(blog, blogPath))
|
handler := serveHome(blog, blogPath)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, serveHome(blog, blogPath))
|
r.With(mw...).Get(fullBlogPath, handler)
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, serveHome(blog, blogPath))
|
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, handler)
|
||||||
|
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, handler)
|
||||||
|
|
||||||
// Custom pages
|
// Custom pages
|
||||||
for _, cp := range blogConfig.CustomPages {
|
for _, cp := range blogConfig.CustomPages {
|
||||||
serveFunc := serveCustomPage(blogConfig, cp)
|
handler := serveCustomPage(blogConfig, cp)
|
||||||
if cp.Cache {
|
if cp.Cache {
|
||||||
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, serveFunc)
|
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, handler)
|
||||||
} else {
|
} 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) {
|
func (d *dynamicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
d.realHandler.Load().(http.Handler).ServeHTTP(w, r)
|
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)
|
p.serveActivityStreams(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
canonical := p.firstParameter("original")
|
||||||
|
if canonical == "" {
|
||||||
|
canonical = appConfig.Server.PublicAddress + p.Path
|
||||||
|
}
|
||||||
render(w, templatePost, &renderData{
|
render(w, templatePost, &renderData{
|
||||||
blogString: p.Blog,
|
blogString: p.Blog,
|
||||||
|
Canonical: canonical,
|
||||||
Data: p,
|
Data: p,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -121,6 +126,7 @@ func serveTaxonomy(blog string, tax *taxonomy) func(w http.ResponseWriter, r *ht
|
||||||
}
|
}
|
||||||
render(w, templateTaxonomy, &renderData{
|
render(w, templateTaxonomy, &renderData{
|
||||||
blogString: blog,
|
blogString: blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + slashTrimmedPath(r),
|
||||||
Data: struct {
|
Data: struct {
|
||||||
Taxonomy *taxonomy
|
Taxonomy *taxonomy
|
||||||
TaxonomyValues []string
|
TaxonomyValues []string
|
||||||
|
@ -214,6 +220,7 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
render(w, template, &renderData{
|
render(w, template, &renderData{
|
||||||
blogString: ic.blog,
|
blogString: ic.blog,
|
||||||
|
Canonical: appConfig.Server.PublicAddress + slashTrimmedPath(r),
|
||||||
Data: &indexTemplateData{
|
Data: &indexTemplateData{
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: description,
|
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)
|
p.Slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
||||||
}
|
}
|
||||||
published, _ := dateparse.ParseIn(p.Published, time.Local)
|
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
|
pathTmplString := appConfig.Blogs[p.Blog].Sections[p.Section].PathTemplate
|
||||||
if pathTmplString == "" {
|
if pathTmplString == "" {
|
||||||
return errors.New("path template empty")
|
return errors.New("path template empty")
|
||||||
|
@ -90,7 +75,14 @@ func (p *post) checkPost() error {
|
||||||
return errors.New("failed to parse location template")
|
return errors.New("failed to parse location template")
|
||||||
}
|
}
|
||||||
var pathBuffer bytes.Buffer
|
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 {
|
if err != nil {
|
||||||
return errors.New("failed to execute location template")
|
return errors.New("failed to execute location template")
|
||||||
}
|
}
|
||||||
|
|
63
render.go
63
render.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,6 +41,9 @@ func initRendering() error {
|
||||||
"menu": func(blog *configBlog, id string) *menu {
|
"menu": func(blog *configBlog, id string) *menu {
|
||||||
return blog.Menus[id]
|
return blog.Menus[id]
|
||||||
},
|
},
|
||||||
|
"user": func() *configUser {
|
||||||
|
return appConfig.User
|
||||||
|
},
|
||||||
"md": func(content string) template.HTML {
|
"md": func(content string) template.HTML {
|
||||||
htmlContent, err := renderMarkdown(content)
|
htmlContent, err := renderMarkdown(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,18 +90,61 @@ func initRendering() error {
|
||||||
ml := monday.Locale(localeString)
|
ml := monday.Locale(localeString)
|
||||||
return monday.Format(d, monday.LongFormatsByLocale[ml], ml)
|
return monday.Format(d, monday.LongFormatsByLocale[ml], ml)
|
||||||
},
|
},
|
||||||
|
"now": func() string {
|
||||||
|
return time.Now().String()
|
||||||
|
},
|
||||||
"asset": assetFile,
|
"asset": assetFile,
|
||||||
"string": getTemplateStringVariant,
|
"string": getTemplateStringVariant,
|
||||||
"include": func(templateName string, blog *configBlog, data interface{}) (template.HTML, error) {
|
"include": func(templateName string, data ...interface{}) (template.HTML, error) {
|
||||||
buf := new(bytes.Buffer)
|
if len(data) == 1 {
|
||||||
err := templates[templateName].ExecuteTemplate(buf, templateName, &renderData{
|
if rd, ok := data[0].(*renderData); ok {
|
||||||
Blog: blog,
|
buf := new(bytes.Buffer)
|
||||||
Data: data,
|
err := templates[templateName].ExecuteTemplate(buf, templateName, rd)
|
||||||
})
|
return template.HTML(buf.String()), err
|
||||||
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,
|
"urlize": urlize,
|
||||||
"sort": sortedStrings,
|
"sort": sortedStrings,
|
||||||
|
"absolute": func(path string) string {
|
||||||
|
return appConfig.Server.PublicAddress + path
|
||||||
|
},
|
||||||
"blogRelative": func(blog *configBlog, path string) string {
|
"blogRelative": func(blog *configBlog, path string) string {
|
||||||
return blog.getRelativePath(path)
|
return blog.getRelativePath(path)
|
||||||
},
|
},
|
||||||
|
@ -138,6 +186,7 @@ func initRendering() error {
|
||||||
|
|
||||||
type renderData struct {
|
type renderData struct {
|
||||||
blogString string
|
blogString string
|
||||||
|
Canonical string
|
||||||
Blog *configBlog
|
Blog *configBlog
|
||||||
Data interface{}
|
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 name=viewport content="width=device-width,initial-scale=1">
|
||||||
<meta http-equiv=x-ua-compatible content="IE=edge">
|
<meta http-equiv=x-ua-compatible content="IE=edge">
|
||||||
<link rel="stylesheet" href="{{ asset "css/styles.css" }}">
|
<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" . }}
|
{{ template "title" . }}
|
||||||
{{ include "micropub" .Blog .Data }}
|
{{ include "micropub" . }}
|
||||||
{{ include "header" .Blog .Data }}
|
{{ include "header" . }}
|
||||||
{{ template "main" . }}
|
{{ template "main" . }}
|
||||||
|
{{ include "footer" . }}
|
||||||
{{ end }}
|
{{ 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>
|
<header>
|
||||||
<h1><a href="{{ blogRelative .Blog "/" }}" rel="home" title="{{ .Blog.Title }}">{{ .Blog.Title }}</a></h1>
|
<h1><a href="{{ blogRelative .Blog "/" }}" rel="home" title="{{ .Blog.Title }}">{{ .Blog.Title }}</a></h1>
|
||||||
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
|
{{ 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>
|
</header>
|
||||||
{{ end }}
|
{{ 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" }}
|
{{ define "photosummary" }}
|
||||||
<article>
|
<article class="h-entry border-bottom">
|
||||||
{{ with p .Data "title" }}<h2>{{ . }}</h2>{{ end }}
|
{{ with p .Data "title" }}<h2>{{ . }}</h2>{{ end }}
|
||||||
{{ if .Data.Published }}<p>{{ longDate .Data.Published .Blog.Lang }}</p>{{ end }}
|
{{ include "postmeta" . }}
|
||||||
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
||||||
{{ md ( printf "![](%s)" $photo ) }}
|
{{ md ( printf "![](%s)" $photo ) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<p>{{ summary .Data }}</p>
|
<p class="p-summary">{{ summary .Data }}</p>
|
||||||
<a href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a>
|
<p>{{ if (hasp .Data "images") }}🖼️ {{ end }}<a class="u-url" href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a></p>
|
||||||
</article>
|
</article>
|
||||||
<hr>
|
|
||||||
{{ end }}
|
{{ end }}
|
|
@ -1,34 +1,29 @@
|
||||||
{{ define "title" }}
|
{{ define "title" }}
|
||||||
<title>{{ with p .Data "title" }}{{ . }} - {{end}}{{ .Blog.Title }}</title>
|
<title>{{ with p .Data "title" }}{{ . }} - {{end}}{{ .Blog.Title }}</title>
|
||||||
|
{{ include "postheadmeta" . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
<main class=h-entry>
|
<main class=h-entry>
|
||||||
<article>
|
<article>
|
||||||
{{ with title .Data }}<h1 class=p-name>{{ . }}</h1>{{ end }}
|
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
|
||||||
{{ if .Data.Published }}
|
{{ with title .Data }}<h1 class=p-name>{{ . }}</h1>{{ end }}
|
||||||
<p>{{ string .Blog.Lang "publishedon" }} {{ longDate .Data.Published .Blog.Lang }}</p>{{ end }}
|
{{ include "postmeta" . }}
|
||||||
{{ if .Data.Updated }}
|
{{ include "postactions" . }}
|
||||||
<p>{{ string .Blog.Lang "updatedon" }} {{ longDate .Data.Updated .Blog.Lang }}</p>{{ end }}
|
{{ if .Data.Content }}
|
||||||
{{ if .Data.Content }}
|
<div class=e-content>
|
||||||
<div class=e-content>{{ content .Data }}</div>
|
{{ with p .Data "audio" }}
|
||||||
{{ end }}
|
<audio controls preload="metadata" class="fw"><source src="{{ . }}"/></audio>
|
||||||
</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>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ content .Data }}
|
||||||
|
{{ with p .Data "link" }}
|
||||||
|
<p><a class="u-bookmark-of" href="{{ . }}" target="_blank" rel="noopener">{{ . }}</a></p>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ range $i, $t := (translations .Data) }}
|
{{ include "posttax" . }}
|
||||||
<p><a href="{{ $t.Path }}">{{ (blog $t.Blog).Title }}</a></p>
|
</article>
|
||||||
{{ end }}
|
{{ include "author" . }}
|
||||||
</main>
|
</main>
|
||||||
{{ end }}
|
{{ 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"
|
authenticate: "Authenticate"
|
||||||
scopes: "Scopes"
|
scopes: "Scopes"
|
||||||
indieauth: "IndieAuth"
|
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" }}
|
{{ define "summary" }}
|
||||||
<article class="h-entry border-bottom">
|
<article class="h-entry border-bottom">
|
||||||
{{ if p .Data "title" }}
|
{{ if p .Data "title" }}
|
||||||
<h2 class="p-name">
|
<h2 class="p-name">
|
||||||
<a class="u-url" href="{{ .Data.Path }}">
|
<a class="u-url" href="{{ .Data.Path }}">
|
||||||
{{ p .Data "title" }}
|
{{ p .Data "title" }}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ include "summarymeta" .Blog .Data }}
|
{{ include "postmeta" . }}
|
||||||
<p class="p-summary">{{ summary .Data }}</p>
|
<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>
|
<p>{{ if (hasp .Data "images") }}🖼️ {{ end }}<a class="u-url" href="{{ .Data.Path }}">{{ string .Blog.Lang "view" }}</a></p>
|
||||||
</article>
|
</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 (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -35,3 +36,14 @@ func generateRandomString(chars int) string {
|
||||||
}
|
}
|
||||||
return string(b)
|
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