diff --git a/blogroll.go b/blogroll.go index 4d0a060..15ad615 100644 --- a/blogroll.go +++ b/blogroll.go @@ -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", }, }) } diff --git a/blogstats.go b/blogstats.go index a610967..784e142 100644 --- a/blogstats.go +++ b/blogstats.go @@ -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), diff --git a/config.go b/config.go index 6337583..d56980d 100644 --- a/config.go +++ b/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 diff --git a/customPages.go b/customPages.go index dd6df89..3f9df38 100644 --- a/customPages.go +++ b/customPages.go @@ -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) diff --git a/geoMap.go b/geoMap.go index 50f9175..6cb0136 100644 --- a/geoMap.go +++ b/geoMap.go @@ -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), }, diff --git a/go.mod b/go.mod index 90efe3b..91e0b01 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index d6ac583..a32b71e 100644 --- a/go.sum +++ b/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= diff --git a/http.go b/http.go index cc80ac8..49a337a 100644 --- a/http.go +++ b/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) } } diff --git a/markdown.go b/markdown.go index 6df7fef..fb951ca 100644 --- a/markdown.go +++ b/markdown.go @@ -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... diff --git a/micropub.go b/micropub.go index cbf0020..172be1f 100644 --- a/micropub.go +++ b/micropub.go @@ -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 diff --git a/opensearch.go b/opensearch.go index 77da779..720ea24 100644 --- a/opensearch.go +++ b/opensearch.go @@ -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(""+ "%s%s"+ ""+ @@ -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 "" } diff --git a/posts.go b/posts.go index 33ff971..b1f1f72 100644 --- a/posts.go +++ b/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) diff --git a/postsDb.go b/postsDb.go index 2c30822..53b61f3 100644 --- a/postsDb.go +++ b/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 { diff --git a/postsDb_test.go b/postsDb_test.go index 514d8f2..5eb8023 100644 --- a/postsDb_test.go +++ b/postsDb_test.go @@ -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{ diff --git a/postsFuncs.go b/postsFuncs.go index b322bfa..d2459f7 100644 --- a/postsFuncs.go +++ b/postsFuncs.go @@ -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"}, diff --git a/search.go b/search.go index 45e5823..4fb146d 100644 --- a/search.go +++ b/search.go @@ -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 } diff --git a/sitemap.go b/sitemap.go index b72e934..8ce690c 100644 --- a/sitemap.go +++ b/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 diff --git a/taxonomies.go b/taxonomies.go index fb25f51..4e15b9d 100644 --- a/taxonomies.go +++ b/taxonomies.go @@ -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) diff --git a/templates/blogroll.gohtml b/templates/blogroll.gohtml index 15e3f50..6b41058 100644 --- a/templates/blogroll.gohtml +++ b/templates/blogroll.gohtml @@ -21,6 +21,9 @@ {{ end }} + {{ if .CommentsEnabled }} + {{ include "interactions" . }} + {{ end }} {{ end }} {{ define "blogroll" }} diff --git a/templates/blogstats.gohtml b/templates/blogstats.gohtml index febbe74..d4ca75b 100644 --- a/templates/blogstats.gohtml +++ b/templates/blogstats.gohtml @@ -9,6 +9,9 @@

{{ string .Blog.Lang "loading" }}

+ {{ if .CommentsEnabled }} + {{ include "interactions" . }} + {{ end }} {{ end }} {{ define "blogstats" }} diff --git a/templates/geomap.gohtml b/templates/geomap.gohtml index cae2489..2201d08 100644 --- a/templates/geomap.gohtml +++ b/templates/geomap.gohtml @@ -24,6 +24,9 @@ {{ end }} + {{ if .CommentsEnabled }} + {{ include "interactions" . }} + {{ end }} {{ end }} {{ define "geomap" }} diff --git a/templates/photosummary.gohtml b/templates/photosummary.gohtml index e80c262..10023a1 100644 --- a/templates/photosummary.gohtml +++ b/templates/photosummary.gohtml @@ -1,6 +1,13 @@ {{ define "photosummary" }}
- {{ with p .Data "title" }}

{{ . }}

{{ end }} + {{ if gt .Data.Priority 0 }}

📌 {{ string .Blog.Lang "pinned" }}

{{ end }} + {{ if p .Data "title" }} +

+ + {{ p .Data "title" }} + +

+ {{ end }} {{ include "summarymeta" . }} {{ range $i, $photo := ( ps .Data .Blog.Photos.Parameter ) }} {{ md ( printf "![](%s)" $photo ) }} diff --git a/templates/strings/de.yaml b/templates/strings/de.yaml index a29b15b..cf30714 100644 --- a/templates/strings/de.yaml +++ b/templates/strings/de.yaml @@ -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" diff --git a/templates/strings/default.yaml b/templates/strings/default.yaml index 3a7d43d..df65b41 100644 --- a/templates/strings/default.yaml +++ b/templates/strings/default.yaml @@ -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" diff --git a/templates/summary.gohtml b/templates/summary.gohtml index 2eee432..0d23c09 100644 --- a/templates/summary.gohtml +++ b/templates/summary.gohtml @@ -1,5 +1,6 @@ {{ define "summary" }}
+ {{ if gt .Data.Priority 0 }}

📌 {{ string .Blog.Lang "pinned" }}

{{ end }} {{ if p .Data "title" }}

diff --git a/utils.go b/utils.go index e5cb232..60b3444 100644 --- a/utils.go +++ b/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 +}