diff --git a/modules/pages/config.go b/modules/pages/config.go
index c6056be35f..dda118768a 100644
--- a/modules/pages/config.go
+++ b/modules/pages/config.go
@@ -44,6 +44,9 @@ type LandingConfig struct {
// CTA section
CTASection CTASectionConfig `yaml:"cta_section,omitempty"`
+ // Blog section
+ Blog BlogSectionConfig `yaml:"blog,omitempty"`
+
// Footer
Footer FooterConfig `yaml:"footer,omitempty"`
@@ -62,9 +65,10 @@ type LandingConfig struct {
// BrandConfig represents brand/identity settings
type BrandConfig struct {
- Name string `yaml:"name,omitempty"`
- LogoURL string `yaml:"logo_url,omitempty"`
- Tagline string `yaml:"tagline,omitempty"`
+ Name string `yaml:"name,omitempty"`
+ LogoURL string `yaml:"logo_url,omitempty"`
+ LogoSource string `yaml:"logo_source,omitempty"` // "url" (default), "repo", or "org" — selects avatar source
+ Tagline string `yaml:"tagline,omitempty"`
}
// HeroConfig represents hero section settings
@@ -145,6 +149,16 @@ type CTASectionConfig struct {
Button CTAButton `yaml:"button,omitempty"`
}
+// BlogSectionConfig represents blog section settings on the landing page
+type BlogSectionConfig struct {
+ Enabled bool `yaml:"enabled,omitempty"`
+ Headline string `yaml:"headline,omitempty"`
+ Subheadline string `yaml:"subheadline,omitempty"`
+ MaxPosts int `yaml:"max_posts,omitempty"` // default 3
+ ShowExcerpt bool `yaml:"show_excerpt,omitempty"` // show subtitle as excerpt
+ CTAButton CTAButton `yaml:"cta_button,omitempty"` // "View All Posts" link
+}
+
// FooterConfig represents footer settings
type FooterConfig struct {
Links []FooterLink `yaml:"links,omitempty"`
diff --git a/routers/web/pages/pages.go b/routers/web/pages/pages.go
index f5d3cfdaa8..828fe37608 100644
--- a/routers/web/pages/pages.go
+++ b/routers/web/pages/pages.go
@@ -11,6 +11,7 @@ import (
"strings"
"time"
+ blog_model "code.gitcaddy.com/server/v3/models/blog"
"code.gitcaddy.com/server/v3/models/renderhelper"
repo_model "code.gitcaddy.com/server/v3/models/repo"
"code.gitcaddy.com/server/v3/modules/git"
@@ -106,6 +107,12 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
ctx.Data["Title"] = getPageTitle(repo, config)
ctx.Data["PageIsPagesLanding"] = true
+ // Provide absolute repo URL for links on custom domains
+ ctx.Data["RepoURL"] = repo.HTMLURL()
+
+ // Resolve logo URL based on logo_source config
+ ctx.Data["LogoURL"] = resolveLogoURL(ctx, repo, config)
+
// Load README content
readme, err := loadReadmeContent(ctx, repo)
if err != nil {
@@ -123,15 +130,54 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
if err == nil && release != nil {
_ = repo_model.GetReleaseAttachments(ctx, release)
ctx.Data["LatestRelease"] = release
+ // Provide tag name with leading "v" stripped to avoid double "v" in templates
+ ctx.Data["LatestReleaseTag"] = strings.TrimPrefix(release.TagName, "v")
}
ctx.Data["PublicReleases"] = config.Advanced.PublicReleases
+ // Load recent blog posts if blog section is enabled
+ if config.Blog.Enabled && repo.BlogEnabled {
+ maxPosts := config.Blog.MaxPosts
+ if maxPosts <= 0 {
+ maxPosts = 3
+ }
+ posts, _, err := blog_model.GetBlogPostsByRepoID(ctx, &blog_model.BlogPostSearchOptions{
+ RepoID: repo.ID,
+ AnyPublicStatus: true,
+ Page: 1,
+ PageSize: maxPosts,
+ })
+ if err == nil && len(posts) > 0 {
+ for _, post := range posts {
+ _ = post.LoadAuthor(ctx)
+ _ = post.LoadFeaturedImage(ctx)
+ }
+ ctx.Data["BlogPosts"] = posts
+ }
+ }
+
// Select template based on config
tpl := selectTemplate(config.Template)
ctx.HTML(http.StatusOK, tpl)
}
+// resolveLogoURL determines the logo URL based on the brand.logo_source config
+func resolveLogoURL(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) string {
+ switch config.Brand.LogoSource {
+ case "repo":
+ return repo.AvatarLink(ctx)
+ case "org":
+ if err := repo.LoadOwner(ctx); err != nil {
+ log.Warn("Failed to load repo owner for logo: %v", err)
+ return ""
+ }
+ return repo.Owner.AvatarLink(ctx)
+ default: // "url" or empty
+ return config.Brand.LogoURL
+ }
+}
+
// getPageTitle returns the page title
func getPageTitle(repo *repo_model.Repository, config *pages_module.LandingConfig) string {
if config.SEO.Title != "" {
diff --git a/templates/pages/bold-marketing.tmpl b/templates/pages/bold-marketing.tmpl
index ea9b892faa..1896fe4ca2 100644
--- a/templates/pages/bold-marketing.tmpl
+++ b/templates/pages/bold-marketing.tmpl
@@ -1113,7 +1113,8 @@
{{if .Config.ValueProps}}Why{{end}}
{{if .Config.Features}}Features{{end}}
{{if .Config.Pricing.Plans}}Pricing{{end}}
-
+ {{if and .Config.Blog.Enabled .BlogPosts}}Blog{{end}}
+
Repo
@@ -1134,7 +1135,8 @@
{{if .Config.ValueProps}}Why{{end}}
{{if .Config.Features}}Features{{end}}
{{if .Config.Pricing.Plans}}Pricing{{end}}
-
+ {{if and .Config.Blog.Enabled .BlogPosts}}Blog{{end}}
+
Repository
@@ -1188,12 +1190,12 @@
Get the latest release
{{.Config.Blog.Subheadline}}
{{end}} +Get the latest release
{{.Config.Blog.Subheadline}}
{{end}} + {{range .BlogPosts}} + + {{if .FeaturedImage}} + + {{end}} +