Add webrings plugin

master
Jan-Lukas Else 1 month ago
parent 1f2510ac53
commit e1edd5c18c
  1. 5
      config.go
  2. 18
      docs/plugins.md
  3. 14
      pkgs/plugintypes/goblog.go
  4. 25
      pkgs/yaegiwrappers/go_goblog_app-app-pkgs-plugintypes.go
  5. 16
      plugins.go
  6. 80
      plugins/webrings/src/webrings/webrings.go
  7. 50
      ui.go

@ -95,6 +95,7 @@ type configBlog struct {
Map *configGeoMap `mapstructure:"map"`
Contact *configContact `mapstructure:"contact"`
Announcement *configAnnouncement `mapstructure:"announcement"`
name string
// Configs read from database
hideOldContentWarning bool
hideShareButton bool
@ -429,6 +430,10 @@ func (a *goBlog) initConfig(logging bool) error {
if a.cfg.Blogs[a.cfg.DefaultBlog] == nil {
return errors.New("default blog does not exist")
}
// Set name attribute for every blog
for name, blog := range a.cfg.Blogs {
blog.name = name
}
// Check media storage config
if ms := a.cfg.Micropub.MediaStorage; ms != nil && ms.MediaURL != "" {
ms.MediaURL = strings.TrimSuffix(ms.MediaURL, "/")

@ -41,4 +41,20 @@ Adds hidden `u-syndication` `data` elements to post page when the configured pos
#### Config
`parameter` (string): Name for the post parameter containing the syndication links.
`parameter` (string): Name for the post parameter containing the syndication links.
### Webrings (plugins/webrings)
Adds webring links to the bottom of the blog footer to easily participate in webrings.
#### Config
You can add webring links like this:
```yaml
config:
default: # Name of the blog
- title: Webring # Title to show for the webring
prev: https://example.com/ # Link to previous webring site
next: https://example.net/ # Link to next webring site
```

@ -27,6 +27,11 @@ type Post interface {
GetParameters() map[string][]string
}
// Blog
type Blog interface {
GetBlog() string
}
// RenderType
type RenderType string
@ -46,3 +51,12 @@ type PostRenderData interface {
RenderData
GetPost() Post
}
// Render footer element on every blog page, data = BlogRenderData
const BlogFooterRenderType RenderType = "blog-footer"
// BlogRenderData is RenderData containing a Blog
type BlogRenderData interface {
RenderData
GetBlog() Blog
}

@ -36,10 +36,13 @@ import (
func init() {
Symbols["go.goblog.app/app/pkgs/plugintypes/plugintypes"] = map[string]reflect.Value{
// function, constant and variable definitions
"BlogFooterRenderType": reflect.ValueOf(plugintypes.BlogFooterRenderType),
"PostMainElementRenderType": reflect.ValueOf(plugintypes.PostMainElementRenderType),
// type definitions
"App": reflect.ValueOf((*plugintypes.App)(nil)),
"Blog": reflect.ValueOf((*plugintypes.Blog)(nil)),
"BlogRenderData": reflect.ValueOf((*plugintypes.BlogRenderData)(nil)),
"Database": reflect.ValueOf((*plugintypes.Database)(nil)),
"Exec": reflect.ValueOf((*plugintypes.Exec)(nil)),
"Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)),
@ -54,6 +57,8 @@ func init() {
// interface wrapper definitions
"_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)),
"_Blog": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Blog)(nil)),
"_BlogRenderData": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_BlogRenderData)(nil)),
"_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)),
"_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)),
"_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)),
@ -76,6 +81,26 @@ func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Datab
return W.WGetDatabase()
}
// _go_goblog_app_app_pkgs_plugintypes_Blog is an interface wrapper for Blog type
type _go_goblog_app_app_pkgs_plugintypes_Blog struct {
IValue interface{}
WGetBlog func() string
}
func (W _go_goblog_app_app_pkgs_plugintypes_Blog) GetBlog() string {
return W.WGetBlog()
}
// _go_goblog_app_app_pkgs_plugintypes_BlogRenderData is an interface wrapper for BlogRenderData type
type _go_goblog_app_app_pkgs_plugintypes_BlogRenderData struct {
IValue interface{}
WGetBlog func() plugintypes.Blog
}
func (W _go_goblog_app_app_pkgs_plugintypes_BlogRenderData) GetBlog() plugintypes.Blog {
return W.WGetBlog()
}
// _go_goblog_app_app_pkgs_plugintypes_Database is an interface wrapper for Database type
type _go_goblog_app_app_pkgs_plugintypes_Database struct {
IValue interface{}

@ -72,3 +72,19 @@ func (d *pluginPostRenderData) GetPost() plugintypes.Post {
func (p *post) pluginRenderData() plugintypes.PostRenderData {
return &pluginPostRenderData{p: p}
}
func (b *configBlog) GetBlog() string {
return b.name
}
type pluginBlogRenderData struct {
b *configBlog
}
func (d *pluginBlogRenderData) GetBlog() plugintypes.Blog {
return d.b
}
func (b *configBlog) pluginRenderData() plugintypes.BlogRenderData {
return &pluginBlogRenderData{b: b}
}

@ -0,0 +1,80 @@
package webrings
import (
"fmt"
"go.goblog.app/app/pkgs/htmlbuilder"
"go.goblog.app/app/pkgs/plugintypes"
)
func GetPlugin() plugintypes.UI {
return &plugin{}
}
type plugin struct {
config map[string]any
}
func (*plugin) SetApp(_ plugintypes.App) {
// Ignore
}
func (p *plugin) SetConfig(config map[string]any) {
p.config = config
}
func (p *plugin) Render(hb *htmlbuilder.HtmlBuilder, t plugintypes.RenderType, data plugintypes.RenderData, render plugintypes.RenderNextFunc) {
render(hb)
if t == plugintypes.BlogFooterRenderType {
bd, ok := data.(plugintypes.BlogRenderData)
if !ok {
fmt.Println("webrings plugin: data is not BlogRenderData!")
return
}
blogData := bd.GetBlog()
if blogData == nil {
fmt.Println("webrings plugin: blog is nil!")
return
}
blog := blogData.GetBlog()
if blog == "" {
fmt.Println("webrings plugin: blog is empty!")
return
}
if blogWebringsAny, ok := p.config[blog]; ok {
if blogWebrings, ok := blogWebringsAny.([]any); ok {
for _, webringAny := range blogWebrings {
if webring, ok := webringAny.(map[string]any); ok {
title, titleOk := unwrapToString(webring["title"])
prev, prevOk := unwrapToString(webring["prev"])
next, nextOk := unwrapToString(webring["next"])
if titleOk && (prevOk || nextOk) {
hb.WriteElementOpen("p")
if prevOk {
hb.WriteElementOpen("a", "href", prev)
hb.WriteEscaped("←")
hb.WriteElementClose("a")
}
hb.WriteEscaped(" " + title + " ")
if nextOk {
hb.WriteElementOpen("a", "href", next)
hb.WriteEscaped("→")
hb.WriteElementClose("a")
}
hb.WriteElementClose("p")
}
}
}
}
}
}
}
func unwrapToString(o any) (string, bool) {
if o == nil {
return "", false
}
s, ok := o.(string)
return s, ok && s != ""
}

50
ui.go

@ -162,32 +162,34 @@ func (a *goBlog) renderBase(hb *htmlbuilder.HtmlBuilder, rd *renderData, title,
}
// Footer
hb.WriteElementOpen("footer")
// Footer menu
if fm, ok := rd.Blog.Menus["footer"]; ok {
hb.WriteElementOpen("nav")
for i, item := range fm.Items {
if i > 0 {
hb.WriteUnescaped(" • ")
a.renderWithPlugins(hb, plugintypes.BlogFooterRenderType, rd.Blog.pluginRenderData(), func(hb *htmlbuilder.HtmlBuilder) {
// Footer menu
if fm, ok := rd.Blog.Menus["footer"]; ok {
hb.WriteElementOpen("nav")
for i, item := range fm.Items {
if i > 0 {
hb.WriteUnescaped(" • ")
}
hb.WriteElementOpen("a", "href", item.Link)
hb.WriteEscaped(a.renderMdTitle(item.Title))
hb.WriteElementClose("a")
}
hb.WriteElementOpen("a", "href", item.Link)
hb.WriteEscaped(a.renderMdTitle(item.Title))
hb.WriteElementClose("a")
hb.WriteElementClose("nav")
}
hb.WriteElementClose("nav")
}
// Copyright
hb.WriteElementOpen("p", "translate", "no")
hb.WriteUnescaped("© ")
hb.WriteEscaped(time.Now().Format("2006"))
hb.WriteUnescaped(" ")
if user != nil && user.Name != "" {
hb.WriteEscaped(user.Name)
} else {
hb.WriteEscaped(renderedBlogTitle)
}
hb.WriteElementClose("p")
// Tor
a.renderTorNotice(hb, rd)
// Copyright
hb.WriteElementOpen("p", "translate", "no")
hb.WriteUnescaped("© ")
hb.WriteEscaped(time.Now().Format("2006"))
hb.WriteUnescaped(" ")
if user != nil && user.Name != "" {
hb.WriteEscaped(user.Name)
} else {
hb.WriteEscaped(renderedBlogTitle)
}
hb.WriteElementClose("p")
// Tor
a.renderTorNotice(hb, rd)
})
hb.WriteElementClose("footer")
// Easter egg
if rd.EasterEgg {

Loading…
Cancel
Save