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"
|
||||
)
|
||||
|
||||
const defaultBlogrollPath = "/blogroll"
|
||||
|
||||
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
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))
|
||||
}
|
||||
c := a.cfg.Blogs[blog].Blogroll
|
||||
can := a.getRelativePath(blog, defaultIfEmpty(c.Path, defaultBlogrollPath))
|
||||
a.render(w, r, templateBlogroll, &renderData{
|
||||
BlogString: blog,
|
||||
Canonical: a.getFullAddress(can),
|
||||
Data: map[string]interface{}{
|
||||
"Title": c.Title,
|
||||
"Description": c.Description,
|
||||
"Outlines": outlines,
|
||||
"Download": c.Path + ".opml",
|
||||
"Download": can + ".opml",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
servertiming "github.com/mitchellh/go-server-timing"
|
||||
)
|
||||
|
||||
const defaultBlogStatsPath = "/statistics"
|
||||
|
||||
func (a *goBlog) initBlogStats() {
|
||||
f := func(p *post) {
|
||||
a.db.resetBlogStats(p.Blog)
|
||||
|
@ -21,7 +23,7 @@ func (a *goBlog) initBlogStats() {
|
|||
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
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{
|
||||
BlogString: blog,
|
||||
Canonical: a.getFullAddress(canonical),
|
||||
|
|
84
config.go
84
config.go
|
@ -52,50 +52,50 @@ type configCache struct {
|
|||
}
|
||||
|
||||
type configBlog struct {
|
||||
Path string `mapstructure:"path"`
|
||||
Lang string `mapstructure:"lang"`
|
||||
Title string `mapstructure:"title"`
|
||||
Description string `mapstructure:"description"`
|
||||
Pagination int `mapstructure:"pagination"`
|
||||
DefaultSection string `mapstructure:"defaultsection"`
|
||||
Sections map[string]*section `mapstructure:"sections"`
|
||||
Taxonomies []*taxonomy `mapstructure:"taxonomies"`
|
||||
Menus map[string]*menu `mapstructure:"menus"`
|
||||
Photos *photos `mapstructure:"photos"`
|
||||
Search *search `mapstructure:"search"`
|
||||
BlogStats *blogStats `mapstructure:"blogStats"`
|
||||
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
||||
CustomPages []*customPage `mapstructure:"custompages"`
|
||||
Telegram *configTelegram `mapstructure:"telegram"`
|
||||
PostAsHome bool `mapstructure:"postAsHome"`
|
||||
RandomPost *randomPost `mapstructure:"randomPost"`
|
||||
Comments *comments `mapstructure:"comments"`
|
||||
Map *configMap `mapstructure:"map"`
|
||||
Path string `mapstructure:"path"`
|
||||
Lang string `mapstructure:"lang"`
|
||||
Title string `mapstructure:"title"`
|
||||
Description string `mapstructure:"description"`
|
||||
Pagination int `mapstructure:"pagination"`
|
||||
DefaultSection string `mapstructure:"defaultsection"`
|
||||
Sections map[string]*configSection `mapstructure:"sections"`
|
||||
Taxonomies []*configTaxonomy `mapstructure:"taxonomies"`
|
||||
Menus map[string]*configMenu `mapstructure:"menus"`
|
||||
Photos *configPhotos `mapstructure:"photos"`
|
||||
Search *configSearch `mapstructure:"search"`
|
||||
BlogStats *configBlogStats `mapstructure:"blogStats"`
|
||||
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
||||
CustomPages []*configCustomPage `mapstructure:"custompages"`
|
||||
Telegram *configTelegram `mapstructure:"telegram"`
|
||||
PostAsHome bool `mapstructure:"postAsHome"`
|
||||
RandomPost *configRandomPost `mapstructure:"randomPost"`
|
||||
Comments *configComments `mapstructure:"comments"`
|
||||
Map *configGeoMap `mapstructure:"map"`
|
||||
}
|
||||
|
||||
type section struct {
|
||||
type configSection struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Title string `mapstructure:"title"`
|
||||
Description string `mapstructure:"description"`
|
||||
PathTemplate string `mapstructure:"pathtemplate"`
|
||||
}
|
||||
|
||||
type taxonomy struct {
|
||||
type configTaxonomy struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Title string `mapstructure:"title"`
|
||||
Description string `mapstructure:"description"`
|
||||
}
|
||||
|
||||
type menu struct {
|
||||
Items []*menuItem `mapstructure:"items"`
|
||||
type configMenu struct {
|
||||
Items []*configMenuItem `mapstructure:"items"`
|
||||
}
|
||||
|
||||
type menuItem struct {
|
||||
type configMenuItem struct {
|
||||
Title string `mapstructure:"title"`
|
||||
Link string `mapstructure:"link"`
|
||||
}
|
||||
|
||||
type photos struct {
|
||||
type configPhotos struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Parameter string `mapstructure:"parameter"`
|
||||
Path string `mapstructure:"path"`
|
||||
|
@ -103,7 +103,7 @@ type photos struct {
|
|||
Description string `mapstructure:"description"`
|
||||
}
|
||||
|
||||
type search struct {
|
||||
type configSearch struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
Title string `mapstructure:"title"`
|
||||
|
@ -111,7 +111,7 @@ type search struct {
|
|||
Placeholder string `mapstructure:"placeholder"`
|
||||
}
|
||||
|
||||
type blogStats struct {
|
||||
type configBlogStats struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
Title string `mapstructure:"title"`
|
||||
|
@ -129,7 +129,7 @@ type configBlogroll struct {
|
|||
Description string `mapstructure:"description"`
|
||||
}
|
||||
|
||||
type customPage struct {
|
||||
type configCustomPage struct {
|
||||
Path string `mapstructure:"path"`
|
||||
Template string `mapstructure:"template"`
|
||||
Cache bool `mapstructure:"cache"`
|
||||
|
@ -137,33 +137,33 @@ type customPage struct {
|
|||
Data *interface{} `mapstructure:"data"`
|
||||
}
|
||||
|
||||
type randomPost struct {
|
||||
type configRandomPost struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
type comments struct {
|
||||
type configComments struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
type configMap struct {
|
||||
type configGeoMap struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
type configUser struct {
|
||||
Nick string `mapstructure:"nick"`
|
||||
Name string `mapstructure:"name"`
|
||||
Password string `mapstructure:"password"`
|
||||
TOTP string `mapstructure:"totp"`
|
||||
AppPasswords []*appPassword `mapstructure:"appPasswords"`
|
||||
Picture string `mapstructure:"picture"`
|
||||
Email string `mapstructure:"email"`
|
||||
Link string `mapstructure:"link"`
|
||||
Identities []string `mapstructure:"identities"`
|
||||
Nick string `mapstructure:"nick"`
|
||||
Name string `mapstructure:"name"`
|
||||
Password string `mapstructure:"password"`
|
||||
TOTP string `mapstructure:"totp"`
|
||||
AppPasswords []*configAppPassword `mapstructure:"appPasswords"`
|
||||
Picture string `mapstructure:"picture"`
|
||||
Email string `mapstructure:"email"`
|
||||
Link string `mapstructure:"link"`
|
||||
Identities []string `mapstructure:"identities"`
|
||||
}
|
||||
|
||||
type appPassword struct {
|
||||
type configAppPassword struct {
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ func (a *goBlog) initConfig() error {
|
|||
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
||||
// Disable comments for all blogs
|
||||
for _, b := range a.cfg.Blogs {
|
||||
b.Comments = &comments{Enabled: false}
|
||||
b.Comments = &configComments{Enabled: false}
|
||||
}
|
||||
}
|
||||
// Check config for each blog
|
||||
|
|
|
@ -5,7 +5,7 @@ import "net/http"
|
|||
const customPageContextKey = "custompage"
|
||||
|
||||
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 page.CacheExpiration != 0 {
|
||||
setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
|
||||
|
|
|
@ -5,8 +5,11 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
const defaultGeoMapPath = "/map"
|
||||
|
||||
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
bc := a.cfg.Blogs[blog]
|
||||
|
||||
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
||||
blog: blog,
|
||||
|
@ -57,6 +60,7 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
a.render(w, r, templateGeoMap, &renderData{
|
||||
BlogString: blog,
|
||||
Canonical: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bc.Map.Path, defaultGeoMapPath))),
|
||||
Data: map[string]interface{}{
|
||||
"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/goldmark-mark v0.0.0-20210522162520-9788c89266a4
|
||||
git.jlel.se/jlelse/template-strings v0.0.0-20210617205924-cfa3bd35ae40
|
||||
github.com/PuerkitoBio/goquery v1.7.0
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.7.1
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||
|
@ -54,7 +53,7 @@ require (
|
|||
github.com/yuin/goldmark v1.4.0
|
||||
// master
|
||||
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/sync v0.0.0-20210220032951-036812b2e83c
|
||||
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/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.7.0 h1:O5SP3b9JWqMSVMG69zMfj577zwkSNpxrFf7ybS74eiw=
|
||||
github.com/PuerkitoBio/goquery v1.7.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
|
||||
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.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/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||
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-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-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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{
|
||||
path: blogConfig.getRelativePath(blogConfig.Photos.Path),
|
||||
parameter: blogConfig.Photos.Parameter,
|
||||
title: blogConfig.Photos.Title,
|
||||
description: blogConfig.Photos.Description,
|
||||
path: blogConfig.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath)),
|
||||
parameter: pc.Parameter,
|
||||
title: pc.Title,
|
||||
description: pc.Description,
|
||||
summaryTemplate: templatePhotosSummary,
|
||||
})
|
||||
}
|
||||
|
||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
||||
a.searchMiddlewares[blog] = middleware.WithValue(pathContextKey, blogConfig.getRelativePath(blogConfig.Search.Path))
|
||||
if bsc := blogConfig.Search; bsc != nil && bsc.Enabled {
|
||||
a.searchMiddlewares[blog] = middleware.WithValue(
|
||||
pathContextKey,
|
||||
blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath)),
|
||||
)
|
||||
}
|
||||
|
||||
for _, cp := range blogConfig.CustomPages {
|
||||
|
@ -437,11 +440,11 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
|||
}
|
||||
|
||||
// Photos
|
||||
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
|
||||
if pc := blogConfig.Photos; pc != nil && pc.Enabled {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(a.privateModeHandler...)
|
||||
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+feedPath, a.serveIndex)
|
||||
r.Get(photoPath+paginationPath, a.serveIndex)
|
||||
|
@ -449,14 +452,13 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
|||
}
|
||||
|
||||
// Search
|
||||
if blogConfig.Search != nil && blogConfig.Search.Enabled {
|
||||
searchPath := blogConfig.getRelativePath(blogConfig.Search.Path)
|
||||
r.With(sbm, a.searchMiddlewares[blog]).Mount(searchPath, a.searchRouter)
|
||||
if bsc := blogConfig.Search; bsc != nil && bsc.Enabled {
|
||||
r.With(sbm, a.searchMiddlewares[blog]).Mount(blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath)), a.searchRouter)
|
||||
}
|
||||
|
||||
// Stats
|
||||
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
|
||||
statsPath := blogConfig.getRelativePath(blogConfig.BlogStats.Path)
|
||||
if bsc := blogConfig.BlogStats; bsc != nil && bsc.Enabled {
|
||||
statsPath := blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(a.privateModeHandler...)
|
||||
r.Use(a.cache.cacheMiddleware, sbm)
|
||||
|
@ -513,11 +515,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
|||
|
||||
// Random post
|
||||
if rp := blogConfig.RandomPost; rp != nil && rp.Enabled {
|
||||
randomPath := rp.Path
|
||||
if randomPath == "" {
|
||||
randomPath = "/random"
|
||||
}
|
||||
r.With(a.privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(randomPath), a.redirectToRandomPost)
|
||||
r.With(a.privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(defaultIfEmpty(rp.Path, "/random")), a.redirectToRandomPost)
|
||||
}
|
||||
|
||||
// Editor
|
||||
|
@ -530,7 +528,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
|||
|
||||
// Blogroll
|
||||
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.Use(a.privateModeHandler...)
|
||||
r.Use(a.cache.cacheMiddleware, sbm)
|
||||
|
@ -541,11 +539,7 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
|
|||
|
||||
// Geo map
|
||||
if mc := blogConfig.Map; mc != nil && mc.Enabled {
|
||||
mapPath := mc.Path
|
||||
if mc.Path == "" {
|
||||
mapPath = "/map"
|
||||
}
|
||||
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm).Get(blogConfig.getRelativePath(mapPath), a.serveGeoMap)
|
||||
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm).Get(blogConfig.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath)), a.serveGeoMap)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
marktag "git.jlel.se/jlelse/goldmark-mark"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/yuin/goldmark"
|
||||
emoji "github.com/yuin/goldmark-emoji"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
|
@ -73,11 +71,7 @@ func (a *goBlog) renderText(s string) string {
|
|||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
d, err := goquery.NewDocumentFromReader(bytes.NewReader(h))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(d.Text())
|
||||
return htmlText(h)
|
||||
}
|
||||
|
||||
// Extensions etc...
|
||||
|
|
|
@ -360,6 +360,10 @@ func (a *goBlog) computeExtraPostParameters(p *post) error {
|
|||
p.Status = postStatus(status[0])
|
||||
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 == "" {
|
||||
// Has no path or section -> default section
|
||||
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)
|
||||
b := a.cfg.Blogs[blog]
|
||||
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/\">"+
|
||||
"<ShortName>%s</ShortName><Description>%s</Description>"+
|
||||
"<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 {
|
||||
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 ""
|
||||
}
|
||||
|
|
31
posts.go
31
posts.go
|
@ -19,16 +19,17 @@ import (
|
|||
var errPostNotFound = errors.New("post not found")
|
||||
|
||||
type post struct {
|
||||
Path string `json:"path"`
|
||||
Content string `json:"content"`
|
||||
Published string `json:"published"`
|
||||
Updated string `json:"updated"`
|
||||
Parameters map[string][]string `json:"parameters"`
|
||||
Blog string `json:"blog"`
|
||||
Section string `json:"section"`
|
||||
Status postStatus `json:"status"`
|
||||
Path string
|
||||
Content string
|
||||
Published string
|
||||
Updated string
|
||||
Parameters map[string][]string
|
||||
Blog string
|
||||
Section string
|
||||
Status postStatus
|
||||
Priority int
|
||||
// Not persisted
|
||||
Slug string `json:"slug"`
|
||||
Slug string
|
||||
rendered template.HTML
|
||||
absoluteRendered template.HTML
|
||||
}
|
||||
|
@ -167,8 +168,8 @@ func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
|
|||
type indexConfig struct {
|
||||
blog string
|
||||
path string
|
||||
section *section
|
||||
tax *taxonomy
|
||||
section *configSection
|
||||
tax *configTaxonomy
|
||||
taxValue string
|
||||
parameter string
|
||||
year, month, day int
|
||||
|
@ -177,6 +178,8 @@ type indexConfig struct {
|
|||
summaryTemplate string
|
||||
}
|
||||
|
||||
const defaultPhotosPath = "/photos"
|
||||
|
||||
const indexConfigKey contextKey = "indexConfig"
|
||||
|
||||
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")
|
||||
if search != "" {
|
||||
search = searchDecode(search)
|
||||
// Decode and sanitize search
|
||||
search = htmlText([]byte(bluemonday.StrictPolicy().Sanitize(searchDecode(search))))
|
||||
}
|
||||
pageNoString := chi.URLParam(r, "page")
|
||||
pageNo, _ := strconv.Atoi(pageNoString)
|
||||
|
@ -210,6 +214,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
|||
publishedMonth: ic.month,
|
||||
publishedDay: ic.day,
|
||||
status: statusPublished,
|
||||
priorityOrder: true,
|
||||
}, db: a.db}, a.cfg.Blogs[blog].Pagination)
|
||||
p.SetPage(pageNo)
|
||||
var posts []*post
|
||||
|
@ -231,8 +236,6 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
|||
} else if search != "" {
|
||||
title = fmt.Sprintf("%s: %s", a.cfg.Blogs[blog].Search.Title, search)
|
||||
}
|
||||
// Clean title
|
||||
title = bluemonday.StrictPolicy().Sanitize(title)
|
||||
// Check if feed
|
||||
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
|
||||
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)
|
||||
}
|
||||
// Insert new post
|
||||
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status) values (?, ?, ?, ?, ?, ?, ?);")
|
||||
sqlArgs = append(sqlArgs, p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section, p.Status)
|
||||
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, p.Priority)
|
||||
// Insert post parameters
|
||||
for param, value := range p.Parameters {
|
||||
for _, value := range value {
|
||||
|
@ -220,12 +220,13 @@ type postsRequestConfig struct {
|
|||
offset int
|
||||
sections []string
|
||||
status postStatus
|
||||
taxonomy *taxonomy
|
||||
taxonomy *configTaxonomy
|
||||
taxonomyValue string
|
||||
parameter string
|
||||
parameterValue string
|
||||
publishedYear, publishedMonth, publishedDay int
|
||||
randomOrder bool
|
||||
priorityOrder bool
|
||||
withoutParameters bool
|
||||
withOnlyParameters []string
|
||||
}
|
||||
|
@ -294,6 +295,8 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
|
|||
sorting := " order by published desc"
|
||||
if c.randomOrder {
|
||||
sorting = " order by random()"
|
||||
} else if c.priorityOrder {
|
||||
sorting = " order by priority desc, published desc"
|
||||
}
|
||||
table += sorting
|
||||
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) {
|
||||
// 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...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Prepare row scanning
|
||||
var path, content, published, updated, blog, section, status string
|
||||
var priority int
|
||||
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
|
||||
}
|
||||
// Create new post, fill and add to list
|
||||
|
@ -359,6 +363,7 @@ func (d *database) getPosts(config *postsRequestConfig) (posts []*post, err erro
|
|||
Blog: blog,
|
||||
Section: section,
|
||||
Status: postStatus(status),
|
||||
Priority: priority,
|
||||
}
|
||||
if !config.withoutParameters {
|
||||
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{
|
||||
"en": {
|
||||
Sections: map[string]*section{
|
||||
"test": {},
|
||||
Sections: map[string]*configSection{
|
||||
"test": {},
|
||||
"micro": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -241,6 +242,57 @@ func Test_ftsWithoutTitle(t *testing.T) {
|
|||
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) {
|
||||
app := &goBlog{
|
||||
cfg: &config{
|
||||
|
|
|
@ -120,14 +120,22 @@ func (p *post) isPublishedSectionPost() bool {
|
|||
}
|
||||
|
||||
func (a *goBlog) postToMfItem(p *post) *microformatItem {
|
||||
params := p.Parameters
|
||||
params["path"] = []string{p.Path}
|
||||
params["section"] = []string{p.Section}
|
||||
params["blog"] = []string{p.Blog}
|
||||
params["published"] = []string{p.Published}
|
||||
params["updated"] = []string{p.Updated}
|
||||
params["status"] = []string{string(p.Status)}
|
||||
pb, _ := yaml.Marshal(p.Parameters)
|
||||
params := map[string]interface{}{}
|
||||
for k, v := range p.Parameters {
|
||||
if l := len(v); l == 1 {
|
||||
params[k] = v[0]
|
||||
} else if l > 1 {
|
||||
params[k] = v
|
||||
}
|
||||
}
|
||||
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)
|
||||
return µformatItem{
|
||||
Type: []string{"h-entry"},
|
||||
|
|
|
@ -7,8 +7,11 @@ import (
|
|||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
)
|
||||
|
||||
const defaultSearchPath = "/search"
|
||||
const searchPlaceholder = "{search}"
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
|
22
sitemap.go
22
sitemap.go
|
@ -101,27 +101,33 @@ func (a *goBlog) serveSitemap(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
// Photos
|
||||
if bc.Photos != nil && bc.Photos.Enabled {
|
||||
if pc := bc.Photos; pc != nil && pc.Enabled {
|
||||
sm.Add(&sitemap.URL{
|
||||
Loc: a.getFullAddress(bc.getRelativePath(bc.Photos.Path)),
|
||||
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))),
|
||||
})
|
||||
}
|
||||
// Search
|
||||
if bc.Search != nil && bc.Search.Enabled {
|
||||
if bsc := bc.Search; bsc != nil && bsc.Enabled {
|
||||
sm.Add(&sitemap.URL{
|
||||
Loc: a.getFullAddress(bc.getRelativePath(bc.Search.Path)),
|
||||
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath))),
|
||||
})
|
||||
}
|
||||
// Stats
|
||||
if bc.BlogStats != nil && bc.BlogStats.Enabled {
|
||||
if bsc := bc.BlogStats; bsc != nil && bsc.Enabled {
|
||||
sm.Add(&sitemap.URL{
|
||||
Loc: a.getFullAddress(bc.getRelativePath(bc.BlogStats.Path)),
|
||||
Loc: a.getFullAddress(bc.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))),
|
||||
})
|
||||
}
|
||||
// Blogroll
|
||||
if bc.Blogroll != nil && bc.Blogroll.Enabled {
|
||||
if brc := bc.Blogroll; brc != nil && brc.Enabled {
|
||||
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
|
||||
|
|
|
@ -6,7 +6,7 @@ const taxonomyContextKey = "taxonomy"
|
|||
|
||||
func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
if err != nil {
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
</ul>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}
|
||||
{{ include "interactions" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blogroll" }}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
|
||||
<script defer src="{{ asset "js/blogstats.js" }}"></script>
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}
|
||||
{{ include "interactions" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blogstats" }}
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
<script defer src="{{ asset "js/geomap.js" }}"></script>
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ if .CommentsEnabled }}
|
||||
{{ include "interactions" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "geomap" }}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
{{ define "photosummary" }}
|
||||
<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" . }}
|
||||
{{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }}
|
||||
{{ md ( printf "![](%s)" $photo ) }}
|
||||
|
|
|
@ -26,6 +26,7 @@ nofiles: "Keine Dateien"
|
|||
nolocations: "Keine Posts mit Standorten"
|
||||
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."
|
||||
pinned: "Angepinnt"
|
||||
posts: "Posts"
|
||||
prev: "Zurück"
|
||||
publishedon: "Veröffentlicht am"
|
||||
|
|
|
@ -38,6 +38,7 @@ noposts: "There are no posts here."
|
|||
notifications: "Notifications"
|
||||
oldcontent: "⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed."
|
||||
password: "Password"
|
||||
pinned: "Pinned"
|
||||
posts: "Posts"
|
||||
prev: "Previous"
|
||||
publishedon: "Published on"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{{ define "summary" }}
|
||||
<article class="h-entry border-bottom">
|
||||
{{ 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 }}">
|
||||
|
|
16
utils.go
16
utils.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
@ -211,3 +212,18 @@ func getSHA256(file io.ReadSeeker) (filename string, err error) {
|
|||
func mBytesString(size int64) string {
|
||||
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