2
0

feat(pages): add gallery section to landing page templates

Add configurable gallery section that displays images from repository's .gallery folder on landing pages. Includes settings for enabled/disabled, headline, subheadline, max images (default 6), and grid columns (default 3). Reads captions from gallery.json metadata. Implements gallery rendering in all four page templates (bold-marketing, minimalist-docs, open-source-hero, saas-conversion). Integrates with existing gallery management feature.
This commit is contained in:
2026-03-15 22:54:11 -04:00
parent fc86952bf4
commit 679810687f
10 changed files with 263 additions and 0 deletions

View File

@@ -47,6 +47,9 @@ type LandingConfig struct {
// Blog section // Blog section
Blog BlogSectionConfig `yaml:"blog,omitempty"` Blog BlogSectionConfig `yaml:"blog,omitempty"`
// Gallery section
Gallery GallerySectionConfig `yaml:"gallery,omitempty"`
// Navigation visibility // Navigation visibility
Navigation NavigationConfig `yaml:"navigation,omitempty"` Navigation NavigationConfig `yaml:"navigation,omitempty"`
@@ -196,6 +199,15 @@ type BlogSectionConfig struct {
CTAButton CTAButton `yaml:"cta_button,omitempty"` // "View All Posts" link CTAButton CTAButton `yaml:"cta_button,omitempty"` // "View All Posts" link
} }
// GallerySectionConfig represents gallery section settings on the landing page
type GallerySectionConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
MaxImages int `yaml:"max_images,omitempty"` // default 6
Columns int `yaml:"columns,omitempty"` // grid columns, default 3
}
// NavigationConfig controls which built-in navigation links appear in the header and footer // NavigationConfig controls which built-in navigation links appear in the header and footer
type NavigationConfig struct { type NavigationConfig struct {
ShowDocs bool `yaml:"show_docs,omitempty"` ShowDocs bool `yaml:"show_docs,omitempty"`

View File

@@ -737,6 +737,13 @@
"repo.settings.pages.stats": "Stats", "repo.settings.pages.stats": "Stats",
"repo.settings.pages.value_props": "Value Propositions", "repo.settings.pages.value_props": "Value Propositions",
"repo.settings.pages.features": "Features", "repo.settings.pages.features": "Features",
"repo.settings.pages.gallery_section": "Gallery Section",
"repo.settings.pages.gallery_enabled_desc": "Show a gallery of images from your repository's .gallery folder on the landing page",
"repo.settings.pages.gallery_headline": "Gallery Headline",
"repo.settings.pages.gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.gallery_max_images": "Maximum Images to Show",
"repo.settings.pages.gallery_columns": "Grid Columns",
"repo.settings.pages.gallery_help_link": "Upload and manage gallery images in Settings > Gallery.",
"repo.settings.pages.company_logos": "Company Logos", "repo.settings.pages.company_logos": "Company Logos",
"repo.settings.pages.testimonials": "Testimonials", "repo.settings.pages.testimonials": "Testimonials",
"repo.settings.pages.pricing_headline": "Pricing Headline", "repo.settings.pages.pricing_headline": "Pricing Headline",

View File

@@ -4544,6 +4544,13 @@
"repo.settings.pages.blog_headline": "Blog Headline", "repo.settings.pages.blog_headline": "Blog Headline",
"repo.settings.pages.blog_subheadline": "Blog Subheadline", "repo.settings.pages.blog_subheadline": "Blog Subheadline",
"repo.settings.pages.blog_max_posts": "Maximum Posts to Show", "repo.settings.pages.blog_max_posts": "Maximum Posts to Show",
"repo.settings.pages.gallery_section": "Gallery Section",
"repo.settings.pages.gallery_enabled_desc": "Show a gallery of images from your repository's .gallery folder on the landing page",
"repo.settings.pages.gallery_headline": "Gallery Headline",
"repo.settings.pages.gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.gallery_max_images": "Maximum Images to Show",
"repo.settings.pages.gallery_columns": "Grid Columns",
"repo.settings.pages.gallery_help_link": "Upload and manage gallery images in Settings > Gallery.",
"repo.settings.pages.ai_generate": "AI Content Generator", "repo.settings.pages.ai_generate": "AI Content Generator",
"repo.settings.pages.ai_generate_desc": "Automatically generate landing page content (headline, features, stats, CTAs) from your repository's README and metadata using AI.", "repo.settings.pages.ai_generate_desc": "Automatically generate landing page content (headline, features, stats, CTAs) from your repository's README and metadata using AI.",
"repo.settings.pages.ai_generate_button": "Generate Content with AI", "repo.settings.pages.ai_generate_button": "Generate Content with AI",

View File

@@ -23,6 +23,7 @@ import (
"code.gitcaddy.com/server/v3/models/renderhelper" "code.gitcaddy.com/server/v3/models/renderhelper"
repo_model "code.gitcaddy.com/server/v3/models/repo" repo_model "code.gitcaddy.com/server/v3/models/repo"
"code.gitcaddy.com/server/v3/modules/git" "code.gitcaddy.com/server/v3/modules/git"
"code.gitcaddy.com/server/v3/modules/gitrepo"
"code.gitcaddy.com/server/v3/modules/json" "code.gitcaddy.com/server/v3/modules/json"
"code.gitcaddy.com/server/v3/modules/log" "code.gitcaddy.com/server/v3/modules/log"
"code.gitcaddy.com/server/v3/modules/markup/markdown" "code.gitcaddy.com/server/v3/modules/markup/markdown"
@@ -196,6 +197,14 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
} }
} }
// Load gallery images if gallery section is enabled
if config.Gallery.Enabled {
images := loadGalleryImagesForLanding(ctx, repo, config)
if len(images) > 0 {
ctx.Data["GalleryImages"] = images
}
}
tpl := selectTemplate(config.Template) tpl := selectTemplate(config.Template)
ctx.HTML(http.StatusOK, tpl) ctx.HTML(http.StatusOK, tpl)
} }
@@ -315,6 +324,94 @@ func getPageTitle(repo *repo_model.Repository, config *pages_module.LandingConfi
return repo.Name return repo.Name
} }
// GalleryImageInfo holds gallery image data for landing page templates
type GalleryImageInfo struct {
Name string
Caption string
URL string
}
// loadGalleryImagesForLanding reads gallery images from the .gallery folder
func loadGalleryImagesForLanding(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) []GalleryImageInfo {
if repo.IsEmpty {
return nil
}
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
if err != nil {
return nil
}
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
if err != nil {
return nil
}
galleryEntry, err := commit.GetTreeEntryByPath(".gallery")
if err != nil || !galleryEntry.IsDir() {
return nil
}
// Load metadata for captions
var metadata struct {
Images []struct {
Name string `json:"name"`
Caption string `json:"caption"`
} `json:"images"`
}
if entry, err := commit.GetTreeEntryByPath(".gallery/gallery.json"); err == nil {
if content, err := entry.Blob().GetBlobContent(100000); err == nil {
_ = json.Unmarshal([]byte(content), &metadata)
}
}
captionMap := make(map[string]string)
for _, img := range metadata.Images {
captionMap[img.Name] = img.Caption
}
tree, err := commit.SubTree(".gallery")
if err != nil {
return nil
}
entries, err := tree.ListEntries()
if err != nil {
return nil
}
maxImages := config.Gallery.MaxImages
if maxImages <= 0 {
maxImages = 6
}
var images []GalleryImageInfo
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if name == "gallery.json" {
continue
}
ext := strings.ToLower(path.Ext(name))
switch ext {
case ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp":
default:
continue
}
images = append(images, GalleryImageInfo{
Name: name,
Caption: captionMap[name],
URL: repo.Link() + "/raw/" + repo.DefaultBranch + "/.gallery/" + name,
})
if len(images) >= maxImages {
break
}
}
return images
}
// loadReadmeContent loads and renders the README content // loadReadmeContent loads and renders the README content
func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository) (template.HTML, error) { func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository) (template.HTML, error) {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())

View File

@@ -545,6 +545,15 @@ func PagesContentPost(ctx *context.Context) {
if maxPosts := ctx.FormInt("blog_max_posts"); maxPosts > 0 { if maxPosts := ctx.FormInt("blog_max_posts"); maxPosts > 0 {
config.Blog.MaxPosts = maxPosts config.Blog.MaxPosts = maxPosts
} }
config.Gallery.Enabled = ctx.FormBool("gallery_enabled")
config.Gallery.Headline = ctx.FormString("gallery_headline")
config.Gallery.Subheadline = ctx.FormString("gallery_subheadline")
if maxImages := ctx.FormInt("gallery_max_images"); maxImages > 0 {
config.Gallery.MaxImages = maxImages
}
if cols := ctx.FormInt("gallery_columns"); cols >= 2 && cols <= 4 {
config.Gallery.Columns = cols
}
config.Stats = nil config.Stats = nil
for i := range 10 { for i := range 10 {
value := ctx.FormString(fmt.Sprintf("stat_value_%d", i)) value := ctx.FormString(fmt.Sprintf("stat_value_%d", i))

View File

@@ -1136,6 +1136,7 @@
{{if .Config.Features}}<a href="#features" class="nb-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="nb-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="nb-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="nb-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="nb-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="nb-nav-repo"> <a href="{{.RepoURL}}" class="nb-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1174,6 +1175,7 @@
{{if .Config.Features}}<a href="#features" class="nb-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="nb-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="nb-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="nb-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="nb-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="nb-nav-repo"> <a href="{{.RepoURL}}" class="nb-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1481,6 +1483,29 @@
</section> </section>
{{end}} {{end}}
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="nb-features" id="gallery" style="border-top: 1px solid var(--nb-border-hard);">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Gallery</div>
<h2><span class="nb-glow-text">{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</span></h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 4px;">
{{range .GalleryImages}}
<div class="nb-reveal" style="overflow: hidden; clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));">
<a href="{{.URL}}" target="_blank" style="display: block; position: relative;">
<img src="{{.URL}}" alt="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="width: 100%; height: 220px; object-fit: cover; display: block;">
{{if .Caption}}
<div style="position: absolute; bottom: 0; left: 0; right: 0; padding: 12px 16px; background: linear-gradient(transparent, rgba(0,0,0,0.7)); font-size: 13px; color: #fff;">{{.Caption}}</div>
{{end}}
</a>
</div>
{{end}}
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}} {{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer --> <!-- Footer -->

View File

@@ -1003,6 +1003,7 @@
{{if .Config.Features}}<a href="#features" class="ea-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="ea-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="ea-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="ea-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="ea-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ea-btn-text"> <a href="{{.RepoURL}}" class="ea-btn-text">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1036,6 +1037,7 @@
{{if .Config.Features}}<a href="#features" class="ea-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="ea-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="ea-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="ea-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="ea-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ea-btn-text"> <a href="{{.RepoURL}}" class="ea-btn-text">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1355,6 +1357,27 @@
</section> </section>
{{end}} {{end}}
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="ea-section" id="gallery">
<div class="ea-section-label ea-reveal">Gallery</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p style="color: var(--ea-muted); font-size: 17px; margin-bottom: 32px;" class="ea-reveal">{{.Config.Gallery.Subheadline}}</p>{{end}}
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 16px;">
{{range .GalleryImages}}
<div class="ea-reveal" style="overflow: hidden; border-radius: 4px; border: 1px solid var(--ea-border);">
<a href="{{.URL}}" target="_blank" style="display: block;">
<img src="{{.URL}}" alt="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="width: 100%; height: 200px; object-fit: cover; display: block;">
</a>
{{if .Caption}}
<div style="padding: 12px 16px; font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--ea-light); letter-spacing: 0.04em;">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}} {{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer --> <!-- Footer -->

View File

@@ -994,6 +994,7 @@
{{if .Config.Features}}<a href="#features" class="osh-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="osh-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="osh-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="osh-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="osh-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="osh-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="osh-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="osh-nav-cta"> <a href="{{.RepoURL}}" class="osh-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1352,6 +1353,31 @@
</section> </section>
{{end}} {{end}}
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="osh-features" id="gallery" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Gallery</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 16px;">
{{range .GalleryImages}}
<div class="osh-feature-card osh-reveal" style="padding: 0; overflow: hidden;">
<a href="{{.URL}}" target="_blank" style="display: block;">
<img src="{{.URL}}" alt="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="width: 100%; height: 220px; object-fit: cover; display: block;">
</a>
{{if .Caption}}
<div style="padding: 16px 20px; font-size: 13px; color: var(--osh-muted);">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}} {{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer --> <!-- Footer -->

View File

@@ -1111,6 +1111,7 @@
{{if .Config.Features}}<a href="#features" class="gm-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="gm-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="gm-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="gm-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="gm-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="gm-nav-repo"> <a href="{{.RepoURL}}" class="gm-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1147,6 +1148,7 @@
{{if .Config.Features}}<a href="#features" class="gm-nav-link">Features</a>{{end}} {{if .Config.Features}}<a href="#features" class="gm-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="gm-nav-link">Pricing</a>{{end}} {{if .Config.Pricing.Plans}}<a href="#pricing" class="gm-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}} {{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="gm-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}} {{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="gm-nav-repo"> <a href="{{.RepoURL}}" class="gm-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> <img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1491,6 +1493,30 @@
</section> </section>
{{end}} {{end}}
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="gm-section" id="gallery">
<div class="gm-section-inner">
<div class="gm-section-header gm-reveal">
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}<span class="gm-serif">Gallery</span>{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 16px;">
{{range .GalleryImages}}
<div class="gm-value-card gm-reveal" style="padding: 0; overflow: hidden;">
<a href="{{.URL}}" target="_blank" style="display: block;">
<img src="{{.URL}}" alt="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="width: 100%; height: 220px; object-fit: cover; display: block;">
</a>
{{if .Caption}}
<div style="padding: 16px 20px; font-size: 14px; color: var(--gm-light);">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}} {{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer --> <!-- Footer -->

View File

@@ -65,6 +65,37 @@
<input name="blog_max_posts" type="number" min="1" max="12" value="{{if .Config.Blog.MaxPosts}}{{.Config.Blog.MaxPosts}}{{else}}3{{end}}"> <input name="blog_max_posts" type="number" min="1" max="12" value="{{if .Config.Blog.MaxPosts}}{{.Config.Blog.MaxPosts}}{{else}}3{{end}}">
</div> </div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.gallery_section"}}</h5>
<div class="inline field">
<div class="ui toggle checkbox">
<input type="checkbox" name="gallery_enabled" {{if .Config.Gallery.Enabled}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.pages.gallery_enabled_desc"}}</label>
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.gallery_headline"}}</label>
<input name="gallery_headline" value="{{.Config.Gallery.Headline}}" placeholder="Gallery">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.gallery_subheadline"}}</label>
<input name="gallery_subheadline" value="{{.Config.Gallery.Subheadline}}" placeholder="Screenshots and visuals from the project">
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.gallery_max_images"}}</label>
<input name="gallery_max_images" type="number" min="1" max="24" value="{{if .Config.Gallery.MaxImages}}{{.Config.Gallery.MaxImages}}{{else}}6{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.gallery_columns"}}</label>
<select name="gallery_columns" class="ui dropdown">
<option value="2" {{if eq .Config.Gallery.Columns 2}}selected{{end}}>2</option>
<option value="3" {{if or (eq .Config.Gallery.Columns 3) (eq .Config.Gallery.Columns 0)}}selected{{end}}>3</option>
<option value="4" {{if eq .Config.Gallery.Columns 4}}selected{{end}}>4</option>
</select>
</div>
</div>
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.gallery_help_link"}}</p>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.stats"}}</h5> <h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.stats"}}</h5>
<div id="stats-container"> <div id="stats-container">
{{range $i, $stat := .Config.Stats}} {{range $i, $stat := .Config.Stats}}