feat: make home welcome/subtitle configurable and fix settings render
Add home_welcome and home_subtitle fields (zh/en) to site_settings so
the home page heading and subtitle are editable from the admin panel,
falling back to the i18n defaults when empty. Wire them through the
config cache, SetUserContext, DefaultData, and home.html.
Fix the /admin/settings/site page rendering blank: Go templates cannot
invoke methods on an interface{}-wrapped struct, so pre-compute
SiteLogoIsURL in the handler instead of calling .Site.LogoIsURL() in
the template.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ func DefaultData(c *gin.Context) gin.H {
|
|||||||
siteLogoIsURL, _ := c.Get("site_logo_is_url")
|
siteLogoIsURL, _ := c.Get("site_logo_is_url")
|
||||||
siteLogoText, _ := c.Get("site_logo_text")
|
siteLogoText, _ := c.Get("site_logo_text")
|
||||||
siteHeaderText, _ := c.Get("site_header_text")
|
siteHeaderText, _ := c.Get("site_header_text")
|
||||||
|
siteHomeWelcome, _ := c.Get("site_home_welcome")
|
||||||
|
siteHomeSubtitle, _ := c.Get("site_home_subtitle")
|
||||||
siteFooterText, _ := c.Get("site_footer_text")
|
siteFooterText, _ := c.Get("site_footer_text")
|
||||||
|
|
||||||
return gin.H{
|
return gin.H{
|
||||||
@@ -37,6 +39,8 @@ func DefaultData(c *gin.Context) gin.H {
|
|||||||
"SiteLogoIsURL": siteLogoIsURL,
|
"SiteLogoIsURL": siteLogoIsURL,
|
||||||
"SiteLogoText": siteLogoText,
|
"SiteLogoText": siteLogoText,
|
||||||
"SiteHeaderText": siteHeaderText,
|
"SiteHeaderText": siteHeaderText,
|
||||||
|
"SiteHomeWelcome": siteHomeWelcome,
|
||||||
|
"SiteHomeSubtitle": siteHomeSubtitle,
|
||||||
"SiteFooterText": siteFooterText,
|
"SiteFooterText": siteFooterText,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func SiteSettingsPage(db *gorm.DB) gin.HandlerFunc {
|
|||||||
data := DefaultData(c)
|
data := DefaultData(c)
|
||||||
data["Title"] = tr["settings_site_title"]
|
data["Title"] = tr["settings_site_title"]
|
||||||
data["Site"] = s
|
data["Site"] = s
|
||||||
|
// Pre-compute derived values so the template never invokes methods on
|
||||||
|
// an interface{}-wrapped struct (which Go templates cannot resolve).
|
||||||
|
data["SiteLogoIsURL"] = s.LogoIsURL()
|
||||||
if msg := c.Query("saved"); msg == "1" {
|
if msg := c.Query("saved"); msg == "1" {
|
||||||
data["Success"] = tr["settings_saved"]
|
data["Success"] = tr["settings_saved"]
|
||||||
}
|
}
|
||||||
@@ -78,6 +81,10 @@ func SiteSettingsSave(db *gorm.DB, storagePath string) gin.HandlerFunc {
|
|||||||
s.LogoTextEn = strings.TrimSpace(c.PostForm("logo_text_en"))
|
s.LogoTextEn = strings.TrimSpace(c.PostForm("logo_text_en"))
|
||||||
s.HeaderTextZh = strings.TrimSpace(c.PostForm("header_text_zh"))
|
s.HeaderTextZh = strings.TrimSpace(c.PostForm("header_text_zh"))
|
||||||
s.HeaderTextEn = strings.TrimSpace(c.PostForm("header_text_en"))
|
s.HeaderTextEn = strings.TrimSpace(c.PostForm("header_text_en"))
|
||||||
|
s.HomeWelcomeZh = strings.TrimSpace(c.PostForm("home_welcome_zh"))
|
||||||
|
s.HomeWelcomeEn = strings.TrimSpace(c.PostForm("home_welcome_en"))
|
||||||
|
s.HomeSubtitleZh = strings.TrimSpace(c.PostForm("home_subtitle_zh"))
|
||||||
|
s.HomeSubtitleEn = strings.TrimSpace(c.PostForm("home_subtitle_en"))
|
||||||
s.FooterTextZh = strings.TrimSpace(c.PostForm("footer_text_zh"))
|
s.FooterTextZh = strings.TrimSpace(c.PostForm("footer_text_zh"))
|
||||||
s.FooterTextEn = strings.TrimSpace(c.PostForm("footer_text_en"))
|
s.FooterTextEn = strings.TrimSpace(c.PostForm("footer_text_en"))
|
||||||
s.UpdatedBy = userIDFromSession(c)
|
s.UpdatedBy = userIDFromSession(c)
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ var translations = map[Lang]map[string]string{
|
|||||||
"settings_logo_text_en": "Site Title (English)",
|
"settings_logo_text_en": "Site Title (English)",
|
||||||
"settings_header_text_zh": "Header Banner Text (Chinese)",
|
"settings_header_text_zh": "Header Banner Text (Chinese)",
|
||||||
"settings_header_text_en": "Header Banner Text (English)",
|
"settings_header_text_en": "Header Banner Text (English)",
|
||||||
|
"settings_home_welcome_zh": "Home Welcome Heading (Chinese)",
|
||||||
|
"settings_home_welcome_en": "Home Welcome Heading (English)",
|
||||||
|
"settings_home_subtitle_zh": "Home Subtitle (Chinese)",
|
||||||
|
"settings_home_subtitle_en": "Home Subtitle (English)",
|
||||||
"settings_footer_text_zh": "Footer Text (Chinese)",
|
"settings_footer_text_zh": "Footer Text (Chinese)",
|
||||||
"settings_footer_text_en": "Footer Text (English)",
|
"settings_footer_text_en": "Footer Text (English)",
|
||||||
"settings_leave_blank": "Leave blank to use the built-in default.",
|
"settings_leave_blank": "Leave blank to use the built-in default.",
|
||||||
@@ -318,6 +322,10 @@ var translations = map[Lang]map[string]string{
|
|||||||
"settings_logo_text_en": "站点标题(英文)",
|
"settings_logo_text_en": "站点标题(英文)",
|
||||||
"settings_header_text_zh": "顶部横幅文本(中文)",
|
"settings_header_text_zh": "顶部横幅文本(中文)",
|
||||||
"settings_header_text_en": "顶部横幅文本(英文)",
|
"settings_header_text_en": "顶部横幅文本(英文)",
|
||||||
|
"settings_home_welcome_zh": "主页欢迎标题(中文)",
|
||||||
|
"settings_home_welcome_en": "主页欢迎标题(英文)",
|
||||||
|
"settings_home_subtitle_zh": "主页副标题(中文)",
|
||||||
|
"settings_home_subtitle_en": "主页副标题(英文)",
|
||||||
"settings_footer_text_zh": "页脚文本(中文)",
|
"settings_footer_text_zh": "页脚文本(中文)",
|
||||||
"settings_footer_text_en": "页脚文本(英文)",
|
"settings_footer_text_en": "页脚文本(英文)",
|
||||||
"settings_leave_blank": "留空则使用内置默认值。",
|
"settings_leave_blank": "留空则使用内置默认值。",
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ func SetUserContext(db *gorm.DB) gin.HandlerFunc {
|
|||||||
c.Set("site_logo_is_url", site.LogoIsURL())
|
c.Set("site_logo_is_url", site.LogoIsURL())
|
||||||
c.Set("site_logo_text", site.LogoText(string(lang)))
|
c.Set("site_logo_text", site.LogoText(string(lang)))
|
||||||
c.Set("site_header_text", site.HeaderText(string(lang)))
|
c.Set("site_header_text", site.HeaderText(string(lang)))
|
||||||
|
c.Set("site_home_welcome", site.HomeWelcome(string(lang)))
|
||||||
|
c.Set("site_home_subtitle", site.HomeSubtitle(string(lang)))
|
||||||
c.Set("site_footer_text", site.FooterText(string(lang)))
|
c.Set("site_footer_text", site.FooterText(string(lang)))
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ type SiteSetting struct {
|
|||||||
LogoTextEn string `gorm:"size:255" json:"logo_text_en"` // top-left title (en)
|
LogoTextEn string `gorm:"size:255" json:"logo_text_en"` // top-left title (en)
|
||||||
HeaderTextZh string `gorm:"size:512" json:"header_text_zh"` // header banner text (zh)
|
HeaderTextZh string `gorm:"size:512" json:"header_text_zh"` // header banner text (zh)
|
||||||
HeaderTextEn string `gorm:"size:512" json:"header_text_en"` // header banner text (en)
|
HeaderTextEn string `gorm:"size:512" json:"header_text_en"` // header banner text (en)
|
||||||
|
HomeWelcomeZh string `gorm:"size:255" json:"home_welcome_zh"` // home page welcome heading (zh)
|
||||||
|
HomeWelcomeEn string `gorm:"size:255" json:"home_welcome_en"` // home page welcome heading (en)
|
||||||
|
HomeSubtitleZh string `gorm:"size:512" json:"home_subtitle_zh"` // home page subtitle (zh)
|
||||||
|
HomeSubtitleEn string `gorm:"size:512" json:"home_subtitle_en"` // home page subtitle (en)
|
||||||
FooterTextZh string `gorm:"size:512" json:"footer_text_zh"` // footer text (zh)
|
FooterTextZh string `gorm:"size:512" json:"footer_text_zh"` // footer text (zh)
|
||||||
FooterTextEn string `gorm:"size:512" json:"footer_text_en"` // footer text (en)
|
FooterTextEn string `gorm:"size:512" json:"footer_text_en"` // footer text (en)
|
||||||
UpdatedBy uint `gorm:"index" json:"updated_by"`
|
UpdatedBy uint `gorm:"index" json:"updated_by"`
|
||||||
@@ -62,6 +66,36 @@ func (s *SiteSetting) HeaderText(lang string) string {
|
|||||||
return s.HeaderTextZh
|
return s.HeaderTextZh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HomeWelcome returns the home page welcome heading for the given language,
|
||||||
|
// falling back to the other language when the requested one is empty.
|
||||||
|
func (s *SiteSetting) HomeWelcome(lang string) string {
|
||||||
|
if lang == "zh" {
|
||||||
|
if s.HomeWelcomeZh != "" {
|
||||||
|
return s.HomeWelcomeZh
|
||||||
|
}
|
||||||
|
return s.HomeWelcomeEn
|
||||||
|
}
|
||||||
|
if s.HomeWelcomeEn != "" {
|
||||||
|
return s.HomeWelcomeEn
|
||||||
|
}
|
||||||
|
return s.HomeWelcomeZh
|
||||||
|
}
|
||||||
|
|
||||||
|
// HomeSubtitle returns the home page subtitle for the given language,
|
||||||
|
// falling back to the other language when the requested one is empty.
|
||||||
|
func (s *SiteSetting) HomeSubtitle(lang string) string {
|
||||||
|
if lang == "zh" {
|
||||||
|
if s.HomeSubtitleZh != "" {
|
||||||
|
return s.HomeSubtitleZh
|
||||||
|
}
|
||||||
|
return s.HomeSubtitleEn
|
||||||
|
}
|
||||||
|
if s.HomeSubtitleEn != "" {
|
||||||
|
return s.HomeSubtitleEn
|
||||||
|
}
|
||||||
|
return s.HomeSubtitleZh
|
||||||
|
}
|
||||||
|
|
||||||
// FooterText returns the footer text for the given language code, falling
|
// FooterText returns the footer text for the given language code, falling
|
||||||
// back to the other language when the requested one is empty.
|
// back to the other language when the requested one is empty.
|
||||||
func (s *SiteSetting) FooterText(lang string) string {
|
func (s *SiteSetting) FooterText(lang string) string {
|
||||||
|
|||||||
@@ -19,14 +19,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2">{{index .Tr "settings_logo"}}</label>
|
<label class="block text-sm font-semibold text-gray-700 mb-2">{{index .Tr "settings_logo"}}</label>
|
||||||
{{if .Site.Logo}}
|
{{if .Site.Logo}}
|
||||||
{{if .Site.LogoIsURL}}
|
{{if .SiteLogoIsURL}}
|
||||||
<img src="{{.Site.Logo}}" alt="logo" class="h-12 w-auto mb-2">
|
<img src="{{.Site.Logo}}" alt="logo" class="h-12 w-auto mb-2">
|
||||||
{{else}}
|
{{else}}
|
||||||
<img src="/uploads/logos/{{.Site.Logo}}" alt="logo" class="h-12 w-auto mb-2">
|
<img src="/uploads/logos/{{.Site.Logo}}" alt="logo" class="h-12 w-auto mb-2">
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<input type="text" name="logo_url" placeholder="{{index .Tr "settings_logo_url"}}"
|
<input type="text" name="logo_url" placeholder="{{index .Tr "settings_logo_url"}}"
|
||||||
class="w-full border border-gray-300 rounded-lg px-3 py-2 mb-2" value="{{if .Site.LogoIsURL}}{{.Site.Logo}}{{end}}">
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 mb-2" value="{{if .SiteLogoIsURL}}{{.Site.Logo}}{{end}}">
|
||||||
<input type="file" name="logo" accept="image/*"
|
<input type="file" name="logo" accept="image/*"
|
||||||
class="block w-full text-sm text-gray-500 mb-2">
|
class="block w-full text-sm text-gray-500 mb-2">
|
||||||
<label class="inline-flex items-center gap-2 text-sm text-gray-600">
|
<label class="inline-flex items-center gap-2 text-sm text-gray-600">
|
||||||
@@ -62,6 +62,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Home welcome & subtitle -->
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-1">{{index .Tr "settings_home_welcome_zh"}}</label>
|
||||||
|
<input type="text" name="home_welcome_zh" value="{{.Site.HomeWelcomeZh}}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-3 py-2">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-1">{{index .Tr "settings_home_welcome_en"}}</label>
|
||||||
|
<input type="text" name="home_welcome_en" value="{{.Site.HomeWelcomeEn}}"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-3 py-2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-1">{{index .Tr "settings_home_subtitle_zh"}}</label>
|
||||||
|
<textarea name="home_subtitle_zh" rows="2"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-3 py-2">{{.Site.HomeSubtitleZh}}</textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-1">{{index .Tr "settings_home_subtitle_en"}}</label>
|
||||||
|
<textarea name="home_subtitle_en" rows="2"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-3 py-2">{{.Site.HomeSubtitleEn}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Footer texts -->
|
<!-- Footer texts -->
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
{{template "header" .}}
|
{{template "header" .}}
|
||||||
<section class="max-w-5xl mx-auto px-4 py-20 text-center">
|
<section class="max-w-5xl mx-auto px-4 py-20 text-center">
|
||||||
<h1 class="text-5xl font-extrabold text-gray-900 mb-4">
|
<h1 class="text-5xl font-extrabold text-gray-900 mb-4">
|
||||||
{{index .Tr "home_welcome"}}
|
{{if .SiteHomeWelcome}}{{.SiteHomeWelcome}}{{else}}{{index .Tr "home_welcome"}}{{end}}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
||||||
{{index .Tr "home_subtitle"}}
|
{{if .SiteHomeSubtitle}}{{.SiteHomeSubtitle}}{{else}}{{index .Tr "home_subtitle"}}{{end}}
|
||||||
</p>
|
</p>
|
||||||
<!-- <div class="flex justify-center gap-4">
|
<!-- <div class="flex justify-center gap-4">
|
||||||
<a href="/login" class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors">
|
<a href="/login" class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors">
|
||||||
|
|||||||
Reference in New Issue
Block a user