From 503b98327fea1b71bde191ab44b4c366056e63a5 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Fri, 24 Feb 2023 16:50:46 +0100 Subject: [PATCH] Add option for plugins to add custom template assets, add custom CSS plugin (fixes #54) --- docs/plugins.md | 8 ++ go.mod | 5 +- go.sum | 9 +-- http.go | 4 +- pkgs/plugintypes/app.go | 5 ++ .../go_goblog_app-app-pkgs-plugintypes.go | 8 ++ plugins.go | 9 +++ plugins/customcss/src/customcss/customcss.go | 73 +++++++++++++++++++ templateAssets.go | 15 ++++ 9 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 plugins/customcss/src/customcss/customcss.go diff --git a/docs/plugins.md b/docs/plugins.md index 19be7fa..e7ade83 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -92,6 +92,14 @@ Third-party modules Some simple plugins are included in the main GoBlog repository. Some can be found elsewhere. +### Custom CSS (Path `embedded:customcss`, Import `customcss`) + +A plugin that can add custom CSS to every HTML page. Just specify a CSS file and it will minify the file and append it to the rendered HTML head. + +#### Config + +`file` (string): Path to the custom CSS file. + ### Demo (Path `embedded:demo`, Import `demo`) A simple demo plugin showcasing some of the features plugins can implement. Take a look at the source code, if you want to implement your own plugin. diff --git a/go.mod b/go.mod index 3358416..9204728 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/pquerna/otp v1.4.0 github.com/samber/lo v1.37.0 github.com/schollz/sqlite3dump v1.3.1 - github.com/snabb/sitemap v1.0.3 + github.com/snabb/sitemap v1.0.4 github.com/sourcegraph/conc v0.2.0 github.com/spf13/cast v1.5.0 github.com/spf13/viper v1.15.0 @@ -91,7 +91,6 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getsentry/sentry-go v0.13.0 // indirect github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea // indirect - github.com/go-test/deep v1.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -118,7 +117,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rs/xid v1.4.0 // indirect github.com/rs/zerolog v1.29.0 // indirect - github.com/snabb/diagio v1.0.1 // indirect + github.com/snabb/diagio v1.0.4 // indirect github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 7d3f2d7..3454e33 100644 --- a/go.sum +++ b/go.sum @@ -198,7 +198,6 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZp github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -495,10 +494,10 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snabb/diagio v1.0.1 h1:l7HODYLuGuPfom3Rbm/HHdp1RdrVyAy5iWEzLkRXlH0= -github.com/snabb/diagio v1.0.1/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1QQa1w= -github.com/snabb/sitemap v1.0.3 h1:4DFB7bcDELJ3nL8OXohX6G3IbahS1vOMrHxMhU5SNzI= -github.com/snabb/sitemap v1.0.3/go.mod h1:sVjDylXD+ZHu+xplNJZ8o5GMfbxNEx+lGVg7YUuXnac= +github.com/snabb/diagio v1.0.4 h1:XnlKoBarZWiAEnNBYE5t1nbvJhdaoTaW7IBzu0R4AqM= +github.com/snabb/diagio v1.0.4/go.mod h1:Y+Pja4UJrskCOKaLxOfa8b8wYSVb0JWpR4YFNHuzjDI= +github.com/snabb/sitemap v1.0.4 h1:BC6cPW5jXLsKWtlYQKD2s1W58CarvNzqOmdl680uQPw= +github.com/snabb/sitemap v1.0.4/go.mod h1:815/fxQQ8Tt7Eqwe8Lcat4ax73zuHyPxWBZySnbaxkc= github.com/sourcegraph/conc v0.2.0 h1:96VpOCAtXDCQ8Oycz0ftHqdPyMi8w12ltN4L2noYg7s= github.com/sourcegraph/conc v0.2.0/go.mod h1:8lmPpTLA0hsWqw4lw7wS1e694U2tMjRrc1Asvupb4QM= github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 h1:4yKiBHEHJXHu9umlQzhX4sRK622p+Aw4TGvvAw9X9j8= diff --git a/http.go b/http.go index 9fad56b..a90f834 100644 --- a/http.go +++ b/http.go @@ -329,8 +329,8 @@ func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc { return } } - // No post, check regex redirects or serve 404 error - alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r) + // No post, check template assets (dynamically registered), regex redirects or serve 404 error + alice.New(a.cacheMiddleware, a.checkTemplateAssets, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r) } } diff --git a/pkgs/plugintypes/app.go b/pkgs/plugintypes/app.go index 7fcc6c4..9ac5198 100644 --- a/pkgs/plugintypes/app.go +++ b/pkgs/plugintypes/app.go @@ -3,6 +3,7 @@ package plugintypes import ( "context" "database/sql" + "io" "net/http" ) @@ -16,6 +17,10 @@ type App interface { PurgeCache() // Get the HTTP client used by GoBlog GetHTTPClient() *http.Client + // Compile an asset (like CSS, JS, etc.) and add it to use when rendering, for some filetypes, it also get's compressed + CompileAsset(filename string, reader io.Reader) error + // Get the asset path with the filename used when compiling the assert + AssetPath(filename string) string } // Database is used to provide access to GoBlog's database. diff --git a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go index a769630..2ca57e6 100644 --- a/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go +++ b/pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go @@ -65,12 +65,20 @@ func init() { // _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type type _go_goblog_app_app_pkgs_plugintypes_App struct { IValue interface{} + WAssetPath func(filename string) string + WCompileAsset func(filename string, reader io.Reader) error WGetDatabase func() plugintypes.Database WGetHTTPClient func() *http.Client WGetPost func(path string) (plugintypes.Post, error) WPurgeCache func() } +func (W _go_goblog_app_app_pkgs_plugintypes_App) AssetPath(filename string) string { + return W.WAssetPath(filename) +} +func (W _go_goblog_app_app_pkgs_plugintypes_App) CompileAsset(filename string, reader io.Reader) error { + return W.WCompileAsset(filename, reader) +} func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database { return W.WGetDatabase() } diff --git a/plugins.go b/plugins.go index 91c16b4..b16f1ee 100644 --- a/plugins.go +++ b/plugins.go @@ -2,6 +2,7 @@ package main import ( "embed" + "io" "io/fs" "net/http" "reflect" @@ -89,6 +90,14 @@ func (a *goBlog) GetHTTPClient() *http.Client { return a.httpClient } +func (a *goBlog) CompileAsset(filename string, reader io.Reader) error { + return a.compileAsset(filename, reader) +} + +func (a *goBlog) AssetPath(filename string) string { + return a.assetFileName(filename) +} + func (p *post) GetPath() string { return p.Path } diff --git a/plugins/customcss/src/customcss/customcss.go b/plugins/customcss/src/customcss/customcss.go new file mode 100644 index 0000000..5ab1276 --- /dev/null +++ b/plugins/customcss/src/customcss/customcss.go @@ -0,0 +1,73 @@ +package customcss + +import ( + "fmt" + "os" + "sync" + + "github.com/PuerkitoBio/goquery" + "go.goblog.app/app/pkgs/bufferpool" + "go.goblog.app/app/pkgs/htmlbuilder" + "go.goblog.app/app/pkgs/plugintypes" +) + +type plugin struct { + app plugintypes.App + + customCSS string + init sync.Once + inited bool +} + +func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI2) { + p := &plugin{} + return p, p, p +} + +func (p *plugin) SetConfig(config map[string]any) { + if filePath, ok := config["file"]; ok { + if filePathString, ok := filePath.(string); ok { + p.customCSS = filePathString + } + } +} + +func (p *plugin) SetApp(app plugintypes.App) { + p.app = app +} + +func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) { + if p.app == nil || p.customCSS == "" { + return + } + + p.init.Do(func() { + f, err := os.Open(p.customCSS) + if err != nil { + fmt.Println("Failed to open custom css file: ", err.Error()) + return + } + defer f.Close() + + err = p.app.CompileAsset("plugincustomcss.css", f) + if err != nil { + fmt.Println("Failed compile custom css: ", err.Error()) + return + } + + p.inited = true + fmt.Println("Custom CSS compiled") + }) + + if !p.inited { + return + } + + buf := bufferpool.Get() + defer bufferpool.Put(buf) + hb := htmlbuilder.NewHtmlBuilder(buf) + + hb.WriteElementOpen("link", "rel", "stylesheet", "href", p.app.AssetPath("plugincustomcss.css")) + + doc.Find("head").AppendHtml(buf.String()) +} diff --git a/templateAssets.go b/templateAssets.go index c665886..c5ed811 100644 --- a/templateAssets.go +++ b/templateAssets.go @@ -89,6 +89,17 @@ func (a *goBlog) allAssetPaths() []string { return paths } +func (a *goBlog) checkTemplateAssets(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + af, ok := a.assetFiles[strings.TrimPrefix(r.URL.Path, "/")] + if !ok { + next.ServeHTTP(w, r) + return + } + a.serveAssetFile(w, af) + }) +} + // Gets only called by registered paths func (a *goBlog) serveAsset(w http.ResponseWriter, r *http.Request) { af, ok := a.assetFiles[strings.TrimPrefix(r.URL.Path, "/")] @@ -96,6 +107,10 @@ func (a *goBlog) serveAsset(w http.ResponseWriter, r *http.Request) { a.serve404(w, r) return } + a.serveAssetFile(w, af) +} + +func (*goBlog) serveAssetFile(w http.ResponseWriter, af *assetFile) { w.Header().Set(cacheControl, "public,max-age=31536000,immutable") w.Header().Set(contentType, af.contentType+contenttype.CharsetUtf8Suffix) _, _ = w.Write(af.body)