mirror of https://github.com/jlelse/GoBlog
Add native blogroll integration
This commit is contained in:
parent
42873f8681
commit
1ef34889ae
|
@ -0,0 +1,115 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaorimatz/go-opml"
|
||||||
|
"github.com/thoas/go-funk"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
var blogrollCacheGroup singleflight.Group
|
||||||
|
|
||||||
|
func serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
|
c := appConfig.Blogs[blog].Blogroll
|
||||||
|
if !c.Enabled {
|
||||||
|
serve404(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outlines, err, _ := blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
|
return getBlogrollOutlines(c)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if appConfig.Cache != nil && appConfig.Cache.Enable {
|
||||||
|
setInternalCacheExpirationHeader(w, r, int(appConfig.Cache.Expiration))
|
||||||
|
}
|
||||||
|
render(w, r, templateBlogroll, &renderData{
|
||||||
|
BlogString: blog,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"Title": c.Title,
|
||||||
|
"Description": c.Description,
|
||||||
|
"Outlines": outlines,
|
||||||
|
"Download": c.Path + ".opml",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
blog := r.Context().Value(blogContextKey).(string)
|
||||||
|
c := appConfig.Blogs[blog].Blogroll
|
||||||
|
if !c.Enabled {
|
||||||
|
serve404(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outlines, err, _ := blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
|
return getBlogrollOutlines(c)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if appConfig.Cache != nil && appConfig.Cache.Enable {
|
||||||
|
setInternalCacheExpirationHeader(w, r, int(appConfig.Cache.Expiration))
|
||||||
|
}
|
||||||
|
w.Header().Set(contentType, contentTypeXMLUTF8)
|
||||||
|
mw := minifier.Writer(contentTypeXML, w)
|
||||||
|
defer func() {
|
||||||
|
_ = mw.Close()
|
||||||
|
}()
|
||||||
|
_ = opml.Render(mw, &opml.OPML{
|
||||||
|
Version: "2.0",
|
||||||
|
DateCreated: time.Now().UTC(),
|
||||||
|
Outlines: outlines.([]*opml.Outline),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlogrollOutlines(config *configBlogroll) ([]*opml.Outline, error) {
|
||||||
|
if config.cachedOutlines != nil && time.Since(config.lastCache).Minutes() < 60 {
|
||||||
|
// return cache if younger than 60 min
|
||||||
|
return config.cachedOutlines, nil
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodGet, config.Opml, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config.AuthHeader != "" && config.AuthValue != "" {
|
||||||
|
req.Header.Set(config.AuthHeader, config.AuthValue)
|
||||||
|
}
|
||||||
|
res, err := appHttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, _ = io.Copy(io.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
}()
|
||||||
|
if code := res.StatusCode; code < 200 || 300 <= code {
|
||||||
|
return nil, fmt.Errorf("opml request not successfull, status code: %d", code)
|
||||||
|
}
|
||||||
|
o, err := opml.Parse(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outlines := o.Outlines
|
||||||
|
if len(config.Categories) > 0 {
|
||||||
|
filtered := []*opml.Outline{}
|
||||||
|
for _, category := range config.Categories {
|
||||||
|
if outline, ok := funk.Find(outlines, func(outline *opml.Outline) bool {
|
||||||
|
return outline.Title == category || outline.Text == category
|
||||||
|
}).(*opml.Outline); ok && outline != nil {
|
||||||
|
filtered = append(filtered, outline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outlines = filtered
|
||||||
|
}
|
||||||
|
config.cachedOutlines = outlines
|
||||||
|
config.lastCache = time.Now()
|
||||||
|
return outlines, nil
|
||||||
|
}
|
7
cache.go
7
cache.go
|
@ -21,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cacheInternalExpirationHeader = "GoBlog-Expire"
|
cacheInternalExpirationHeader = "Goblog-Expire"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -212,6 +212,9 @@ func purgeCache() {
|
||||||
cacheR.Clear()
|
cacheR.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setInternalCacheExpirationHeader(w http.ResponseWriter, expiration int) {
|
func setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) {
|
||||||
|
if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.Header().Set(cacheInternalExpirationHeader, strconv.Itoa(expiration))
|
w.Header().Set(cacheInternalExpirationHeader, strconv.Itoa(expiration))
|
||||||
}
|
}
|
||||||
|
|
22
config.go
22
config.go
|
@ -4,7 +4,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaorimatz/go-opml"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,6 +65,7 @@ type configBlog struct {
|
||||||
Photos *photos `mapstructure:"photos"`
|
Photos *photos `mapstructure:"photos"`
|
||||||
Search *search `mapstructure:"search"`
|
Search *search `mapstructure:"search"`
|
||||||
BlogStats *blogStats `mapstructure:"blogStats"`
|
BlogStats *blogStats `mapstructure:"blogStats"`
|
||||||
|
Blogroll *configBlogroll `mapstructure:"blogroll"`
|
||||||
CustomPages []*customPage `mapstructure:"custompages"`
|
CustomPages []*customPage `mapstructure:"custompages"`
|
||||||
Telegram *configTelegram `mapstructure:"telegram"`
|
Telegram *configTelegram `mapstructure:"telegram"`
|
||||||
PostAsHome bool `mapstructure:"postAsHome"`
|
PostAsHome bool `mapstructure:"postAsHome"`
|
||||||
|
@ -115,6 +118,19 @@ type blogStats struct {
|
||||||
Description string `mapstructure:"description"`
|
Description string `mapstructure:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type configBlogroll struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
Opml string `mapstructure:"opml"`
|
||||||
|
AuthHeader string `mapstructure:"authHeader"`
|
||||||
|
AuthValue string `mapstructure:"authValue"`
|
||||||
|
Categories []string `mapstructure:"categories"`
|
||||||
|
Title string `mapstructure:"title"`
|
||||||
|
Description string `mapstructure:"description"`
|
||||||
|
cachedOutlines []*opml.Outline
|
||||||
|
lastCache time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type customPage struct {
|
type customPage struct {
|
||||||
Path string `mapstructure:"path"`
|
Path string `mapstructure:"path"`
|
||||||
Template string `mapstructure:"template"`
|
Template string `mapstructure:"template"`
|
||||||
|
@ -288,6 +304,12 @@ func initConfig() error {
|
||||||
b.Comments = &comments{Enabled: false}
|
b.Comments = &comments{Enabled: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check config for each blog
|
||||||
|
for _, blog := range appConfig.Blogs {
|
||||||
|
if br := blog.Blogroll; br != nil && br.Enabled && br.Opml == "" {
|
||||||
|
br.Enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ func serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||||
page := r.Context().Value(customPageContextKey).(*customPage)
|
page := r.Context().Value(customPageContextKey).(*customPage)
|
||||||
if appConfig.Cache != nil && appConfig.Cache.Enable && page.Cache {
|
if appConfig.Cache != nil && appConfig.Cache.Enable && page.Cache {
|
||||||
if page.CacheExpiration != 0 {
|
if page.CacheExpiration != 0 {
|
||||||
setInternalCacheExpirationHeader(w, page.CacheExpiration)
|
setInternalCacheExpirationHeader(w, r, page.CacheExpiration)
|
||||||
} else {
|
} else {
|
||||||
setInternalCacheExpirationHeader(w, int(appConfig.Cache.Expiration))
|
setInternalCacheExpirationHeader(w, r, int(appConfig.Cache.Expiration))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render(w, r, page.Template, &renderData{
|
render(w, r, page.Template, &renderData{
|
||||||
|
|
|
@ -175,6 +175,17 @@ blogs:
|
||||||
path: /statistics # Path
|
path: /statistics # Path
|
||||||
title: Statistics # Title
|
title: Statistics # Title
|
||||||
description: "Here are some statistics with the number of posts per year:" # Description
|
description: "Here are some statistics with the number of posts per year:" # Description
|
||||||
|
# Blogroll
|
||||||
|
blogroll:
|
||||||
|
enabled: true # Enable
|
||||||
|
path: /blogroll # Path
|
||||||
|
title: Blogroll # Title
|
||||||
|
description: "I follow these blog:" # Description
|
||||||
|
opml: https://example.com/blogroll.opml # Required, URL to the OPML file
|
||||||
|
authHeader: X-Auth # Optional, header to use for OPML authentication
|
||||||
|
authValue: abc # Authentication value for OPML
|
||||||
|
categories: # Optional, allow only these categories
|
||||||
|
- Blogs
|
||||||
# Custom pages
|
# Custom pages
|
||||||
custompages:
|
custompages:
|
||||||
- path: /blogroll # Path
|
- path: /blogroll # Path
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -25,6 +25,7 @@ require (
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
|
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
|
||||||
|
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/kyokomi/emoji/v2 v2.2.8
|
github.com/kyokomi/emoji/v2 v2.2.8
|
||||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -178,6 +178,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9 h1:+9REu9CK9D1AQ6C/PXXwGRcoKdT04cuHR5JgGD4DKqc=
|
||||||
|
github.com/kaorimatz/go-opml v0.0.0-20210201121027-bc8e2852d7f9/go.mod h1:OvY5ZBrAC9kOvM2PZs9Lw0BH+5K7tjrT6T7SFhn27OA=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
||||||
|
@ -413,6 +415,7 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA=
|
golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA=
|
||||||
|
|
13
http.go
13
http.go
|
@ -26,6 +26,7 @@ const (
|
||||||
charsetUtf8Suffix = "; charset=utf-8"
|
charsetUtf8Suffix = "; charset=utf-8"
|
||||||
|
|
||||||
contentTypeHTML = "text/html"
|
contentTypeHTML = "text/html"
|
||||||
|
contentTypeXML = "text/xml"
|
||||||
contentTypeJSON = "application/json"
|
contentTypeJSON = "application/json"
|
||||||
contentTypeWWWForm = "application/x-www-form-urlencoded"
|
contentTypeWWWForm = "application/x-www-form-urlencoded"
|
||||||
contentTypeMultipartForm = "multipart/form-data"
|
contentTypeMultipartForm = "multipart/form-data"
|
||||||
|
@ -35,6 +36,7 @@ const (
|
||||||
contentTypeJSONFeed = "application/feed+json"
|
contentTypeJSONFeed = "application/feed+json"
|
||||||
|
|
||||||
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
|
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
|
||||||
|
contentTypeXMLUTF8 = contentTypeXML + charsetUtf8Suffix
|
||||||
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
|
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
|
||||||
contentTypeASUTF8 = contentTypeAS + charsetUtf8Suffix
|
contentTypeASUTF8 = contentTypeAS + charsetUtf8Suffix
|
||||||
|
|
||||||
|
@ -542,6 +544,17 @@ func buildDynamicRouter() (*chi.Mux, error) {
|
||||||
commentsPath := blogPath + "/comment"
|
commentsPath := blogPath + "/comment"
|
||||||
r.With(sbm, commentsMiddlewares[blog]).Mount(commentsPath, commentsRouter)
|
r.With(sbm, commentsMiddlewares[blog]).Mount(commentsPath, commentsRouter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blogroll
|
||||||
|
if brConfig := blogConfig.Blogroll; brConfig != nil && brConfig.Enabled {
|
||||||
|
brPath := blogPath + brConfig.Path
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(privateModeHandler...)
|
||||||
|
r.Use(cacheMiddleware, sbm)
|
||||||
|
r.Get(brPath, serveBlogroll)
|
||||||
|
r.Get(brPath+".opml", serveBlogrollExport)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sitemap
|
// Sitemap
|
||||||
|
|
|
@ -17,7 +17,7 @@ func initMinify() {
|
||||||
minifier = minify.New()
|
minifier = minify.New()
|
||||||
minifier.AddFunc(contentTypeHTML, mHtml.Minify)
|
minifier.AddFunc(contentTypeHTML, mHtml.Minify)
|
||||||
minifier.AddFunc("text/css", mCss.Minify)
|
minifier.AddFunc("text/css", mCss.Minify)
|
||||||
minifier.AddFunc("text/xml", mXml.Minify)
|
minifier.AddFunc(contentTypeXML, mXml.Minify)
|
||||||
minifier.AddFunc("application/javascript", mJs.Minify)
|
minifier.AddFunc("application/javascript", mJs.Minify)
|
||||||
minifier.AddFunc(contentTypeRSS, mXml.Minify)
|
minifier.AddFunc(contentTypeRSS, mXml.Minify)
|
||||||
minifier.AddFunc(contentTypeATOM, mXml.Minify)
|
minifier.AddFunc(contentTypeATOM, mXml.Minify)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -39,6 +40,7 @@ const (
|
||||||
templateCommentsAdmin = "commentsadmin"
|
templateCommentsAdmin = "commentsadmin"
|
||||||
templateNotificationsAdmin = "notificationsadmin"
|
templateNotificationsAdmin = "notificationsadmin"
|
||||||
templateWebmentionAdmin = "webmentionadmin"
|
templateWebmentionAdmin = "webmentionadmin"
|
||||||
|
templateBlogroll = "blogroll"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates map[string]*template.Template = map[string]*template.Template{}
|
var templates map[string]*template.Template = map[string]*template.Template{}
|
||||||
|
@ -164,6 +166,9 @@ func initRendering() error {
|
||||||
})
|
})
|
||||||
return mentions
|
return mentions
|
||||||
},
|
},
|
||||||
|
"urlToString": func(u url.URL) string {
|
||||||
|
return u.String()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
|
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
|
||||||
|
|
|
@ -114,6 +114,12 @@ func serveSitemap(w http.ResponseWriter, r *http.Request) {
|
||||||
Loc: appConfig.Server.PublicAddress + bc.getRelativePath(bc.BlogStats.Path),
|
Loc: appConfig.Server.PublicAddress + bc.getRelativePath(bc.BlogStats.Path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Blogroll
|
||||||
|
if bc.Blogroll != nil && bc.Blogroll.Enabled {
|
||||||
|
sm.Add(&sitemap.URL{
|
||||||
|
Loc: appConfig.Server.PublicAddress + bc.getRelativePath(bc.Blogroll.Path),
|
||||||
|
})
|
||||||
|
}
|
||||||
// Custom pages
|
// Custom pages
|
||||||
for _, cp := range bc.CustomPages {
|
for _, cp := range bc.CustomPages {
|
||||||
sm.Add(&sitemap.URL{
|
sm.Add(&sitemap.URL{
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
{{ define "title" }}
|
||||||
|
<title>{{ .Data.Title }} - {{ .Blog.Title }}</title>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "main" }}
|
||||||
|
<main>
|
||||||
|
{{ with .Data.Title }}<h1>{{ . }}</h1>{{ end }}
|
||||||
|
{{ with .Data.Description }}{{ md . }}{{ end }}
|
||||||
|
<p><a href="{{ blogrelative .Blog .Data.Download }}" class="button" download>{{ string .Blog.Lang "download" }}</a></p>
|
||||||
|
{{ $lang := .Blog.Lang }}
|
||||||
|
{{ range .Data.Outlines }}
|
||||||
|
<h2>{{ with .Title }}{{ . }}{{ else }}{{ with .Text }}{{ . }}{{ end }}{{ end }}</h2>
|
||||||
|
<ul>
|
||||||
|
{{ range .Outlines }}
|
||||||
|
<li><a href="{{ urlToString .HTMLURL }}" target="_blank">{{ with .Title }}{{ . }}{{ else }}{{ with .Text }}{{ . }}{{ end }}{{ end }}</a> (<a href="{{ urlToString .XMLURL }}" target="_blank">{{ string $lang "feed" }}</a>)</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
{{ end }}
|
||||||
|
</main>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "blogroll" }}
|
||||||
|
{{ template "base" . }}
|
||||||
|
{{ end }}
|
|
@ -1,23 +0,0 @@
|
||||||
{{ define "title" }}
|
|
||||||
<title>{{ .Data.Title }} - {{ .Blog.Title }}</title>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "main" }}
|
|
||||||
<main>
|
|
||||||
<h1>{{ .Data.Title }}</h1>
|
|
||||||
{{ md .Data.Description }}
|
|
||||||
{{ $opmlJson := index ( jsonFile "data/opml.json" ) "outline" }}
|
|
||||||
{{ range $opmlJson }}
|
|
||||||
{{ md (printf "%s %s" "##" ._text) }}
|
|
||||||
<ul>
|
|
||||||
{{ range (index . "outline") }}
|
|
||||||
<li><a href="{{ ._htmlUrl }}" target="_blank">{{ ._title }}</a> (<a href="{{ ._xmlUrl }}" target="_blank">Feed</a>)</li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
{{ end }}
|
|
||||||
</main>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "blogroll" }}
|
|
||||||
{{ template "base" . }}
|
|
||||||
{{ end }}
|
|
|
@ -6,6 +6,7 @@ confirmdelete: "Löschen bestätigen"
|
||||||
create: "Erstellen"
|
create: "Erstellen"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
docomment: "Kommentieren"
|
docomment: "Kommentieren"
|
||||||
|
download: "Herunterladen"
|
||||||
drafts: "Entwürfe"
|
drafts: "Entwürfe"
|
||||||
editor: "Editor"
|
editor: "Editor"
|
||||||
interactions: "Interaktionen & Kommentare"
|
interactions: "Interaktionen & Kommentare"
|
||||||
|
|
|
@ -11,8 +11,10 @@ confirmdelete: "Confirm deletion"
|
||||||
create: "Create"
|
create: "Create"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
docomment: "Comment"
|
docomment: "Comment"
|
||||||
|
download: "Download"
|
||||||
drafts: "Drafts"
|
drafts: "Drafts"
|
||||||
editor: "Editor"
|
editor: "Editor"
|
||||||
|
feed: "Feed"
|
||||||
indieauth: "IndieAuth"
|
indieauth: "IndieAuth"
|
||||||
interactions: "Interactions & Comments"
|
interactions: "Interactions & Comments"
|
||||||
interactionslabel: "Have you published a response to this? Paste the URL here."
|
interactionslabel: "Have you published a response to this? Paste the URL here."
|
||||||
|
|
Loading…
Reference in New Issue