Add UI2 plugin type for more resource efficiency and rewrite the UI plugins to use the new type

This commit is contained in:
Jan-Lukas Else 2023-02-13 20:22:42 +01:00
parent e1914d8610
commit 812cff1941
10 changed files with 65 additions and 33 deletions

View File

@ -162,7 +162,7 @@ func (a *goBlog) apCheckMentions(p *post) {
}
apc := a.apHttpClients[p.Blog]
mentions := []string{}
for _, link := range lo.Uniq(links) {
for _, link := range links {
act, err := apc.Actor(context.Background(), ap.IRI(link))
if err != nil || act == nil || act.Type != ap.PersonType {
continue

View File

@ -33,6 +33,7 @@ You need to specify the path to the plugin (remember to mount the path to your G
- `Exec` (Command that is executed in a Go routine when starting GoBlog) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Exec
- `Middleware` (HTTP middleware to intercept or modify HTTP requests) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Middleware
- `UI` (Modify rendered HTML) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI
- `UI2` (Modify rendered HTML using a goquery document which improves performance and avoids multiple HTML parsing and rendering when using multiple plugins) - see https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI2
More types will be added later. Any plugin can implement multiple types, see the demo plugin as example.

View File

@ -3,6 +3,8 @@ package plugintypes
import (
"io"
"net/http"
"github.com/PuerkitoBio/goquery"
)
// SetApp is used to allow GoBlog set its app instance to be accessible by the plugin.
@ -34,3 +36,10 @@ type UI interface {
// The renderContext provides information such as the path of the request or the blog name.
Render(renderContext RenderContext, rendered io.Reader, modified io.Writer)
}
// UI2 plugins get called when rendering HTML.
type UI2 interface {
// The renderContext provides information such as the path of the request or the blog name.
// The document can be used to add or modify HTML.
RenderWithDocument(renderContext RenderContext, doc *goquery.Document)
}

View File

@ -55,6 +55,7 @@ func init() {
"ErrorJSON": reflect.ValueOf(requests.ErrorJSON),
"GzipConfig": reflect.ValueOf(requests.GzipConfig),
"HasStatusErr": reflect.ValueOf(requests.HasStatusErr),
"LogTransport": reflect.ValueOf(requests.LogTransport),
"MaxFollow": reflect.ValueOf(requests.MaxFollow),
"NewCookieJar": reflect.ValueOf(requests.NewCookieJar),
"NoFollow": reflect.ValueOf(&requests.NoFollow).Elem(),

View File

@ -27,6 +27,7 @@ package yaegiwrappers
import (
"context"
"database/sql"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/plugintypes"
"io"
"net/http"
@ -45,6 +46,7 @@ func init() {
"SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)),
"SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)),
"UI": reflect.ValueOf((*plugintypes.UI)(nil)),
"UI2": reflect.ValueOf((*plugintypes.UI2)(nil)),
// interface wrapper definitions
"_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)),
@ -56,6 +58,7 @@ func init() {
"_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)),
"_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)),
"_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)),
"_UI2": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI2)(nil)),
}
}
@ -204,3 +207,13 @@ type _go_goblog_app_app_pkgs_plugintypes_UI struct {
func (W _go_goblog_app_app_pkgs_plugintypes_UI) Render(renderContext plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
W.WRender(renderContext, rendered, modified)
}
// _go_goblog_app_app_pkgs_plugintypes_UI2 is an interface wrapper for UI2 type
type _go_goblog_app_app_pkgs_plugintypes_UI2 struct {
IValue interface{}
WRenderWithDocument func(renderContext plugintypes.RenderContext, doc *goquery.Document)
}
func (W _go_goblog_app_app_pkgs_plugintypes_UI2) RenderWithDocument(renderContext plugintypes.RenderContext, doc *goquery.Document) {
W.WRenderWithDocument(renderContext, doc)
}

View File

@ -18,6 +18,7 @@ const (
pluginSetAppType = "setapp"
pluginSetConfigType = "setconfig"
pluginUiType = "ui"
pluginUi2Type = "ui2"
pluginExecType = "exec"
pluginMiddlewareType = "middleware"
)
@ -32,6 +33,7 @@ func (a *goBlog) initPlugins() error {
pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(),
pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(),
pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(),
pluginUi2Type: reflect.TypeOf((*plugintypes.UI2)(nil)).Elem(),
pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(),
pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(),
},

View File

@ -19,11 +19,12 @@ type plugin struct {
func GetPlugin() (
plugintypes.SetApp, plugintypes.SetConfig,
plugintypes.UI,
plugintypes.UI2,
plugintypes.Exec,
plugintypes.Middleware,
) {
p := &plugin{}
return p, p, p, p, p
return p, p, p, p, p, p
}
// SetApp
@ -40,7 +41,7 @@ func (p *plugin) SetConfig(config map[string]any) {
func (*plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil {
fmt.Println("demoui plugin: " + err.Error())
fmt.Println("demo plugin: " + err.Error())
return
}
buf := bufferpool.Get()
@ -53,6 +54,17 @@ func (*plugin) Render(_ plugintypes.RenderContext, rendered io.Reader, modified
_ = goquery.Render(modified, doc.Selection)
}
// UI
func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
buf := bufferpool.Get()
defer bufferpool.Put(buf)
hb := htmlbuilder.NewHtmlBuilder(buf)
hb.WriteElementOpen("p")
hb.WriteEscaped("Second end of post content")
hb.WriteElementClose("p")
doc.Find("main.h-entry article div.e-content").AppendHtml(buf.String())
}
// Exec
func (p *plugin) Exec() {
fmt.Println("Hello World from the demo plugin!")

View File

@ -1,9 +1,6 @@
package syndication
import (
"fmt"
"io"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/htmlbuilder"
@ -15,7 +12,7 @@ type plugin struct {
parameterName string
}
func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI) {
func GetPlugin() (plugintypes.SetConfig, plugintypes.SetApp, plugintypes.UI2) {
p := &plugin{}
return p, p, p
}
@ -33,24 +30,13 @@ func (p *plugin) SetConfig(config map[string]any) {
}
}
func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
def := func() {
_, _ = io.Copy(modified, rendered)
}
func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
post, err := p.app.GetPost(rc.GetPath())
if err != nil || post == nil {
def()
return
}
syndicationLinks, ok := post.GetParameters()[p.parameterName]
if !ok || len(syndicationLinks) == 0 {
def()
return
}
doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil {
fmt.Println("syndication plugin: " + err.Error())
def()
return
}
buf := bufferpool.Get()
@ -61,5 +47,4 @@ func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modifi
hb.WriteElementClose("data")
}
doc.Find("main.h-entry article").AppendHtml(buf.String())
_ = goquery.Render(modified, doc.Selection)
}

View File

@ -2,7 +2,6 @@ package webrings
import (
"fmt"
"io"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/bufferpool"
@ -10,7 +9,7 @@ import (
"go.goblog.app/app/pkgs/plugintypes"
)
func GetPlugin() (plugintypes.SetConfig, plugintypes.UI) {
func GetPlugin() (plugintypes.SetConfig, plugintypes.UI2) {
p := &plugin{}
return p, p
}
@ -23,17 +22,12 @@ func (p *plugin) SetConfig(config map[string]any) {
p.config = config
}
func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modified io.Writer) {
func (p *plugin) RenderWithDocument(rc plugintypes.RenderContext, doc *goquery.Document) {
blog := rc.GetBlog()
if blog == "" {
fmt.Println("webrings plugin: blog is empty!")
return
}
doc, err := goquery.NewDocumentFromReader(rendered)
if err != nil {
fmt.Println("webrings plugin: " + err.Error())
return
}
if blogWebringsAny, ok := p.config[blog]; ok {
if blogWebrings, ok := blogWebringsAny.([]any); ok {
buf := bufferpool.Get()
@ -74,7 +68,6 @@ func (p *plugin) Render(rc plugintypes.RenderContext, rendered io.Reader, modifi
}
}
}
_ = goquery.Render(modified, doc.Selection)
}
func unwrapToString(o any) (string, bool) {

View File

@ -4,6 +4,7 @@ import (
"io"
"net/http"
"github.com/PuerkitoBio/goquery"
"go.goblog.app/app/pkgs/contenttype"
"go.goblog.app/app/pkgs/htmlbuilder"
"go.goblog.app/app/pkgs/plugintypes"
@ -45,15 +46,30 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
f(htmlbuilder.NewHtmlBuilder(renderPipeWriter), data)
_ = renderPipeWriter.Close()
}()
// Create render context for plugins
rc := &pluginRenderContext{
blog: data.BlogString,
path: r.URL.Path,
}
// Run UI plugins
pluginPipeReader, pluginPipeWriter := io.Pipe()
go func() {
a.chainUiPlugins(a.getPlugins(pluginUiType), &pluginRenderContext{
blog: data.BlogString,
path: r.URL.Path,
}, renderPipeReader, pluginPipeWriter)
a.chainUiPlugins(a.getPlugins(pluginUiType), rc, renderPipeReader, pluginPipeWriter)
_ = pluginPipeWriter.Close()
}()
// Run UI2 plugins
ui2Plugins := a.getPlugins(pluginUi2Type)
if len(ui2Plugins) > 0 {
doc, _ := goquery.NewDocumentFromReader(pluginPipeReader)
_ = pluginPipeReader.Close()
for _, plugin := range ui2Plugins {
plugin.(plugintypes.UI2).RenderWithDocument(rc, doc)
}
pluginPipeReader, pluginPipeWriter = io.Pipe()
go func() {
_ = pluginPipeWriter.CloseWithError(goquery.Render(pluginPipeWriter, doc.Selection))
}()
}
// Return minified HTML
_ = pluginPipeReader.CloseWithError(a.min.Get().Minify(contenttype.HTML, w, pluginPipeReader))
}