meme: use templ and htmx

This commit is contained in:
Chris Sexton 2024-02-27 17:16:45 -05:00
parent 2d06fd6be8
commit a5e919733c
6 changed files with 371 additions and 52 deletions

83
plugins/meme/meme.templ Normal file
View File

@ -0,0 +1,83 @@
package meme
templ (p *MemePlugin) index(all webResps) {
<div class="container">
<form>
<div class="row">
<div class="col-3">
<input type="text" name="name" placeholder="Name..." />
</div>
<div class="col-3">
<input type="text" name="url" placeholder="URL..." />
</div>
<div class="col-3">
<textarea name="config">
</textarea>
</div>
<div class="col-3">
<button class="btn btn-primary"
hx-post="/meme/add"
hx-target="#newMemes"
>Save</button>
</div>
</div>
</form>
<div id="newMemes">
</div>
for _, meme := range all {
@p.Show(meme)
}
</div>
}
templ (p *MemePlugin) Show(meme webResp) {
<div class="row" id={ meme.Name }>
<div class="col-3">
{ meme.Name }
<img
class="img-thumbnail rounded"
alt={ meme.Name }
src={ meme.URL } />
</div>
<div class="col-3">
<pre>
{ meme.Config }
</pre>
</div>
<div class="col-3">
<button class="btn btn-primary"
hx-get={ "/meme/edit/"+meme.Name }
hx-target={ "#"+meme.Name }
>Edit</button>
</div>
</div>
}
templ (p *MemePlugin) Edit(meme webResp) {
<form>
<div class="row" id={ meme.Name }>
<div class="col-3">
<img
class="img-thumbnail rounded"
alt={ meme.Name }
src={ meme.URL } />
</div>
<div class="col-3">
<textarea name="config">
{ meme.Config }
</textarea>
<input type="text" name="url" value={ meme.URL } />
</div>
<div class="col-3">
<button class="btn btn-primary"
hx-put={ "/meme/save/"+meme.Name }
hx-target={ "#"+meme.Name }
>Save</button>
<button class="btn btn-danger"
hx-delete={ "/meme/rm/"+meme.Name }
hx-target={ "#"+meme.Name }
>Delete</button>
</div>
</div>
</form>
}

236
plugins/meme/meme_templ.go Normal file
View File

@ -0,0 +1,236 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.543
package meme
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func (p *MemePlugin) index(all webResps) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container\"><form><div class=\"row\"><div class=\"col-3\"><input type=\"text\" name=\"name\" placeholder=\"Name...\"></div><div class=\"col-3\"><input type=\"text\" name=\"url\" placeholder=\"URL...\"></div><div class=\"col-3\"><textarea name=\"config\"></textarea></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-post=\"/meme/add\" hx-target=\"#newMemes\">Save</button></div></div></form><div id=\"newMemes\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, meme := range all {
templ_7745c5c3_Err = p.Show(meme).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func (p *MemePlugin) Show(meme webResp) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"col-3\"><img class=\"img-thumbnail rounded\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 39, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col-3\"><pre>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Config)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 43, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</pre></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/edit/" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Edit</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func (p *MemePlugin) Edit(meme webResp) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form><div class=\"row\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"col-3\"><img class=\"img-thumbnail rounded\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"col-3\"><textarea name=\"config\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Config)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 66, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea> <input type=\"text\" name=\"url\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-put=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/save/" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Save</button> <button class=\"btn btn-danger\" hx-delete=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/rm/" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Delete</button></div></div></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -20,11 +20,12 @@ var embeddedFS embed.FS
func (p *MemePlugin) registerWeb(c bot.Connector) { func (p *MemePlugin) registerWeb(c bot.Connector) {
r := chi.NewRouter() r := chi.NewRouter()
r.HandleFunc("/slash", p.slashMeme(c)) r.HandleFunc("/slash", p.slashMeme(c))
r.HandleFunc("/img", p.img) r.Get("/img", p.img)
r.HandleFunc("/all", p.all) r.Put("/save/{name}", p.saveMeme)
r.HandleFunc("/add", p.addMeme) r.Post("/add", p.saveMeme)
r.HandleFunc("/rm", p.rmMeme) r.Delete("/rm/{name}", p.rmMeme)
r.HandleFunc("/", p.webRoot) r.Get("/edit/{name}", p.editMeme)
r.Get("/", p.webRoot)
p.bot.GetWeb().RegisterWebName(r, "/meme", "Memes") p.bot.GetWeb().RegisterWebName(r, "/meme", "Memes")
} }
@ -43,7 +44,7 @@ type ByName struct{ webResps }
func (s ByName) Less(i, j int) bool { return s.webResps[i].Name < s.webResps[j].Name } func (s ByName) Less(i, j int) bool { return s.webResps[i].Name < s.webResps[j].Name }
func (p *MemePlugin) all(w http.ResponseWriter, r *http.Request) { func (p *MemePlugin) all() webResps {
memes := p.c.GetMap("meme.memes", defaultFormats) memes := p.c.GetMap("meme.memes", defaultFormats)
configs := p.c.GetMap("meme.memeconfigs", map[string]string{}) configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
@ -51,7 +52,7 @@ func (p *MemePlugin) all(w http.ResponseWriter, r *http.Request) {
for n, u := range memes { for n, u := range memes {
config, ok := configs[n] config, ok := configs[n]
if !ok { if !ok {
b, _ := json.Marshal(p.defaultFormatConfig()) b, _ := json.MarshalIndent(p.defaultFormatConfig(), " ", " ")
config = string(b) config = string(b)
} }
realURL, err := url.Parse(u) realURL, err := url.Parse(u)
@ -64,13 +65,7 @@ func (p *MemePlugin) all(w http.ResponseWriter, r *http.Request) {
} }
sort.Sort(ByName{values}) sort.Sort(ByName{values})
out, err := json.Marshal(values) return values
if err != nil {
w.WriteHeader(500)
log.Error().Err(err).Msgf("could not serve all memes route")
return
}
w.Write(out)
} }
func mkCheckError(w http.ResponseWriter) func(error) bool { func mkCheckError(w http.ResponseWriter) func(error) bool {
@ -87,56 +82,61 @@ func mkCheckError(w http.ResponseWriter) func(error) bool {
} }
func (p *MemePlugin) rmMeme(w http.ResponseWriter, r *http.Request) { func (p *MemePlugin) rmMeme(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete { name := chi.URLParam(r, "name")
w.WriteHeader(405)
fmt.Fprintf(w, "Incorrect HTTP method")
return
}
checkError := mkCheckError(w)
decoder := json.NewDecoder(r.Body)
values := webResp{}
err := decoder.Decode(&values)
if checkError(err) {
return
}
formats := p.c.GetMap("meme.memes", defaultFormats) formats := p.c.GetMap("meme.memes", defaultFormats)
delete(formats, values.Name) delete(formats, name)
err = p.c.SetMap("meme.memes", formats) err := p.c.SetMap("meme.memes", formats)
checkError(err) mkCheckError(w)(err)
} }
func (p *MemePlugin) addMeme(w http.ResponseWriter, r *http.Request) { func (p *MemePlugin) saveMeme(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { name := chi.URLParam(r, "name")
w.WriteHeader(405) if name == "" {
fmt.Fprintf(w, "Incorrect HTTP method") name = r.FormValue("name")
return
} }
checkError := mkCheckError(w) checkError := mkCheckError(w)
decoder := json.NewDecoder(r.Body)
values := webResp{}
err := decoder.Decode(&values)
if checkError(err) {
log.Error().Err(err).Msgf("could not decode body")
return
}
log.Debug().Msgf("POSTed values: %+v", values)
formats := p.c.GetMap("meme.memes", defaultFormats) formats := p.c.GetMap("meme.memes", defaultFormats)
formats[values.Name] = values.URL formats[name] = r.FormValue("url")
err = p.c.SetMap("meme.memes", formats) err := p.c.SetMap("meme.memes", formats)
checkError(err) checkError(err)
if values.Config == "" { config := r.FormValue("config")
values.Config = p.defaultFormatConfigJSON() if config == "" {
config = p.defaultFormatConfigJSON()
} }
configs := p.c.GetMap("meme.memeconfigs", map[string]string{}) configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
configs[values.Name] = values.Config configs[name] = config
err = p.c.SetMap("meme.memeconfigs", configs) err = p.c.SetMap("meme.memeconfigs", configs)
checkError(err) checkError(err)
meme := webResp{
Name: name,
URL: formats[name],
Config: configs[name],
}
p.Show(meme).Render(r.Context(), w)
} }
func (p *MemePlugin) webRoot(w http.ResponseWriter, r *http.Request) { func (p *MemePlugin) webRoot(w http.ResponseWriter, r *http.Request) {
index, _ := embeddedFS.ReadFile("index.html") p.bot.GetWeb().Index("Meme", p.index(p.all())).Render(r.Context(), w)
w.Write(index) }
func (p *MemePlugin) editMeme(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
memes := p.c.GetMap("meme.memes", defaultFormats)
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
meme, ok := memes[name]
if !ok {
fmt.Fprintf(w, "Didn't find that meme.")
}
resp := webResp{
Name: name,
URL: meme,
Config: configs[name],
}
p.Edit(resp).Render(r.Context(), w)
} }
func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) { func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) {

View File

@ -18,7 +18,7 @@ type PageComment struct {
c *config.Config c *config.Config
} }
func New(b bot.Bot) *PageComment { func New(b bot.Bot) bot.Plugin {
p := &PageComment{ p := &PageComment{
b: b, b: b,
c: b.Config(), c: b.Config(),

View File

@ -19,7 +19,7 @@ type RolesPlugin struct {
h bot.HandlerTable h bot.HandlerTable
} }
func New(b bot.Bot) *RolesPlugin { func New(b bot.Bot) bot.Plugin {
p := &RolesPlugin{ p := &RolesPlugin{
b: b, b: b,
c: b.Config(), c: b.Config(),

View File

@ -19,7 +19,7 @@ type SecretsPlugin struct {
db *sqlx.DB db *sqlx.DB
} }
func New(b bot.Bot) *SecretsPlugin { func New(b bot.Bot) bot.Plugin {
p := &SecretsPlugin{ p := &SecretsPlugin{
b: b, b: b,
c: b.Config(), c: b.Config(),