Improve templates and stuff

This commit is contained in:
Jan-Lukas Else 2020-11-01 18:37:21 +01:00
parent d13b0a5394
commit f300c3d498
29 changed files with 318 additions and 193 deletions

View File

@ -150,5 +150,4 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter) {
} }
} }
_ = json.NewEncoder(w).Encode(asBlog) _ = json.NewEncoder(w).Encode(asBlog)
} }

View File

@ -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 {

View File

@ -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,
}) })
} }
} }

View File

@ -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"

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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
}

View File

@ -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,

View File

@ -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")
} }

View File

@ -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{}
} }

View File

@ -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();

10
templates/author.gohtml Normal file
View File

@ -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 }}

View File

@ -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 }}

12
templates/footer.gohtml Normal file
View File

@ -0,0 +1,12 @@
{{ define "footer" }}
<footer>
{{ with menu .Blog "footer" }}
{{ $first := true }}
{{ range $i, $item := .Items }}
{{ if ne $first true }} &bull; {{ end }}<a
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
{{ end }}
{{ end }}
<p>&copy; {{ dateformat now "2006" }} {{ .Blog.Title }}</p>
</footer>
{{ end }}

View File

@ -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 }} &bull; {{ end }}<a
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
{{ end }}
{{ end }}
</nav>
</header> </header>
{{ end }} {{ end }}

View File

@ -1,11 +0,0 @@
{{ define "menu" }}
<nav>
{{ with menu .Blog "main" }}
{{ $first := true }}
{{ range $i, $item := .Items }}
{{ if ne $first true }} &bull; {{ end }}<a
href="{{ $item.Link }}">{{ $item.Title }}</a>{{ $first = false }}
{{ end }}
{{ end }}
</nav>
{{ end }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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>&nbsp;
<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 }}

View File

@ -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 }}

17
templates/postmeta.gohtml Normal file
View File

@ -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 }}

14
templates/posttax.gohtml Normal file
View File

@ -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 }}

11
templates/strings/de.yaml Normal file
View File

@ -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!"

View File

@ -1,5 +0,0 @@
publishedon: "Veröffentlicht am"
updatedon: "Aktualisiert am"
next: "Weiter"
prev: "Zurück"
view: "Anschauen"

View File

@ -5,4 +5,10 @@ prev: "Previous"
view: "View" 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!"

View File

@ -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>

View File

@ -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 }}

View File

@ -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
}