diff --git a/feeds.go b/feeds.go new file mode 100644 index 0000000..6b11bef --- /dev/null +++ b/feeds.go @@ -0,0 +1,59 @@ +package main + +import ( + "github.com/gorilla/feeds" + "net/http" + "time" +) + +type feedType string + +const ( + RSS feedType = "rss" + ATOM feedType = "atom" + JSON feedType = "json" +) + +func generateFeed(f feedType, w http.ResponseWriter, posts []*Post, title string, description string) { + now := time.Now() + feed := &feeds.Feed{ + Title: title, + Description: description, + Link: &feeds.Link{}, + Created: now, + } + for _, postItem := range posts { + htmlContent, _ := renderMarkdown(postItem.Content) + feed.Add(&feeds.Item{ + Title: postItem.title(), + Link: &feeds.Link{Href: postItem.Path}, + Description: string(htmlContent), + Id: postItem.Path, + Content: string(htmlContent), + }) + } + var feedStr string + var err error + switch f { + case RSS: + feedStr, err = feed.ToRss() + case ATOM: + feedStr, err = feed.ToAtom() + case JSON: + feedStr, err = feed.ToJSON() + } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + switch f { + case RSS: + w.Header().Add("Content-Type", "application/rss+xml; charset=utf-8") + case ATOM: + w.Header().Add("Content-Type", "application/atom+xml; charset=utf-8") + case JSON: + w.Header().Add("Content-Type", "application/feed+json; charset=utf-8") + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(feedStr)) +} diff --git a/go.mod b/go.mod index 149575c..9bec854 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/go-chi/chi v4.1.2+incompatible github.com/google/go-cmp v0.5.2 // indirect github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect + github.com/gorilla/feeds v1.1.1 github.com/jinzhu/gorm v1.9.16 // indirect github.com/klauspost/cpuid v1.3.1 // indirect + github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kyokomi/emoji v2.2.4+incompatible github.com/lib/pq v1.8.0 // indirect @@ -26,7 +28,7 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.7.1 - github.com/tdewolff/minify/v2 v2.9.2 + github.com/tdewolff/minify/v2 v2.9.3 github.com/vcraescu/go-paginator v0.0.0-20200304054438-86d84f27c0b3 github.com/yuin/goldmark v1.2.1 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect @@ -34,7 +36,7 @@ require ( golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/ini.v1 v1.60.2 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect diff --git a/go.sum b/go.sum index 14a429f..6d18b53 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= +github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -251,6 +253,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -412,8 +416,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tdewolff/minify/v2 v2.9.2 h1:paeFMZEtrmWVJSmvZ+htMQ5RhbkBbLVyXdzs6f8Es+s= -github.com/tdewolff/minify/v2 v2.9.2/go.mod h1:njYNbXhVTAhI1hARVHCbHAgRd44j+AEt0LdW+menKsY= +github.com/tdewolff/minify/v2 v2.9.3 h1:5RMoa8DAqfI/Gne9rM1hnsQXZax24dnZhcQWHOFD7f4= +github.com/tdewolff/minify/v2 v2.9.3/go.mod h1:njYNbXhVTAhI1hARVHCbHAgRd44j+AEt0LdW+menKsY= github.com/tdewolff/parse/v2 v2.5.1 h1:1PxbcgMxb8RWH1nmeQjBC+lZIOTUEjiYQ3u8RpzndN0= github.com/tdewolff/parse/v2 v2.5.1/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= @@ -682,8 +686,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= diff --git a/http.go b/http.go index 6d00e5f..78e8476 100644 --- a/http.go +++ b/http.go @@ -110,10 +110,9 @@ func buildHandler() (http.Handler, error) { } } - routePatterns := routesToStringSlice(r.Routes()) rootPath := "/" blogPath := "/blog" - if !routePatterns.has(rootPath) { + if routePatterns := routesToStringSlice(r.Routes()); !routePatterns.has(rootPath) { r.With(cacheMiddleware, minifier.Middleware).Get(rootPath, serveHome(rootPath)) r.With(cacheMiddleware, minifier.Middleware).Get(paginationPath, serveHome(rootPath)) } else if !routePatterns.has(blogPath) { diff --git a/minify.go b/minify.go index 1a24297..849cd04 100644 --- a/minify.go +++ b/minify.go @@ -5,7 +5,8 @@ import ( "github.com/tdewolff/minify/v2/css" "github.com/tdewolff/minify/v2/html" "github.com/tdewolff/minify/v2/js" - "regexp" + "github.com/tdewolff/minify/v2/json" + "github.com/tdewolff/minify/v2/xml" ) var minifier *minify.M @@ -14,5 +15,8 @@ func initMinify() { minifier = minify.New() minifier.AddFunc("text/html", html.Minify) minifier.AddFunc("text/css", css.Minify) - minifier.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify) + minifier.AddFunc("application/javascript", js.Minify) + minifier.AddFunc("application/rss+xml", xml.Minify) + minifier.AddFunc("application/atom+xml", xml.Minify) + minifier.AddFunc("application/feed+json", json.Minify) } diff --git a/posts.go b/posts.go index 9f3cbc6..d2159e0 100644 --- a/posts.go +++ b/posts.go @@ -23,6 +23,17 @@ type Post struct { Parameters map[string][]string `json:"parameters"` } +func (p *Post) firstParameter(parameter string) (result string) { + if pp := p.Parameters[parameter]; len(pp) > 0 { + result = pp[0] + } + return +} + +func (p *Post) title() string { + return p.firstParameter("title") +} + func servePost(w http.ResponseWriter, r *http.Request) { path := slashTrimmedPath(r) post, err := getPost(r.Context(), path) @@ -140,6 +151,11 @@ func serveIndex(path string, sec *section, tax *taxonomy, taxonomyValue string) title = appConfig.Blog.Title description = appConfig.Blog.Description } + f := feedType(r.URL.Query().Get("feed")) + if f != "" { + generateFeed(f, w, posts, title, description) + return + } render(w, templateIndex, &indexTemplateData{ Title: title, Description: description, diff --git a/render.go b/render.go index 3c11213..fa29756 100644 --- a/render.go +++ b/render.go @@ -32,17 +32,16 @@ func initRendering() { return template.HTML(htmlContent) }, // First parameter value - "p": func(post Post, parameter string) string { - if len(post.Parameters[parameter]) > 0 { - return post.Parameters[parameter][0] - } else { - return "" - } + "p": func(post *Post, parameter string) string { + return post.firstParameter(parameter) }, // All parameter values - "ps": func(post Post, parameter string) []string { + "ps": func(post *Post, parameter string) []string { return post.Parameters[parameter] }, + "title": func(post *Post) string { + return post.title() + }, "include": func(templateName string, data interface{}) (template.HTML, error) { buf := new(bytes.Buffer) err := templates[templateName].ExecuteTemplate(buf, templateName, data) diff --git a/templates/post.gohtml b/templates/post.gohtml index ef3f90b..aee0d11 100644 --- a/templates/post.gohtml +++ b/templates/post.gohtml @@ -5,7 +5,7 @@ {{ define "main" }}
- {{ with p . "title" }}

{{ . }}

{{ end }} + {{ with title . }}

{{ . }}

{{ end }} {{ with .Content }}
{{ md . }}
{{ end }}