Skip to content

Commit 686f2fa

Browse files
compscidrclaude
andcommitted
Extract social icons into a plugin
Move social URL settings out of the core settings table and hardcoded footer templates into a socialicons plugin. Plugin (plugins/socialicons/): - Defines settings for 10 social platforms (GitHub, LinkedIn, X, etc.) - TemplateFooter renders icon links with Font Awesome - TemplateData provides structured links list for themes that want it - Enabled by default Migration: - Moves existing social URL values from settings to plugin_settings - Removes the social URL keys from the main settings table - Sets socialicons.enabled to true for existing installs Footer templates: - Removed hardcoded social icon blocks from all three themes - Social icons now rendered via {{ .plugin_footer_html }} Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 73585fb commit 686f2fa

6 files changed

Lines changed: 147 additions & 109 deletions

File tree

goblog.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"goblog/blog"
1212
gplugin "goblog/plugin"
1313
"goblog/plugins/analytics"
14+
"goblog/plugins/socialicons"
1415
"goblog/tools"
1516
"goblog/wizard"
1617
"gorm.io/driver/mysql"
@@ -311,6 +312,7 @@ func main() {
311312
// Initialize plugin system
312313
registry := gplugin.NewRegistry(db)
313314
registry.Register(analytics.New())
315+
registry.Register(socialicons.New())
314316
gplugin.LoadDynamicPlugins(registry, "plugins/dynamic")
315317
if db != nil {
316318
registry.Init()

plugins/socialicons/socialicons.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Package socialicons provides a plugin that renders social media icon links.
2+
// When enabled, it injects social icon HTML into the template footer,
3+
// replacing the need for hardcoded social URLs in theme templates.
4+
package socialicons
5+
6+
import (
7+
"goblog/plugin"
8+
9+
"github.com/gin-gonic/gin"
10+
"gorm.io/gorm"
11+
)
12+
13+
type social struct {
14+
key string // setting key
15+
label string // display label
16+
icon string // Font Awesome icon class
17+
}
18+
19+
var socials = []social{
20+
{"github_url", "GitHub", "fab fa-github"},
21+
{"linkedin_url", "LinkedIn", "fab fa-linkedin"},
22+
{"x_url", "X", "fab fa-x"},
23+
{"keybase_url", "Keybase", "fab fa-keybase"},
24+
{"instagram_url", "Instagram", "fab fa-instagram"},
25+
{"facebook_url", "Facebook", "fab fa-facebook"},
26+
{"strava_url", "Strava", "fab fa-strava"},
27+
{"spotify_url", "Spotify", "fab fa-spotify"},
28+
{"xbox_url", "Xbox", "fab fa-xbox"},
29+
{"steam_url", "Steam", "fab fa-steam"},
30+
}
31+
32+
// SocialIconsPlugin renders social media icon links in the footer.
33+
type SocialIconsPlugin struct {
34+
plugin.BasePlugin
35+
}
36+
37+
// New creates a new social icons plugin.
38+
func New() *SocialIconsPlugin {
39+
return &SocialIconsPlugin{}
40+
}
41+
42+
func (p *SocialIconsPlugin) Name() string { return "socialicons" }
43+
func (p *SocialIconsPlugin) DisplayName() string { return "Social Icons" }
44+
func (p *SocialIconsPlugin) Version() string { return "1.0.0" }
45+
46+
func (p *SocialIconsPlugin) OnInit(db *gorm.DB) error { return nil }
47+
48+
func (p *SocialIconsPlugin) Settings() []plugin.SettingDefinition {
49+
defs := []plugin.SettingDefinition{
50+
{Key: "enabled", Type: "text", DefaultValue: "true", Label: "Enabled", Description: "Set to 'true' to show social icons"},
51+
}
52+
for _, s := range socials {
53+
defs = append(defs, plugin.SettingDefinition{
54+
Key: s.key,
55+
Type: "text",
56+
DefaultValue: "",
57+
Label: s.label + " URL",
58+
Description: "Full URL to your " + s.label + " profile",
59+
})
60+
}
61+
return defs
62+
}
63+
64+
func (p *SocialIconsPlugin) TemplateData(ctx *plugin.HookContext) gin.H {
65+
if ctx.Settings["enabled"] != "true" {
66+
return nil
67+
}
68+
// Build a list of active social links for templates that want structured data
69+
type socialLink struct {
70+
Name string
71+
URL string
72+
Icon string
73+
}
74+
var links []socialLink
75+
for _, s := range socials {
76+
if url := ctx.Settings[s.key]; url != "" {
77+
links = append(links, socialLink{Name: s.label, URL: url, Icon: s.icon})
78+
}
79+
}
80+
return gin.H{"links": links}
81+
}
82+
83+
func (p *SocialIconsPlugin) TemplateFooter(ctx *plugin.HookContext) string {
84+
if ctx.Settings["enabled"] != "true" {
85+
return ""
86+
}
87+
html := `<div class="text-center" style="padding: 10px 0;">`
88+
for _, s := range socials {
89+
url := ctx.Settings[s.key]
90+
if url == "" {
91+
continue
92+
}
93+
html += `<a href="` + url + `" target="_blank" rel="noopener noreferrer" title="` + s.label + `" style="margin: 0 6px; color: inherit;"><i class="` + s.icon + ` fa-1x"></i></a>`
94+
}
95+
html += `</div>`
96+
return html
97+
}

themes/default/templates/footer.html

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,6 @@
11
<div id="footer" class="text-center">
2-
<div class="footer">
3-
{{ if .settings.github_url.Value }}
4-
<a href="{{ .settings.github_url.Value }}" target="_blank" title="{{ .settings.site_title.Value }} on Github"><i class="fab fa-github fa-1x"></i></a>
5-
{{ end }}
6-
{{ if .settings.linkedin_url.Value }}
7-
<a href="{{ .settings.linkedin_url.Value }}" target="_blank" title="{{ .settings.site_title.Value }} on LinkedIn"><i class="fab fa-linkedin fa-1x"></i></a>
8-
{{ end }}
9-
{{ if .settings.x_url.Value }}
10-
<a href="{{ .settings.x_url.Value }}" target="_blank" title="{{ .settings.site_title.Value }} on X"><i class="fab fa-x fa-1x"></i></a>
11-
{{ end }}
12-
{{ if .settings.keybase_url.Value }}
13-
<a href="{{ .settings.keybase_url.Value }}" target="_blank" title="{{ .settings.site_title.Value }} on Keybase"><i class="fab fa-keybase fa-1x"></i></a>
14-
{{ end }}
15-
{{ if .settings.instagram_url.Value }}
16-
<a href="https://www.instagram.com/compscidr/" target="_blank" title="Jason Ernst on Instagram"><i class="fab fa-instagram fa-1x"></i></a>
17-
{{ end }}
18-
{{ if .settings.facebook_url.Value }}
19-
<a href="{{ .settings.facebook_url.Value }}" target="_blank" title="Jason Ernst on Facebook"><i class="fab fa-facebook fa-1x"></i></a>
20-
{{ end }}
21-
{{ if .settings.strava_url.Value }}
22-
<a href="{{ .settings.strava_url.Value }}" target="_blank" title="Jason Ernst on Strava"><i class="fab fa-strava fa-1x"></i></a>
23-
{{ end }}
24-
{{ if .settings.spotify_url.Value }}
25-
<a href="{{ .settings.spotify_url.Value }}" target="_blank" title="Jason Ernst on Spotify"><i class="fab fa-spotify fa-1x"></i></a>
26-
{{ end }}
27-
{{ if .settings.xbox_url.Value }}
28-
<a href="{{ .settings.xbox_url.Value }}" target="_blank" title="Jason Ernst on Xbox"><i class="fab fa-xbox fa-1x"></i></a>
29-
{{ end }}
30-
{{ if .settings.steam_url.Value }}
31-
<a href="{{ .settings.steam_url.Value }}" target="_blank" title="Jason Ernst on Steam"><i class="fab fa-steam fa-1x"></i></a>
32-
{{ end }}
33-
</div> <!-- /footer -->
2+
{{ with .plugin_footer_html }}{{ . | rawHTML }}{{ end }}
3+
<div class="footer"></div>
344
<div class="spacer version">Powered by <a href="https://github.com/compscidr/goblog" target="goblog {{ .version }}">goblog {{ .version }}</a></div>
355
</div>
366
</div> <!-- / wrapper -->

themes/forest/templates/footer.html

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,7 @@
44
<div style="display: flex; justify-content: space-between; align-items: start; flex-wrap: wrap; gap: 20px;">
55
<div>
66
<p style="font-weight: 600; font-size: 14px; margin-bottom: 8px; color: #d4e4d4;">{{ .settings.site_title.Value }}</p>
7-
<div style="display: flex; gap: 14px; font-size: 16px;">
8-
{{ if .settings.github_url.Value }}
9-
<a href="{{ .settings.github_url.Value }}" target="_blank" rel="noopener noreferrer" title="Github" style="color: #8bc48b;"><i class="fab fa-github"></i></a>
10-
{{ end }}
11-
{{ if .settings.linkedin_url.Value }}
12-
<a href="{{ .settings.linkedin_url.Value }}" target="_blank" rel="noopener noreferrer" title="LinkedIn" style="color: #8bc48b;"><i class="fab fa-linkedin"></i></a>
13-
{{ end }}
14-
{{ if .settings.x_url.Value }}
15-
<a href="{{ .settings.x_url.Value }}" target="_blank" rel="noopener noreferrer" title="X" style="color: #8bc48b;"><i class="fab fa-x"></i></a>
16-
{{ end }}
17-
{{ if .settings.keybase_url.Value }}
18-
<a href="{{ .settings.keybase_url.Value }}" target="_blank" rel="noopener noreferrer" title="Keybase" style="color: #8bc48b;"><i class="fab fa-keybase"></i></a>
19-
{{ end }}
20-
{{ if .settings.instagram_url.Value }}
21-
<a href="{{ .settings.instagram_url.Value }}" target="_blank" rel="noopener noreferrer" title="Instagram" style="color: #8bc48b;"><i class="fab fa-instagram"></i></a>
22-
{{ end }}
23-
{{ if .settings.facebook_url.Value }}
24-
<a href="{{ .settings.facebook_url.Value }}" target="_blank" rel="noopener noreferrer" title="Facebook" style="color: #8bc48b;"><i class="fab fa-facebook"></i></a>
25-
{{ end }}
26-
{{ if .settings.strava_url.Value }}
27-
<a href="{{ .settings.strava_url.Value }}" target="_blank" rel="noopener noreferrer" title="Strava" style="color: #8bc48b;"><i class="fab fa-strava"></i></a>
28-
{{ end }}
29-
{{ if .settings.spotify_url.Value }}
30-
<a href="{{ .settings.spotify_url.Value }}" target="_blank" rel="noopener noreferrer" title="Spotify" style="color: #8bc48b;"><i class="fab fa-spotify"></i></a>
31-
{{ end }}
32-
{{ if .settings.xbox_url.Value }}
33-
<a href="{{ .settings.xbox_url.Value }}" target="_blank" rel="noopener noreferrer" title="Xbox" style="color: #8bc48b;"><i class="fab fa-xbox"></i></a>
34-
{{ end }}
35-
{{ if .settings.steam_url.Value }}
36-
<a href="{{ .settings.steam_url.Value }}" target="_blank" rel="noopener noreferrer" title="Steam" style="color: #8bc48b;"><i class="fab fa-steam"></i></a>
37-
{{ end }}
38-
</div>
7+
{{ with .plugin_footer_html }}{{ . | rawHTML }}{{ end }}
398
</div>
409
<p style="color: rgba(255,255,255,0.4); font-size: 11px; margin: 0;">Powered by <a href="https://github.com/goblogplatform/goblog" style="color: rgba(255,255,255,0.4);">goblog {{ .version }}</a></p>
4110
</div>
@@ -47,5 +16,4 @@
4716
{{ with index .settings "custom_footer_code" }}{{ if .Value }}
4817
{{ .Value | rawHTML }}
4918
{{ end }}{{ end }}
50-
{{ with .plugin_footer_html }}{{ . | rawHTML }}{{ end }}
5119
</body>

themes/minimal/templates/footer.html

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,9 @@
44
<div style="display: flex; justify-content: space-between; align-items: start; flex-wrap: wrap; gap: 20px;">
55
<div>
66
<p style="font-weight: 600; font-size: 14px; margin-bottom: 8px; color: #333;">{{ .settings.site_title.Value }}</p>
7-
<div style="display: flex; gap: 12px; font-size: 16px;">
8-
{{ if .settings.github_url.Value }}
9-
<a href="{{ .settings.github_url.Value }}" target="_blank" title="Github" style="color: #64748b;"><i class="fab fa-github"></i></a>
10-
{{ end }}
11-
{{ if .settings.linkedin_url.Value }}
12-
<a href="{{ .settings.linkedin_url.Value }}" target="_blank" title="LinkedIn" style="color: #64748b;"><i class="fab fa-linkedin"></i></a>
13-
{{ end }}
14-
{{ if .settings.x_url.Value }}
15-
<a href="{{ .settings.x_url.Value }}" target="_blank" title="X" style="color: #64748b;"><i class="fab fa-x"></i></a>
16-
{{ end }}
17-
{{ if .settings.keybase_url.Value }}
18-
<a href="{{ .settings.keybase_url.Value }}" target="_blank" title="Keybase" style="color: #64748b;"><i class="fab fa-keybase"></i></a>
19-
{{ end }}
20-
{{ if .settings.instagram_url.Value }}
21-
<a href="{{ .settings.instagram_url.Value }}" target="_blank" title="Instagram" style="color: #64748b;"><i class="fab fa-instagram"></i></a>
22-
{{ end }}
23-
{{ if .settings.facebook_url.Value }}
24-
<a href="{{ .settings.facebook_url.Value }}" target="_blank" title="Facebook" style="color: #64748b;"><i class="fab fa-facebook"></i></a>
25-
{{ end }}
26-
{{ if .settings.strava_url.Value }}
27-
<a href="{{ .settings.strava_url.Value }}" target="_blank" title="Strava" style="color: #64748b;"><i class="fab fa-strava"></i></a>
28-
{{ end }}
29-
{{ if .settings.spotify_url.Value }}
30-
<a href="{{ .settings.spotify_url.Value }}" target="_blank" title="Spotify" style="color: #64748b;"><i class="fab fa-spotify"></i></a>
31-
{{ end }}
32-
{{ if .settings.xbox_url.Value }}
33-
<a href="{{ .settings.xbox_url.Value }}" target="_blank" title="Xbox" style="color: #64748b;"><i class="fab fa-xbox"></i></a>
34-
{{ end }}
35-
{{ if .settings.steam_url.Value }}
36-
<a href="{{ .settings.steam_url.Value }}" target="_blank" title="Steam" style="color: #64748b;"><i class="fab fa-steam"></i></a>
37-
{{ end }}
38-
</div>
7+
{{ with .plugin_footer_html }}{{ . | rawHTML }}{{ end }}
398
</div>
40-
<p style="color: #94a3b8; font-size: 11px; margin: 0;">Powered by <a href="https://github.com/compscidr/goblog" style="color: #94a3b8;">goblog {{ .version }}</a></p>
9+
<p style="color: #94a3b8; font-size: 11px; margin: 0;">Powered by <a href="https://github.com/goblogplatform/goblog" style="color: #94a3b8;">goblog {{ .version }}</a></p>
4110
</div>
4211
</div>
4312
</div>
@@ -47,5 +16,4 @@
4716
{{ with index .settings "custom_footer_code" }}{{ if .Value }}
4817
{{ .Value | rawHTML }}
4918
{{ end }}{{ end }}
50-
{{ with .plugin_footer_html }}{{ . | rawHTML }}{{ end }}
5119
</body>

tools/migrate.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"goblog/auth"
88
"goblog/blog"
9+
gplugin "goblog/plugin"
910
"gorm.io/gorm"
1011
"log"
1112
"regexp"
@@ -272,16 +273,6 @@ func seedDefaultSettings(db *gorm.DB) {
272273
{Key: "site_tags", Type: "text", Value: "Decentralization, Mesh Net"},
273274
{Key: "landing_page_image", Type: "file", Value: "/img/profile.png"},
274275
{Key: "favicon", Type: "file", Value: "/img/favicon.ico"},
275-
{Key: "github_url", Type: "text", Value: "https://www.github.com/compscidr"},
276-
{Key: "linkedin_url", Type: "text", Value: "https://www.linkedin.com/in/jasonernst/"},
277-
{Key: "x_url", Type: "text", Value: "https://www.x.com/compscidr"},
278-
{Key: "keybase_url", Type: "text", Value: "https://keybase.io/compscidr"},
279-
{Key: "instagram_url", Type: "text", Value: "https://www.instagram.com/compscidr"},
280-
{Key: "facebook_url", Type: "text", Value: "https://www.facebook.com/jason.b.ernst"},
281-
{Key: "strava_url", Type: "text", Value: "https://www.strava.com/athletes/2021127"},
282-
{Key: "spotify_url", Type: "text", Value: "https://open.spotify.com/user/csgrad"},
283-
{Key: "xbox_url", Type: "text", Value: "https://account.xbox.com/en-us/profile?gamertag=Compscidr"},
284-
{Key: "steam_url", Type: "text", Value: "https://steamcommunity.com/id/compscidr"},
285276
{Key: "custom_header_code", Type: "textarea", Value: ""},
286277
{Key: "custom_footer_code", Type: "textarea", Value: ""},
287278
{Key: "theme", Type: "text", Value: "default"},
@@ -379,11 +370,53 @@ func Migrate(db *gorm.DB) error {
379370
seedDefaultPages(db)
380371
linkWritingPagesToPostType(db)
381372
cleanupEmptyTags(db)
373+
migrateSocialURLsToPlugin(db)
382374
cleanupPluginSettingsFromMainTable(db)
383375

384376
return nil
385377
}
386378

379+
// migrateSocialURLsToPlugin moves social URL settings from the main settings
380+
// table to the plugin_settings table under the "socialicons" plugin.
381+
func migrateSocialURLsToPlugin(db *gorm.DB) {
382+
socialKeys := []string{
383+
"github_url", "linkedin_url", "x_url", "keybase_url",
384+
"instagram_url", "facebook_url", "strava_url", "spotify_url",
385+
"xbox_url", "steam_url",
386+
}
387+
388+
// Ensure plugin_settings table exists
389+
db.AutoMigrate(&gplugin.PluginSetting{})
390+
391+
for _, key := range socialKeys {
392+
var setting blog.Setting
393+
if err := db.Where("key = ?", key).First(&setting).Error; err != nil {
394+
continue // not found, skip
395+
}
396+
if setting.Value == "" {
397+
continue
398+
}
399+
// Migrate to plugin_settings if not already there
400+
ps := gplugin.PluginSetting{
401+
PluginName: "socialicons",
402+
Key: key,
403+
Value: setting.Value,
404+
}
405+
db.Where("plugin_name = ? AND key = ?", "socialicons", key).FirstOrCreate(&ps)
406+
}
407+
408+
// Also ensure the enabled setting exists
409+
db.Where("plugin_name = ? AND key = ?", "socialicons", "enabled").
410+
FirstOrCreate(&gplugin.PluginSetting{
411+
PluginName: "socialicons",
412+
Key: "enabled",
413+
Value: "true",
414+
})
415+
416+
// Remove migrated keys from main settings table
417+
db.Where("key IN ?", socialKeys).Delete(&blog.Setting{})
418+
}
419+
387420
// cleanupPluginSettingsFromMainTable removes any dot-namespaced keys
388421
// from the main settings table that belong in plugin_settings instead.
389422
func cleanupPluginSettingsFromMainTable(db *gorm.DB) {

0 commit comments

Comments
 (0)