Add option for plugins to add custom template assets, add custom CSS plugin (fixes #54)

This commit is contained in:
Jan-Lukas Else 2023-02-24 16:50:46 +01:00
parent 49a0053044
commit 503b98327f
9 changed files with 126 additions and 10 deletions

View File

@ -92,6 +92,14 @@ Third-party modules
Some simple plugins are included in the main GoBlog repository. Some can be found elsewhere. 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`) ### 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. 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.

5
go.mod
View File

@ -48,7 +48,7 @@ require (
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/samber/lo v1.37.0 github.com/samber/lo v1.37.0
github.com/schollz/sqlite3dump v1.3.1 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/sourcegraph/conc v0.2.0
github.com/spf13/cast v1.5.0 github.com/spf13/cast v1.5.0
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.15.0
@ -91,7 +91,6 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.13.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-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/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.5.2 // 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/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/xid v1.4.0 // indirect github.com/rs/xid v1.4.0 // indirect
github.com/rs/zerolog v1.29.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/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect

9
go.sum
View File

@ -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 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= 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 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 h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= 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/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/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/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.4 h1:XnlKoBarZWiAEnNBYE5t1nbvJhdaoTaW7IBzu0R4AqM=
github.com/snabb/diagio v1.0.1/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1QQa1w= github.com/snabb/diagio v1.0.4/go.mod h1:Y+Pja4UJrskCOKaLxOfa8b8wYSVb0JWpR4YFNHuzjDI=
github.com/snabb/sitemap v1.0.3 h1:4DFB7bcDELJ3nL8OXohX6G3IbahS1vOMrHxMhU5SNzI= github.com/snabb/sitemap v1.0.4 h1:BC6cPW5jXLsKWtlYQKD2s1W58CarvNzqOmdl680uQPw=
github.com/snabb/sitemap v1.0.3/go.mod h1:sVjDylXD+ZHu+xplNJZ8o5GMfbxNEx+lGVg7YUuXnac= 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 h1:96VpOCAtXDCQ8Oycz0ftHqdPyMi8w12ltN4L2noYg7s=
github.com/sourcegraph/conc v0.2.0/go.mod h1:8lmPpTLA0hsWqw4lw7wS1e694U2tMjRrc1Asvupb4QM= github.com/sourcegraph/conc v0.2.0/go.mod h1:8lmPpTLA0hsWqw4lw7wS1e694U2tMjRrc1Asvupb4QM=
github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 h1:4yKiBHEHJXHu9umlQzhX4sRK622p+Aw4TGvvAw9X9j8= github.com/sourcegraph/sourcegraph/lib v0.0.0-20221216004406-749998a2ac74 h1:4yKiBHEHJXHu9umlQzhX4sRK622p+Aw4TGvvAw9X9j8=

View File

@ -329,8 +329,8 @@ func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
return return
} }
} }
// No post, check regex redirects or serve 404 error // No post, check template assets (dynamically registered), regex redirects or serve 404 error
alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r) alice.New(a.cacheMiddleware, a.checkTemplateAssets, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
} }
} }

View File

@ -3,6 +3,7 @@ package plugintypes
import ( import (
"context" "context"
"database/sql" "database/sql"
"io"
"net/http" "net/http"
) )
@ -16,6 +17,10 @@ type App interface {
PurgeCache() PurgeCache()
// Get the HTTP client used by GoBlog // Get the HTTP client used by GoBlog
GetHTTPClient() *http.Client 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. // Database is used to provide access to GoBlog's database.

View File

@ -65,12 +65,20 @@ func init() {
// _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type // _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type
type _go_goblog_app_app_pkgs_plugintypes_App struct { type _go_goblog_app_app_pkgs_plugintypes_App struct {
IValue interface{} IValue interface{}
WAssetPath func(filename string) string
WCompileAsset func(filename string, reader io.Reader) error
WGetDatabase func() plugintypes.Database WGetDatabase func() plugintypes.Database
WGetHTTPClient func() *http.Client WGetHTTPClient func() *http.Client
WGetPost func(path string) (plugintypes.Post, error) WGetPost func(path string) (plugintypes.Post, error)
WPurgeCache func() 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 { func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database {
return W.WGetDatabase() return W.WGetDatabase()
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"embed" "embed"
"io"
"io/fs" "io/fs"
"net/http" "net/http"
"reflect" "reflect"
@ -89,6 +90,14 @@ func (a *goBlog) GetHTTPClient() *http.Client {
return a.httpClient 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 { func (p *post) GetPath() string {
return p.Path return p.Path
} }

View File

@ -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())
}

View File

@ -89,6 +89,17 @@ func (a *goBlog) allAssetPaths() []string {
return paths 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 // Gets only called by registered paths
func (a *goBlog) serveAsset(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveAsset(w http.ResponseWriter, r *http.Request) {
af, ok := a.assetFiles[strings.TrimPrefix(r.URL.Path, "/")] 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) a.serve404(w, r)
return 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(cacheControl, "public,max-age=31536000,immutable")
w.Header().Set(contentType, af.contentType+contenttype.CharsetUtf8Suffix) w.Header().Set(contentType, af.contentType+contenttype.CharsetUtf8Suffix)
_, _ = w.Write(af.body) _, _ = w.Write(af.body)