mirror of https://github.com/jlelse/GoBlog
Priorities for posts (pinned posts), comments on blogroll, stats and map & some refactorings and fixes
This commit is contained in:
parent
fabd30cc20
commit
4205fb173f
|
@ -16,6 +16,8 @@ import (
|
||||||
"go.goblog.app/app/pkgs/contenttype"
|
"go.goblog.app/app/pkgs/contenttype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultBlogrollPath = "/blogroll"
|
||||||
|
|
||||||
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
t := servertiming.FromContext(r.Context()).NewMetric("bg").Start()
|
t := servertiming.FromContext(r.Context()).NewMetric("bg").Start()
|
||||||
|
@ -32,13 +34,15 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
|
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
|
||||||
}
|
}
|
||||||
c := a.cfg.Blogs[blog].Blogroll
|
c := a.cfg.Blogs[blog].Blogroll
|
||||||
|
can := a.getRelativePath(blog, defaultIfEmpty(c.Path, defaultBlogrollPath))
|
||||||
a.render(w, r, templateBlogroll, &renderData{
|
a.render(w, r, templateBlogroll, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
|
Canonical: a.getFullAddress(can),
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Title": c.Title,
|
"Title": c.Title,
|
||||||
"Description": c.Description,
|
"Description": c.Description,
|
||||||
"Outlines": outlines,
|
"Outlines": outlines,
|
||||||
"Download": c.Path + ".opml",
|
"Download": can + ".opml",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
servertiming "github.com/mitchellh/go-server-timing"
|
servertiming "github.com/mitchellh/go-server-timing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultBlogStatsPath = "/statistics"
|
||||||
|
|
||||||
func (a *goBlog) initBlogStats() {
|
func (a *goBlog) initBlogStats() {
|
||||||
f := func(p *post) {
|
f := func(p *post) {
|
||||||
a.db.resetBlogStats(p.Blog)
|
a.db.resetBlogStats(p.Blog)
|
||||||
|
@ -21,7 +23,7 @@ func (a *goBlog) initBlogStats() {
|
||||||
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
bc := a.cfg.Blogs[blog]
|
bc := a.cfg.Blogs[blog]
|
||||||
canonical := bc.getRelativePath(bc.BlogStats.Path)
|
canonical := bc.getRelativePath(defaultIfEmpty(bc.BlogStats.Path, defaultBlogStatsPath))
|
||||||
a.render(w, r, templateBlogStats, &renderData{
|
a.render(w, r, templateBlogStats, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Canonical: a.getFullAddress(canonical),
|
Canonical: a.getFullAddress(canonical),
|
||||||
|
|
84
config.go
84
config.go
|
@ -52,50 +52,50 @@ 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"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
Pagination int `mapstructure:"pagination"`
|
Pagination int `mapstructure:"pagination"`
|
||||||
DefaultSection string `mapstructure:"defaultsection"`
|
DefaultSection string `mapstructure:"defaultsection"`
|
||||||
Sections map[string]*section `mapstructure:"sections"`
|
Sections map[string]*configSection `mapstructure:"sections"`
|
||||||
Taxonomies []*taxonomy `mapstructure:"taxonomies"`
|
Taxonomies []*configTaxonomy `mapstructure:"taxonomies"`
|
||||||
Menus map[string]*menu `mapstructure:"menus"`
|
Menus map[string]*configMenu `mapstructure:"menus"`
|
||||||
Photos *photos `mapstructure:"photos"`
|
Photos *configPhotos `mapstructure:"photos"`
|
||||||
Search *search `mapstructure:"search"`
|
Search *configSearch `mapstructure:"search"`
|
||||||
BlogStats *blogStats `mapstructure:"blogStats"`
|
BlogStats *configBlogStats `mapstructure:"blogStats"`
|
||||||
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
||||||
CustomPages []*customPage `mapstructure:"custompages"`
|
CustomPages []*configCustomPage `mapstructure:"custompages"`
|
||||||
Telegram *configTelegram `mapstructure:"telegram"`
|
Telegram *configTelegram `mapstructure:"telegram"`
|
||||||
PostAsHome bool `mapstructure:"postAsHome"`
|
PostAsHome bool `mapstructure:"postAsHome"`
|
||||||
RandomPost *randomPost `mapstructure:"randomPost"`
|
RandomPost *configRandomPost `mapstructure:"randomPost"`
|
||||||
Comments *comments `mapstructure:"comments"`
|
Comments *configComments `mapstructure:"comments"`
|
||||||
Map *configMap `mapstructure:"map"`
|
Map *configGeoMap `mapstructure:"map"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type section struct {
|
type configSection struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
PathTemplate string `mapstructure:"pathtemplate"`
|
PathTemplate string `mapstructure:"pathtemplate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type taxonomy struct {
|
type configTaxonomy struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type menu struct {
|
type configMenu struct {
|
||||||
Items []*menuItem `mapstructure:"items"`
|
Items []*configMenuItem `mapstructure:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type menuItem struct {
|
type configMenuItem struct {
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
Link string `mapstructure:"link"`
|
Link string `mapstructure:"link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type photos struct {
|
type configPhotos struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Parameter string `mapstructure:"parameter"`
|
Parameter string `mapstructure:"parameter"`
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
|
@ -103,7 +103,7 @@ type photos struct {
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type search struct {
|
type configSearch struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
|
@ -111,7 +111,7 @@ type search struct {
|
||||||
Placeholder string `mapstructure:"placeholder"`
|
Placeholder string `mapstructure:"placeholder"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type blogStats struct {
|
type configBlogStats struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
Title string `mapstructure:"title"`
|
Title string `mapstructure:"title"`
|
||||||
|
@ -129,7 +129,7 @@ type configBlogroll struct {
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type customPage struct {
|
type configCustomPage struct {
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
Template string `mapstructure:"template"`
|
Template string `mapstructure:"template"`
|
||||||
Cache bool `mapstructure:"cache"`
|
Cache bool `mapstructure:"cache"`
|
||||||
|
@ -137,33 +137,33 @@ type customPage struct {
|
||||||
Data *interface{} `mapstructure:"data"`
|
Data *interface{} `mapstructure:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type randomPost struct {
|
type configRandomPost struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type comments struct {
|
type configComments struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type configMap struct {
|
type configGeoMap struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type configUser struct {
|
type configUser struct {
|
||||||
Nick string `mapstructure:"nick"`
|
Nick string `mapstructure:"nick"`
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
TOTP string `mapstructure:"totp"`
|
TOTP string `mapstructure:"totp"`
|
||||||
AppPasswords []*appPassword `mapstructure:"appPasswords"`
|
AppPasswords []*configAppPassword `mapstructure:"appPasswords"`
|
||||||
Picture string `mapstructure:"picture"`
|
Picture string `mapstructure:"picture"`
|
||||||
Email string `mapstructure:"email"`
|
Email string `mapstructure:"email"`
|
||||||
Link string `mapstructure:"link"`
|
Link string `mapstructure:"link"`
|
||||||
Identities []string `mapstructure:"identities"`
|
Identities []string `mapstructure:"identities"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type appPassword struct {
|
type configAppPassword struct {
|
||||||
Username string `mapstructure:"username"`
|
Username string `mapstructure:"username"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,7 @@ func (a *goBlog) initConfig() error {
|
||||||
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
||||||
// Disable comments for all blogs
|
// Disable comments for all blogs
|
||||||
for _, b := range a.cfg.Blogs {
|
for _, b := range a.cfg.Blogs {
|
||||||
b.Comments = &comments{Enabled: false}
|
b.Comments = &configComments{Enabled: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check config for each blog
|
// Check config for each blog
|
||||||
|
|
|
@ -5,7 +5,7 @@ import "net/http"
|
||||||
const customPageContextKey = "custompage"
|
const customPageContextKey = "custompage"
|
||||||
|
|
||||||
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||||
page := r.Context().Value(customPageContextKey).(*customPage)
|
page := r.Context().Value(customPageContextKey).(*configCustomPage)
|
||||||
if a.cfg.Cache != nil && a.cfg.Cache.Enable && page.Cache {
|
if a.cfg.Cache != nil && a.cfg.Cache.Enable && page.Cache {
|
||||||
if page.CacheExpiration != 0 {
|
if page.CacheExpiration != 0 {
|
||||||
setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
|
setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
|
||||||
|
|
|
@ -5,8 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultGeoMapPath = "/map"
|
||||||
|
|
||||||
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
|
bc := a.cfg.Blogs[blog]
|
||||||
|
|
||||||
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
||||||
blog: blog,
|
blog: blog,
|
||||||
|
@ -57,6 +60,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
a.render(w, r, templateGeoMap, &renderData{
|
a.render(w, r, templateGeoMap, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
|
Canonical: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bc.Map.Path, defaultGeoMapPath))),
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"locations": string(jb),
|
"locations": string(jb),
|
||||||
},
|
},
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -7,8 +7,7 @@ require (
|
||||||
git.jlel.se/jlelse/go-shutdowner v0.0.0-20210707065515-773db8099c30
|
git.jlel.se/jlelse/go-shutdowner v0.0.0-20210707065515-773db8099c30
|
||||||
git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4
|
git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4
|
||||||
git.jlel.se/jlelse/template-strings v0.0.0-20210617205924-cfa3bd35ae40
|
git.jlel.se/jlelse/template-strings v0.0.0-20210617205924-cfa3bd35ae40
|
||||||
github.com/PuerkitoBio/goquery v1.7.0
|
github.com/PuerkitoBio/goquery v1.7.1
|
||||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||||
|
@ -54,7 +53,7 @@ require (
|
||||||
github.com/yuin/goldmark v1.4.0
|
github.com/yuin/goldmark v1.4.0
|
||||||
// master
|
// master
|
||||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -49,10 +49,9 @@ git.jlel.se/jlelse/template-strings v0.0.0-20210617205924-cfa3bd35ae40/go.mod h1
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||||
github.com/PuerkitoBio/goquery v1.7.0 h1:O5SP3b9JWqMSVMG69zMfj577zwkSNpxrFf7ybS74eiw=
|
github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
|
||||||
github.com/PuerkitoBio/goquery v1.7.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
|
||||||
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
|
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
|
||||||
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
@ -415,8 +414,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
44
http.go
44
http.go
|
@ -254,18 +254,21 @@ func (a *goBlog) buildStaticHandlersRouters() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
|
if pc := blogConfig.Photos; pc != nil && pc.Enabled {
|
||||||
a.photosMiddlewares[blog] = middleware.WithValue(indexConfigKey, &indexConfig{
|
a.photosMiddlewares[blog] = middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
path: blogConfig.getRelativePath(blogConfig.Photos.Path),
|
path: blogConfig.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath)),
|
||||||
parameter: blogConfig.Photos.Parameter,
|
parameter: pc.Parameter,
|
||||||
title: blogConfig.Photos.Title,
|
title: pc.Title,
|
||||||
description: blogConfig.Photos.Description,
|
description: pc.Description,
|
||||||
summaryTemplate: templatePhotosSummary,
|
summaryTemplate: templatePhotosSummary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
if bsc := blogConfig.Search; bsc != nil && bsc.Enabled {
|
||||||
a.searchMiddlewares[blog] = middleware.WithValue(pathContextKey, blogConfig.getRelativePath(blogConfig.Search.Path))
|
a.searchMiddlewares[blog] = middleware.WithValue(
|
||||||
|
pathContextKey,
|
||||||
|
blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cp := range blogConfig.CustomPages {
|
for _, cp := range blogConfig.CustomPages {
|
||||||
|
@ -437,11 +440,11 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Photos
|
// Photos
|
||||||
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
|
if pc := blogConfig.Photos; pc != nil && pc.Enabled {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(a.privateModeHandler...)
|
r.Use(a.privateModeHandler...)
|
||||||
r.Use(a.cache.cacheMiddleware, sbm, a.photosMiddlewares[blog])
|
r.Use(a.cache.cacheMiddleware, sbm, a.photosMiddlewares[blog])
|
||||||
photoPath := blogConfig.getRelativePath(blogConfig.Photos.Path)
|
photoPath := blogConfig.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))
|
||||||
r.Get(photoPath, a.serveIndex)
|
r.Get(photoPath, a.serveIndex)
|
||||||
r.Get(photoPath+feedPath, a.serveIndex)
|
r.Get(photoPath+feedPath, a.serveIndex)
|
||||||
r.Get(photoPath+paginationPath, a.serveIndex)
|
r.Get(photoPath+paginationPath, a.serveIndex)
|
||||||
|
@ -449,14 +452,13 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
if bsc := blogConfig.Search; bsc != nil && bsc.Enabled {
|
||||||
searchPath := blogConfig.getRelativePath(blogConfig.Search.Path)
|
r.With(sbm, a.searchMiddlewares[blog]).Mount(blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath)), a.searchRouter)
|
||||||
r.With(sbm, a.searchMiddlewares[blog]).Mount(searchPath, a.searchRouter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
|
if bsc := blogConfig.BlogStats; bsc != nil && bsc.Enabled {
|
||||||
statsPath := blogConfig.getRelativePath(blogConfig.BlogStats.Path)
|
statsPath := blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(a.privateModeHandler...)
|
r.Use(a.privateModeHandler...)
|
||||||
r.Use(a.cache.cacheMiddleware, sbm)
|
r.Use(a.cache.cacheMiddleware, sbm)
|
||||||
|
@ -513,11 +515,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
||||||
|
|
||||||
// Random post
|
// Random post
|
||||||
if rp := blogConfig.RandomPost; rp != nil && rp.Enabled {
|
if rp := blogConfig.RandomPost; rp != nil && rp.Enabled {
|
||||||
randomPath := rp.Path
|
r.With(a.privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(defaultIfEmpty(rp.Path, "/random")), a.redirectToRandomPost)
|
||||||
if randomPath == "" {
|
|
||||||
randomPath = "/random"
|
|
||||||
}
|
|
||||||
r.With(a.privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(randomPath), a.redirectToRandomPost)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor
|
// Editor
|
||||||
|
@ -530,7 +528,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
||||||
|
|
||||||
// Blogroll
|
// Blogroll
|
||||||
if brConfig := blogConfig.Blogroll; brConfig != nil && brConfig.Enabled {
|
if brConfig := blogConfig.Blogroll; brConfig != nil && brConfig.Enabled {
|
||||||
brPath := blogConfig.getRelativePath(brConfig.Path)
|
brPath := blogConfig.getRelativePath(defaultIfEmpty(brConfig.Path, defaultBlogrollPath))
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(a.privateModeHandler...)
|
r.Use(a.privateModeHandler...)
|
||||||
r.Use(a.cache.cacheMiddleware, sbm)
|
r.Use(a.cache.cacheMiddleware, sbm)
|
||||||
|
@ -541,11 +539,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
||||||
|
|
||||||
// Geo map
|
// Geo map
|
||||||
if mc := blogConfig.Map; mc != nil && mc.Enabled {
|
if mc := blogConfig.Map; mc != nil && mc.Enabled {
|
||||||
mapPath := mc.Path
|
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm).Get(blogConfig.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath)), a.serveGeoMap)
|
||||||
if mc.Path == "" {
|
|
||||||
mapPath = "/map"
|
|
||||||
}
|
|
||||||
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm).Get(blogConfig.getRelativePath(mapPath), a.serveGeoMap)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
|
||||||
|
|
||||||
marktag "git.jlel.se/jlelse/goldmark-mark"
|
marktag "git.jlel.se/jlelse/goldmark-mark"
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
emoji "github.com/yuin/goldmark-emoji"
|
emoji "github.com/yuin/goldmark-emoji"
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
|
@ -73,11 +71,7 @@ func (a *goBlog) renderText(s string) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
d, err := goquery.NewDocumentFromReader(bytes.NewReader(h))
|
return htmlText(h)
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(d.Text())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions etc...
|
// Extensions etc...
|
||||||
|
|
|
@ -360,6 +360,10 @@ func (a *goBlog) computeExtraPostParameters(p *post) error {
|
||||||
p.Status = postStatus(status[0])
|
p.Status = postStatus(status[0])
|
||||||
delete(p.Parameters, "status")
|
delete(p.Parameters, "status")
|
||||||
}
|
}
|
||||||
|
if priority := p.Parameters["priority"]; len(priority) == 1 {
|
||||||
|
p.Priority = cast.ToInt(priority[0])
|
||||||
|
delete(p.Parameters, "priority")
|
||||||
|
}
|
||||||
if p.Path == "" && p.Section == "" {
|
if p.Path == "" && p.Section == "" {
|
||||||
// Has no path or section -> default section
|
// Has no path or section -> default section
|
||||||
p.Section = a.cfg.Blogs[p.Blog].DefaultSection
|
p.Section = a.cfg.Blogs[p.Blog].DefaultSection
|
||||||
|
|
|
@ -11,7 +11,7 @@ func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
b := a.cfg.Blogs[blog]
|
b := a.cfg.Blogs[blog]
|
||||||
title := b.Title
|
title := b.Title
|
||||||
sURL := a.getFullAddress(b.getRelativePath(b.Search.Path))
|
sURL := a.getFullAddress(b.getRelativePath(defaultIfEmpty(b.Search.Path, defaultSearchPath)))
|
||||||
xml := fmt.Sprintf("<?xml version=\"1.0\"?><OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">"+
|
xml := fmt.Sprintf("<?xml version=\"1.0\"?><OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"http://www.mozilla.org/2006/browser/search/\">"+
|
||||||
"<ShortName>%s</ShortName><Description>%s</Description>"+
|
"<ShortName>%s</ShortName><Description>%s</Description>"+
|
||||||
"<Url type=\"text/html\" method=\"post\" template=\"%s\"><Param name=\"q\" value=\"{searchTerms}\" /></Url>"+
|
"<Url type=\"text/html\" method=\"post\" template=\"%s\"><Param name=\"q\" value=\"{searchTerms}\" /></Url>"+
|
||||||
|
@ -24,7 +24,7 @@ func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func openSearchUrl(b *configBlog) string {
|
func openSearchUrl(b *configBlog) string {
|
||||||
if b.Search != nil && b.Search.Enabled {
|
if b.Search != nil && b.Search.Enabled {
|
||||||
return b.getRelativePath(b.Search.Path + "/opensearch.xml")
|
return b.getRelativePath(defaultIfEmpty(b.Search.Path, defaultSearchPath) + "/opensearch.xml")
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
31
posts.go
31
posts.go
|
@ -19,16 +19,17 @@ import (
|
||||||
var errPostNotFound = errors.New("post not found")
|
var errPostNotFound = errors.New("post not found")
|
||||||
|
|
||||||
type post struct {
|
type post struct {
|
||||||
Path string `json:"path"`
|
Path string
|
||||||
Content string `json:"content"`
|
Content string
|
||||||
Published string `json:"published"`
|
Published string
|
||||||
Updated string `json:"updated"`
|
Updated string
|
||||||
Parameters map[string][]string `json:"parameters"`
|
Parameters map[string][]string
|
||||||
Blog string `json:"blog"`
|
Blog string
|
||||||
Section string `json:"section"`
|
Section string
|
||||||
Status postStatus `json:"status"`
|
Status postStatus
|
||||||
|
Priority int
|
||||||
// Not persisted
|
// Not persisted
|
||||||
Slug string `json:"slug"`
|
Slug string
|
||||||
rendered template.HTML
|
rendered template.HTML
|
||||||
absoluteRendered template.HTML
|
absoluteRendered template.HTML
|
||||||
}
|
}
|
||||||
|
@ -167,8 +168,8 @@ func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
|
||||||
type indexConfig struct {
|
type indexConfig struct {
|
||||||
blog string
|
blog string
|
||||||
path string
|
path string
|
||||||
section *section
|
section *configSection
|
||||||
tax *taxonomy
|
tax *configTaxonomy
|
||||||
taxValue string
|
taxValue string
|
||||||
parameter string
|
parameter string
|
||||||
year, month, day int
|
year, month, day int
|
||||||
|
@ -177,6 +178,8 @@ type indexConfig struct {
|
||||||
summaryTemplate string
|
summaryTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultPhotosPath = "/photos"
|
||||||
|
|
||||||
const indexConfigKey contextKey = "indexConfig"
|
const indexConfigKey contextKey = "indexConfig"
|
||||||
|
|
||||||
func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -187,7 +190,8 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
search := chi.URLParam(r, "search")
|
search := chi.URLParam(r, "search")
|
||||||
if search != "" {
|
if search != "" {
|
||||||
search = searchDecode(search)
|
// Decode and sanitize search
|
||||||
|
search = htmlText([]byte(bluemonday.StrictPolicy().Sanitize(searchDecode(search))))
|
||||||
}
|
}
|
||||||
pageNoString := chi.URLParam(r, "page")
|
pageNoString := chi.URLParam(r, "page")
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
pageNo, _ := strconv.Atoi(pageNoString)
|
||||||
|
@ -210,6 +214,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
publishedMonth: ic.month,
|
publishedMonth: ic.month,
|
||||||
publishedDay: ic.day,
|
publishedDay: ic.day,
|
||||||
status: statusPublished,
|
status: statusPublished,
|
||||||
|
priorityOrder: true,
|
||||||
}, db: a.db}, a.cfg.Blogs[blog].Pagination)
|
}, db: a.db}, a.cfg.Blogs[blog].Pagination)
|
||||||
p.SetPage(pageNo)
|
p.SetPage(pageNo)
|
||||||
var posts []*post
|
var posts []*post
|
||||||
|
@ -231,8 +236,6 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
} else if search != "" {
|
} else if search != "" {
|
||||||
title = fmt.Sprintf("%s: %s", a.cfg.Blogs[blog].Search.Title, search)
|
title = fmt.Sprintf("%s: %s", a.cfg.Blogs[blog].Search.Title, search)
|
||||||
}
|
}
|
||||||
// Clean title
|
|
||||||
title = bluemonday.StrictPolicy().Sanitize(title)
|
|
||||||
// Check if feed
|
// Check if feed
|
||||||
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
|
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
|
||||||
a.generateFeed(blog, ft, w, r, posts, title, description)
|
a.generateFeed(blog, ft, w, r, posts, title, description)
|
||||||
|
|
15
postsDb.go
15
postsDb.go
|
@ -162,8 +162,8 @@ func (db *database) savePost(p *post, o *postCreationOptions) error {
|
||||||
sqlArgs = append(sqlArgs, o.oldPath, o.oldPath)
|
sqlArgs = append(sqlArgs, o.oldPath, o.oldPath)
|
||||||
}
|
}
|
||||||
// Insert new post
|
// Insert new post
|
||||||
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status) values (?, ?, ?, ?, ?, ?, ?);")
|
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status, priority) values (?, ?, ?, ?, ?, ?, ?, ?);")
|
||||||
sqlArgs = append(sqlArgs, p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section, p.Status)
|
sqlArgs = append(sqlArgs, p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section, p.Status, p.Priority)
|
||||||
// Insert post parameters
|
// Insert post parameters
|
||||||
for param, value := range p.Parameters {
|
for param, value := range p.Parameters {
|
||||||
for _, value := range value {
|
for _, value := range value {
|
||||||
|
@ -220,12 +220,13 @@ type postsRequestConfig struct {
|
||||||
offset int
|
offset int
|
||||||
sections []string
|
sections []string
|
||||||
status postStatus
|
status postStatus
|
||||||
taxonomy *taxonomy
|
taxonomy *configTaxonomy
|
||||||
taxonomyValue string
|
taxonomyValue string
|
||||||
parameter string
|
parameter string
|
||||||
parameterValue string
|
parameterValue string
|
||||||
publishedYear, publishedMonth, publishedDay int
|
publishedYear, publishedMonth, publishedDay int
|
||||||
randomOrder bool
|
randomOrder bool
|
||||||
|
priorityOrder bool
|
||||||
withoutParameters bool
|
withoutParameters bool
|
||||||
withOnlyParameters []string
|
withOnlyParameters []string
|
||||||
}
|
}
|
||||||
|
@ -294,6 +295,8 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
|
||||||
sorting := " order by published desc"
|
sorting := " order by published desc"
|
||||||
if c.randomOrder {
|
if c.randomOrder {
|
||||||
sorting = " order by random()"
|
sorting = " order by random()"
|
||||||
|
} else if c.priorityOrder {
|
||||||
|
sorting = " order by priority desc, published desc"
|
||||||
}
|
}
|
||||||
table += sorting
|
table += sorting
|
||||||
if c.limit != 0 || c.offset != 0 {
|
if c.limit != 0 || c.offset != 0 {
|
||||||
|
@ -339,15 +342,16 @@ func (d *database) getPostParameters(path string, parameters ...string) (params
|
||||||
|
|
||||||
func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err error) {
|
func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err error) {
|
||||||
// Query posts
|
// Query posts
|
||||||
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status")
|
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority")
|
||||||
rows, err := d.query(query, queryParams...)
|
rows, err := d.query(query, queryParams...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Prepare row scanning
|
// Prepare row scanning
|
||||||
var path, content, published, updated, blog, section, status string
|
var path, content, published, updated, blog, section, status string
|
||||||
|
var priority int
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if err = rows.Scan(&path, &content, &published, &updated, &blog, §ion, &status); err != nil {
|
if err = rows.Scan(&path, &content, &published, &updated, &blog, §ion, &status, &priority); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create new post, fill and add to list
|
// Create new post, fill and add to list
|
||||||
|
@ -359,6 +363,7 @@ func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err erro
|
||||||
Blog: blog,
|
Blog: blog,
|
||||||
Section: section,
|
Section: section,
|
||||||
Status: postStatus(status),
|
Status: postStatus(status),
|
||||||
|
Priority: priority,
|
||||||
}
|
}
|
||||||
if !config.withoutParameters {
|
if !config.withoutParameters {
|
||||||
if p.Parameters, err = d.getPostParameters(path, config.withOnlyParameters...); err != nil {
|
if p.Parameters, err = d.getPostParameters(path, config.withOnlyParameters...); err != nil {
|
||||||
|
|
|
@ -20,8 +20,9 @@ func Test_postsDb(t *testing.T) {
|
||||||
},
|
},
|
||||||
Blogs: map[string]*configBlog{
|
Blogs: map[string]*configBlog{
|
||||||
"en": {
|
"en": {
|
||||||
Sections: map[string]*section{
|
Sections: map[string]*configSection{
|
||||||
"test": {},
|
"test": {},
|
||||||
|
"micro": {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -241,6 +242,57 @@ func Test_ftsWithoutTitle(t *testing.T) {
|
||||||
assert.Len(t, ps, 1)
|
assert.Len(t, ps, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_postsPriority(t *testing.T) {
|
||||||
|
// Added because there was a bug where there were no search results without title
|
||||||
|
|
||||||
|
app := &goBlog{
|
||||||
|
cfg: &config{
|
||||||
|
Db: &configDb{
|
||||||
|
File: filepath.Join(t.TempDir(), "test.db"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.initDatabase(false)
|
||||||
|
|
||||||
|
err := app.db.savePost(&post{
|
||||||
|
Path: "/test/abc",
|
||||||
|
Content: "ABC",
|
||||||
|
Published: toLocalSafe(time.Now().String()),
|
||||||
|
Blog: "en",
|
||||||
|
Section: "test",
|
||||||
|
Status: statusPublished,
|
||||||
|
}, &postCreationOptions{new: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = app.db.savePost(&post{
|
||||||
|
Path: "/test/def",
|
||||||
|
Content: "DEF",
|
||||||
|
Published: toLocalSafe(time.Now().String()),
|
||||||
|
Blog: "en",
|
||||||
|
Section: "test",
|
||||||
|
Status: statusPublished,
|
||||||
|
Priority: 1,
|
||||||
|
}, &postCreationOptions{new: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ps, err := app.db.getPosts(&postsRequestConfig{
|
||||||
|
priorityOrder: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if assert.Len(t, ps, 2) {
|
||||||
|
post1 := ps[0]
|
||||||
|
|
||||||
|
assert.Equal(t, "/test/def", post1.Path)
|
||||||
|
assert.Equal(t, 1, post1.Priority)
|
||||||
|
|
||||||
|
post2 := ps[1]
|
||||||
|
|
||||||
|
assert.Equal(t, "/test/abc", post2.Path)
|
||||||
|
assert.Equal(t, 0, post2.Priority)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_usesOfMediaFile(t *testing.T) {
|
func Test_usesOfMediaFile(t *testing.T) {
|
||||||
app := &goBlog{
|
app := &goBlog{
|
||||||
cfg: &config{
|
cfg: &config{
|
||||||
|
|
|
@ -120,14 +120,22 @@ func (p *post) isPublishedSectionPost() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
||||||
params := p.Parameters
|
params := map[string]interface{}{}
|
||||||
params["path"] = []string{p.Path}
|
for k, v := range p.Parameters {
|
||||||
params["section"] = []string{p.Section}
|
if l := len(v); l == 1 {
|
||||||
params["blog"] = []string{p.Blog}
|
params[k] = v[0]
|
||||||
params["published"] = []string{p.Published}
|
} else if l > 1 {
|
||||||
params["updated"] = []string{p.Updated}
|
params[k] = v
|
||||||
params["status"] = []string{string(p.Status)}
|
}
|
||||||
pb, _ := yaml.Marshal(p.Parameters)
|
}
|
||||||
|
params["path"] = p.Path
|
||||||
|
params["section"] = p.Section
|
||||||
|
params["blog"] = p.Blog
|
||||||
|
params["published"] = p.Published
|
||||||
|
params["updated"] = p.Updated
|
||||||
|
params["status"] = string(p.Status)
|
||||||
|
params["priority"] = p.Priority
|
||||||
|
pb, _ := yaml.Marshal(params)
|
||||||
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
|
content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content)
|
||||||
return µformatItem{
|
return µformatItem{
|
||||||
Type: []string{"h-entry"},
|
Type: []string{"h-entry"},
|
||||||
|
|
|
@ -7,8 +7,11 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultSearchPath = "/search"
|
||||||
const searchPlaceholder = "{search}"
|
const searchPlaceholder = "{search}"
|
||||||
|
|
||||||
func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -20,6 +23,9 @@ func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if q := r.Form.Get("q"); q != "" {
|
if q := r.Form.Get("q"); q != "" {
|
||||||
|
// Clean query
|
||||||
|
q = htmlText([]byte(bluemonday.StrictPolicy().Sanitize(q)))
|
||||||
|
// Redirect to results
|
||||||
http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound)
|
http.Redirect(w, r, path.Join(servePath, searchEncode(q)), http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
22
sitemap.go
22
sitemap.go
|
@ -101,27 +101,33 @@ func (a *goBlog) serveSitemap(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Photos
|
// Photos
|
||||||
if bc.Photos != nil && bc.Photos.Enabled {
|
if pc := bc.Photos; pc != nil && pc.Enabled {
|
||||||
sm.Add(&sitemap.URL{
|
sm.Add(&sitemap.URL{
|
||||||
Loc: a.getFullAddress(bc.getRelativePath(bc.Photos.Path)),
|
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Search
|
// Search
|
||||||
if bc.Search != nil && bc.Search.Enabled {
|
if bsc := bc.Search; bsc != nil && bsc.Enabled {
|
||||||
sm.Add(&sitemap.URL{
|
sm.Add(&sitemap.URL{
|
||||||
Loc: a.getFullAddress(bc.getRelativePath(bc.Search.Path)),
|
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Stats
|
// Stats
|
||||||
if bc.BlogStats != nil && bc.BlogStats.Enabled {
|
if bsc := bc.BlogStats; bsc != nil && bsc.Enabled {
|
||||||
sm.Add(&sitemap.URL{
|
sm.Add(&sitemap.URL{
|
||||||
Loc: a.getFullAddress(bc.getRelativePath(bc.BlogStats.Path)),
|
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Blogroll
|
// Blogroll
|
||||||
if bc.Blogroll != nil && bc.Blogroll.Enabled {
|
if brc := bc.Blogroll; brc != nil && brc.Enabled {
|
||||||
sm.Add(&sitemap.URL{
|
sm.Add(&sitemap.URL{
|
||||||
Loc: a.getFullAddress(bc.getRelativePath(bc.Blogroll.Path)),
|
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(brc.Path, defaultBlogrollPath))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Geo map
|
||||||
|
if mc := bc.Map; mc != nil && mc.Enabled {
|
||||||
|
sm.Add(&sitemap.URL{
|
||||||
|
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Custom pages
|
// Custom pages
|
||||||
|
|
|
@ -6,7 +6,7 @@ const taxonomyContextKey = "taxonomy"
|
||||||
|
|
||||||
func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
tax := r.Context().Value(taxonomyContextKey).(*taxonomy)
|
tax := r.Context().Value(taxonomyContextKey).(*configTaxonomy)
|
||||||
allValues, err := a.db.allTaxonomyValues(blog, tax.Name)
|
allValues, err := a.db.allTaxonomyValues(blog, tax.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
@ -21,6 +21,9 @@
|
||||||
</ul>
|
</ul>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
|
{{ if .CommentsEnabled }}
|
||||||
|
{{ include "interactions" . }}
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "blogroll" }}
|
{{ define "blogroll" }}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
|
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
|
||||||
<script defer src="{{ asset "js/blogstats.js" }}"></script>
|
<script defer src="{{ asset "js/blogstats.js" }}"></script>
|
||||||
</main>
|
</main>
|
||||||
|
{{ if .CommentsEnabled }}
|
||||||
|
{{ include "interactions" . }}
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "blogstats" }}
|
{{ define "blogstats" }}
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
|
{{ if .CommentsEnabled }}
|
||||||
|
{{ include "interactions" . }}
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "geomap" }}
|
{{ define "geomap" }}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
{{ define "photosummary" }}
|
{{ define "photosummary" }}
|
||||||
<article class="h-entry border-bottom">
|
<article class="h-entry border-bottom">
|
||||||
{{ with p .Data "title" }}<h2>{{ . }}</h2>{{ end }}
|
{{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }}
|
||||||
|
{{ if p .Data "title" }}
|
||||||
|
<h2 class="p-name">
|
||||||
|
<a class="u-url" href="{{ .Data.Path }}">
|
||||||
|
{{ p .Data "title" }}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
{{ end }}
|
||||||
{{ include "summarymeta" . }}
|
{{ include "summarymeta" . }}
|
||||||
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
||||||
{{ md ( printf "![](%s)" $photo ) }}
|
{{ md ( printf "![](%s)" $photo ) }}
|
||||||
|
|
|
@ -26,6 +26,7 @@ nofiles: "Keine Dateien"
|
||||||
nolocations: "Keine Posts mit Standorten"
|
nolocations: "Keine Posts mit Standorten"
|
||||||
noposts: "Hier sind keine Posts."
|
noposts: "Hier sind keine Posts."
|
||||||
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
|
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."
|
||||||
|
pinned: "Angepinnt"
|
||||||
posts: "Posts"
|
posts: "Posts"
|
||||||
prev: "Zurück"
|
prev: "Zurück"
|
||||||
publishedon: "Veröffentlicht am"
|
publishedon: "Veröffentlicht am"
|
||||||
|
|
|
@ -38,6 +38,7 @@ noposts: "There are no posts here."
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
||||||
password: "Password"
|
password: "Password"
|
||||||
|
pinned: "Pinned"
|
||||||
posts: "Posts"
|
posts: "Posts"
|
||||||
prev: "Previous"
|
prev: "Previous"
|
||||||
publishedon: "Published on"
|
publishedon: "Published on"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{ define "summary" }}
|
{{ define "summary" }}
|
||||||
<article class="h-entry border-bottom">
|
<article class="h-entry border-bottom">
|
||||||
|
{{ if gt .Data.Priority 0 }}<p>📌 {{ string .Blog.Lang "pinned" }}</p>{{ end }}
|
||||||
{{ 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 }}">
|
||||||
|
|
16
utils.go
16
utils.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -211,3 +212,18 @@ func getSHA256(file io.ReadSeeker) (filename string, err error) {
|
||||||
func mBytesString(size int64) string {
|
func mBytesString(size int64) string {
|
||||||
return fmt.Sprintf("%.2f MB", datasize.ByteSize(size).MBytes())
|
return fmt.Sprintf("%.2f MB", datasize.ByteSize(size).MBytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func htmlText(b []byte) string {
|
||||||
|
d, err := goquery.NewDocumentFromReader(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(d.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIfEmpty(s, d string) string {
|
||||||
|
if s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue