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.
### 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.

5
go.mod
View File

@ -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

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/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=

View File

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

View File

@ -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.

View File

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

View File

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

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