Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m53s
Build and Release / Unit Tests (push) Successful in 8m48s
Build and Release / Lint (push) Successful in 9m12s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m35s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m50s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m2s
Build and Release / Build Binary (linux/arm64) (push) Failing after 14m53s
Add json struct tags to all LandingConfig types for proper JSON serialization in v2 API. Uses omitempty for primitives and omitzero for structs to exclude empty values from JSON output. Enables clean JSON responses from GET /repos/{owner}/{repo}/pages/config API endpoint.
472 lines
19 KiB
Go
472 lines
19 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package pages
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"slices"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// LandingConfig represents the parsed .gitea/landing.yaml configuration
|
|
type LandingConfig struct {
|
|
Enabled bool `yaml:"enabled" json:"enabled"`
|
|
PublicLanding bool `yaml:"public_landing" json:"public_landing"`
|
|
Template string `yaml:"template" json:"template"` // open-source-hero, minimalist-docs, saas-conversion, bold-marketing
|
|
|
|
// Custom domain (optional)
|
|
Domain string `yaml:"domain,omitempty" json:"domain,omitempty"`
|
|
|
|
// Brand configuration
|
|
Brand BrandConfig `yaml:"brand,omitempty" json:"brand,omitzero"`
|
|
|
|
// Hero section
|
|
Hero HeroConfig `yaml:"hero,omitempty" json:"hero,omitzero"`
|
|
|
|
// Stats/metrics
|
|
Stats []StatConfig `yaml:"stats,omitempty" json:"stats,omitempty"`
|
|
|
|
// Value propositions
|
|
ValueProps []ValuePropConfig `yaml:"value_props,omitempty" json:"value_props,omitempty"`
|
|
|
|
// Features
|
|
Features []FeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
|
|
|
|
// Social proof
|
|
SocialProof SocialProofConfig `yaml:"social_proof,omitempty" json:"social_proof,omitzero"`
|
|
|
|
// Pricing (for saas-conversion template)
|
|
Pricing PricingConfig `yaml:"pricing,omitempty" json:"pricing,omitzero"`
|
|
|
|
// CTA section
|
|
CTASection CTASectionConfig `yaml:"cta_section,omitempty" json:"cta_section,omitzero"`
|
|
|
|
// Blog section
|
|
Blog BlogSectionConfig `yaml:"blog,omitempty" json:"blog,omitzero"`
|
|
|
|
// Gallery section
|
|
Gallery GallerySectionConfig `yaml:"gallery,omitempty" json:"gallery,omitzero"`
|
|
|
|
// Comparison section
|
|
Comparison ComparisonSectionConfig `yaml:"comparison,omitempty" json:"comparison,omitzero"`
|
|
|
|
// Navigation visibility
|
|
Navigation NavigationConfig `yaml:"navigation,omitempty" json:"navigation,omitzero"`
|
|
|
|
// Footer
|
|
Footer FooterConfig `yaml:"footer,omitempty" json:"footer,omitzero"`
|
|
|
|
// Theme customization
|
|
Theme ThemeConfig `yaml:"theme,omitempty" json:"theme,omitzero"`
|
|
|
|
// SEO & Social
|
|
SEO SEOConfig `yaml:"seo,omitempty" json:"seo,omitzero"`
|
|
|
|
// Analytics
|
|
Analytics AnalyticsConfig `yaml:"analytics,omitempty" json:"analytics,omitzero"`
|
|
|
|
// Advanced settings
|
|
Advanced AdvancedConfig `yaml:"advanced,omitempty" json:"advanced,omitzero"`
|
|
|
|
// A/B testing experiments
|
|
Experiments ExperimentConfig `yaml:"experiments,omitempty" json:"experiments,omitzero"`
|
|
|
|
// Multi-language support
|
|
I18n I18nConfig `yaml:"i18n,omitempty" json:"i18n,omitzero"`
|
|
}
|
|
|
|
// BrandConfig represents brand/identity settings
|
|
type BrandConfig struct {
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
LogoURL string `yaml:"logo_url,omitempty" json:"logo_url,omitempty"`
|
|
UploadedLogo string `yaml:"uploaded_logo,omitempty" json:"uploaded_logo,omitempty"`
|
|
LogoSource string `yaml:"logo_source,omitempty" json:"logo_source,omitempty"` // "url" (default), "repo", or "org" — selects avatar source
|
|
Tagline string `yaml:"tagline,omitempty" json:"tagline,omitempty"`
|
|
FaviconURL string `yaml:"favicon_url,omitempty" json:"favicon_url,omitempty"`
|
|
UploadedFavicon string `yaml:"uploaded_favicon,omitempty" json:"uploaded_favicon,omitempty"`
|
|
}
|
|
|
|
// ResolvedLogoURL returns the uploaded logo path or the external URL.
|
|
func (b *BrandConfig) ResolvedLogoURL() string {
|
|
if b.UploadedLogo != "" {
|
|
return "/repo-avatars/" + b.UploadedLogo
|
|
}
|
|
return b.LogoURL
|
|
}
|
|
|
|
// ResolvedFaviconURL returns the uploaded favicon path or the external URL.
|
|
func (b *BrandConfig) ResolvedFaviconURL() string {
|
|
if b.UploadedFavicon != "" {
|
|
return "/repo-avatars/" + b.UploadedFavicon
|
|
}
|
|
return b.FaviconURL
|
|
}
|
|
|
|
// HeroConfig represents hero section settings
|
|
type HeroConfig struct {
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
PrimaryCTA CTAButton `yaml:"primary_cta,omitempty" json:"primary_cta,omitzero"`
|
|
SecondaryCTA CTAButton `yaml:"secondary_cta,omitempty" json:"secondary_cta,omitzero"`
|
|
ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"`
|
|
UploadedImage string `yaml:"uploaded_image,omitempty" json:"uploaded_image,omitempty"` // filename in repo-avatars storage
|
|
CodeExample string `yaml:"code_example,omitempty" json:"code_example,omitempty"`
|
|
VideoURL string `yaml:"video_url,omitempty" json:"video_url,omitempty"`
|
|
}
|
|
|
|
// ResolvedImageURL returns the effective hero image URL, preferring uploaded image over URL.
|
|
func (h *HeroConfig) ResolvedImageURL() string {
|
|
if h.UploadedImage != "" {
|
|
return "/repo-avatars/" + h.UploadedImage
|
|
}
|
|
return h.ImageURL
|
|
}
|
|
|
|
// CTAButton represents a call-to-action button
|
|
type CTAButton struct {
|
|
Label string `yaml:"label,omitempty" json:"label,omitempty"`
|
|
URL string `yaml:"url,omitempty" json:"url,omitempty"`
|
|
Variant string `yaml:"variant,omitempty" json:"variant,omitempty"` // primary, secondary, outline, text
|
|
}
|
|
|
|
// StatConfig represents a single stat/metric
|
|
type StatConfig struct {
|
|
Value string `yaml:"value,omitempty" json:"value,omitempty"`
|
|
Label string `yaml:"label,omitempty" json:"label,omitempty"`
|
|
}
|
|
|
|
// ValuePropConfig represents a value proposition
|
|
type ValuePropConfig struct {
|
|
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
|
|
}
|
|
|
|
// FeatureConfig represents a single feature item
|
|
type FeatureConfig struct {
|
|
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
|
|
ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"`
|
|
}
|
|
|
|
// SocialProofConfig represents social proof section
|
|
type SocialProofConfig struct {
|
|
Logos []string `yaml:"logos,omitempty" json:"logos,omitempty"`
|
|
Testimonial TestimonialConfig `yaml:"testimonial,omitempty" json:"testimonial,omitzero"`
|
|
Testimonials []TestimonialConfig `yaml:"testimonials,omitempty" json:"testimonials,omitempty"`
|
|
}
|
|
|
|
// TestimonialConfig represents a testimonial
|
|
type TestimonialConfig struct {
|
|
Quote string `yaml:"quote,omitempty" json:"quote,omitempty"`
|
|
Author string `yaml:"author,omitempty" json:"author,omitempty"`
|
|
Role string `yaml:"role,omitempty" json:"role,omitempty"`
|
|
Avatar string `yaml:"avatar,omitempty" json:"avatar,omitempty"`
|
|
}
|
|
|
|
// PricingConfig represents pricing section
|
|
type PricingConfig struct {
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
Plans []PricingPlanConfig `yaml:"plans,omitempty" json:"plans,omitempty"`
|
|
}
|
|
|
|
// PricingPlanConfig represents a pricing plan
|
|
type PricingPlanConfig struct {
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
Price string `yaml:"price,omitempty" json:"price,omitempty"`
|
|
Period string `yaml:"period,omitempty" json:"period,omitempty"`
|
|
Features []string `yaml:"features,omitempty" json:"features,omitempty"`
|
|
CTA string `yaml:"cta,omitempty" json:"cta,omitempty"`
|
|
Featured bool `yaml:"featured,omitempty" json:"featured,omitempty"`
|
|
}
|
|
|
|
// CTASectionConfig represents the final CTA section
|
|
type CTASectionConfig struct {
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
Button CTAButton `yaml:"button,omitempty" json:"button,omitzero"`
|
|
}
|
|
|
|
// BlogSectionConfig represents blog section settings on the landing page
|
|
type BlogSectionConfig struct {
|
|
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
MaxPosts int `yaml:"max_posts,omitempty" json:"max_posts,omitempty"` // default 3
|
|
ShowExcerpt bool `yaml:"show_excerpt,omitempty" json:"show_excerpt,omitempty"` // show subtitle as excerpt
|
|
CTAButton CTAButton `yaml:"cta_button,omitempty" json:"cta_button,omitzero"` // "View All Posts" link
|
|
}
|
|
|
|
// GallerySectionConfig represents gallery section settings on the landing page
|
|
type GallerySectionConfig struct {
|
|
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
MaxImages int `yaml:"max_images,omitempty" json:"max_images,omitempty"` // default 6
|
|
Columns int `yaml:"columns,omitempty" json:"columns,omitempty"` // grid columns, default 3
|
|
}
|
|
|
|
// ComparisonSectionConfig represents a feature comparison matrix section
|
|
type ComparisonSectionConfig struct {
|
|
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
|
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
|
|
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
|
|
Columns []ComparisonColumnConfig `yaml:"columns,omitempty" json:"columns,omitempty"`
|
|
Groups []ComparisonGroupConfig `yaml:"groups,omitempty" json:"groups,omitempty"`
|
|
}
|
|
|
|
// HasData returns true if the comparison section has columns and at least one feature
|
|
func (c *ComparisonSectionConfig) HasData() bool {
|
|
if len(c.Columns) == 0 || len(c.Groups) == 0 {
|
|
return false
|
|
}
|
|
for _, g := range c.Groups {
|
|
if len(g.Features) > 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ComparisonColumnConfig represents a column header in the comparison table
|
|
type ComparisonColumnConfig struct {
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
Highlight bool `yaml:"highlight,omitempty" json:"highlight,omitempty"`
|
|
}
|
|
|
|
// ComparisonGroupConfig represents a group of features in the comparison table
|
|
type ComparisonGroupConfig struct {
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
Features []ComparisonFeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
|
|
}
|
|
|
|
// ComparisonFeatureConfig represents a single feature row in the comparison table
|
|
type ComparisonFeatureConfig struct {
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
Values []string `yaml:"values,omitempty" json:"values,omitempty"` // "true"/"false" for check/x, anything else displayed as text
|
|
}
|
|
|
|
// NavigationConfig controls which built-in navigation links appear in the header and footer
|
|
type NavigationConfig struct {
|
|
ShowDocs bool `yaml:"show_docs,omitempty" json:"show_docs,omitempty"`
|
|
ShowAPI bool `yaml:"show_api,omitempty" json:"show_api,omitempty"`
|
|
ShowRepository bool `yaml:"show_repository,omitempty" json:"show_repository,omitempty"`
|
|
ShowReleases bool `yaml:"show_releases,omitempty" json:"show_releases,omitempty"`
|
|
ShowIssues bool `yaml:"show_issues,omitempty" json:"show_issues,omitempty"`
|
|
// Translatable labels for nav items and section headers (defaults to English)
|
|
LabelValueProps string `yaml:"label_value_props,omitempty" json:"label_value_props,omitempty"`
|
|
LabelFeatures string `yaml:"label_features,omitempty" json:"label_features,omitempty"`
|
|
LabelPricing string `yaml:"label_pricing,omitempty" json:"label_pricing,omitempty"`
|
|
LabelBlog string `yaml:"label_blog,omitempty" json:"label_blog,omitempty"`
|
|
LabelGallery string `yaml:"label_gallery,omitempty" json:"label_gallery,omitempty"`
|
|
LabelCompare string `yaml:"label_compare,omitempty" json:"label_compare,omitempty"`
|
|
LabelDocs string `yaml:"label_docs,omitempty" json:"label_docs,omitempty"`
|
|
LabelReleases string `yaml:"label_releases,omitempty" json:"label_releases,omitempty"`
|
|
LabelAPI string `yaml:"label_api,omitempty" json:"label_api,omitempty"`
|
|
LabelIssues string `yaml:"label_issues,omitempty" json:"label_issues,omitempty"`
|
|
}
|
|
|
|
// FooterConfig represents footer settings
|
|
type FooterConfig struct {
|
|
Links []FooterLink `yaml:"links,omitempty" json:"links,omitempty"`
|
|
Social []SocialLink `yaml:"social,omitempty" json:"social,omitempty"`
|
|
Copyright string `yaml:"copyright,omitempty" json:"copyright,omitempty"`
|
|
ShowPoweredBy bool `yaml:"show_powered_by,omitempty" json:"show_powered_by,omitempty"`
|
|
}
|
|
|
|
// FooterLink represents a single footer link
|
|
type FooterLink struct {
|
|
Label string `yaml:"label,omitempty" json:"label,omitempty"`
|
|
URL string `yaml:"url,omitempty" json:"url,omitempty"`
|
|
}
|
|
|
|
// SocialLink represents a social media link
|
|
type SocialLink struct {
|
|
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"` // bluesky, discord, facebook, github, instagram, linkedin, mastodon, reddit, rss, substack, threads, tiktok, twitch, twitter, youtube
|
|
URL string `yaml:"url,omitempty" json:"url,omitempty"`
|
|
}
|
|
|
|
// ThemeConfig represents theme customization
|
|
type ThemeConfig struct {
|
|
PrimaryColor string `yaml:"primary_color,omitempty" json:"primary_color,omitempty"`
|
|
AccentColor string `yaml:"accent_color,omitempty" json:"accent_color,omitempty"`
|
|
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` // light, dark, auto
|
|
}
|
|
|
|
// SEOConfig represents SEO and social sharing settings
|
|
type SEOConfig struct {
|
|
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
Keywords []string `yaml:"keywords,omitempty" json:"keywords,omitempty"`
|
|
OGImage string `yaml:"og_image,omitempty" json:"og_image,omitempty"`
|
|
UseMediaKitOG bool `yaml:"use_media_kit_og,omitempty" json:"use_media_kit_og,omitempty"`
|
|
TwitterCard string `yaml:"twitter_card,omitempty" json:"twitter_card,omitempty"`
|
|
TwitterSite string `yaml:"twitter_site,omitempty" json:"twitter_site,omitempty"`
|
|
}
|
|
|
|
// AnalyticsConfig represents analytics settings
|
|
type AnalyticsConfig struct {
|
|
Plausible string `yaml:"plausible,omitempty" json:"plausible,omitempty"`
|
|
Umami UmamiConfig `yaml:"umami,omitempty" json:"umami,omitzero"`
|
|
GoogleAnalytics string `yaml:"google_analytics,omitempty" json:"google_analytics,omitempty"`
|
|
}
|
|
|
|
// UmamiConfig represents Umami analytics settings
|
|
type UmamiConfig struct {
|
|
WebsiteID string `yaml:"website_id,omitempty" json:"website_id,omitempty"`
|
|
URL string `yaml:"url,omitempty" json:"url,omitempty"`
|
|
}
|
|
|
|
// ExperimentConfig represents A/B testing experiment settings
|
|
type ExperimentConfig struct {
|
|
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
|
|
AutoOptimize bool `yaml:"auto_optimize,omitempty" json:"auto_optimize,omitempty"`
|
|
MinImpressions int `yaml:"min_impressions,omitempty" json:"min_impressions,omitempty"`
|
|
ApprovalRequired bool `yaml:"approval_required,omitempty" json:"approval_required,omitempty"`
|
|
}
|
|
|
|
// I18nConfig represents multi-language settings for the landing page
|
|
type I18nConfig struct {
|
|
DefaultLang string `yaml:"default_lang,omitempty" json:"default_lang,omitempty"`
|
|
Languages []string `yaml:"languages,omitempty" json:"languages,omitempty"`
|
|
}
|
|
|
|
// LanguageDisplayNames returns a map of language codes to display names
|
|
func LanguageDisplayNames() map[string]string {
|
|
return map[string]string{
|
|
"en": "English",
|
|
"es": "Espanol",
|
|
"de": "Deutsch",
|
|
"fr": "Francais",
|
|
"ja": "Japanese",
|
|
"zh": "Chinese",
|
|
"pt": "Portugues",
|
|
"ru": "Russian",
|
|
"ko": "Korean",
|
|
"it": "Italiano",
|
|
"hi": "Hindi",
|
|
"ar": "Arabic",
|
|
"nl": "Nederlands",
|
|
"pl": "Polski",
|
|
"tr": "Turkish",
|
|
}
|
|
}
|
|
|
|
// AdvancedConfig represents advanced settings
|
|
type AdvancedConfig struct {
|
|
CustomCSS string `yaml:"custom_css,omitempty" json:"custom_css,omitempty"`
|
|
CustomHead string `yaml:"custom_head,omitempty" json:"custom_head,omitempty"`
|
|
Redirects map[string]string `yaml:"redirects,omitempty" json:"redirects,omitempty"`
|
|
PublicReleases bool `yaml:"public_releases,omitempty" json:"public_releases,omitempty"`
|
|
HideMobileReleases bool `yaml:"hide_mobile_releases,omitempty" json:"hide_mobile_releases,omitempty"`
|
|
GooglePlayID string `yaml:"google_play_id,omitempty" json:"google_play_id,omitempty"`
|
|
AppStoreID string `yaml:"app_store_id,omitempty" json:"app_store_id,omitempty"`
|
|
}
|
|
|
|
// ParseLandingConfig parses a landing.yaml file content
|
|
func ParseLandingConfig(content []byte) (*LandingConfig, error) {
|
|
config := &LandingConfig{
|
|
Enabled: true,
|
|
Template: "open-source-hero",
|
|
}
|
|
|
|
if err := yaml.Unmarshal(content, config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Apply defaults
|
|
if config.Template == "" {
|
|
config.Template = "open-source-hero"
|
|
}
|
|
if config.Theme.Mode == "" {
|
|
config.Theme.Mode = "auto"
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// HashConfig returns a SHA256 hash of the config content for change detection
|
|
func HashConfig(content []byte) string {
|
|
hash := sha256.Sum256(content)
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
// DefaultConfig returns a default landing page configuration
|
|
func DefaultConfig() *LandingConfig {
|
|
return &LandingConfig{
|
|
Enabled: true,
|
|
Template: "open-source-hero",
|
|
Hero: HeroConfig{
|
|
Headline: "Build something amazing",
|
|
Subheadline: "A powerful toolkit for developers who want to ship fast.",
|
|
PrimaryCTA: CTAButton{
|
|
Label: "Get Started",
|
|
URL: "#",
|
|
},
|
|
SecondaryCTA: CTAButton{
|
|
Label: "View on GitHub",
|
|
URL: "#",
|
|
},
|
|
},
|
|
Stats: []StatConfig{
|
|
{Value: "10k+", Label: "Downloads"},
|
|
{Value: "100+", Label: "Contributors"},
|
|
{Value: "MIT", Label: "License"},
|
|
},
|
|
ValueProps: []ValuePropConfig{
|
|
{Title: "Fast", Description: "Optimized for performance out of the box.", Icon: "zap"},
|
|
{Title: "Flexible", Description: "Adapts to your workflow, not the other way around.", Icon: "gear"},
|
|
{Title: "Open Source", Description: "Free forever. Community driven.", Icon: "heart"},
|
|
},
|
|
Navigation: NavigationConfig{
|
|
ShowDocs: true,
|
|
ShowRepository: true,
|
|
ShowReleases: true,
|
|
},
|
|
CTASection: CTASectionConfig{
|
|
Headline: "Ready to get started?",
|
|
Subheadline: "Join thousands of developers already using this project.",
|
|
Button: CTAButton{
|
|
Label: "Get Started Free",
|
|
URL: "#",
|
|
},
|
|
},
|
|
Footer: FooterConfig{
|
|
ShowPoweredBy: true,
|
|
},
|
|
Theme: ThemeConfig{
|
|
Mode: "auto",
|
|
},
|
|
}
|
|
}
|
|
|
|
// ValidTemplates returns the list of valid template names
|
|
func ValidTemplates() []string {
|
|
return []string{"open-source-hero", "minimalist-docs", "saas-conversion", "bold-marketing", "documentation-first", "developer-tool", "visual-showcase", "cli-terminal", "architecture-deep-dive"}
|
|
}
|
|
|
|
// IsValidTemplate checks if a template name is valid
|
|
func IsValidTemplate(name string) bool {
|
|
return slices.Contains(ValidTemplates(), name)
|
|
}
|
|
|
|
// TemplateDisplayNames returns a map of template names to display names
|
|
func TemplateDisplayNames() map[string]string {
|
|
return map[string]string{
|
|
"open-source-hero": "Open Source Product",
|
|
"minimalist-docs": "Minimalist Product",
|
|
"saas-conversion": "SaaS Product",
|
|
"bold-marketing": "Bold Marketing Product",
|
|
"documentation-first": "Documentation First",
|
|
"developer-tool": "Developer Tool",
|
|
"visual-showcase": "Visual Showcase",
|
|
"cli-terminal": "CLI Terminal",
|
|
"architecture-deep-dive": "Architecture Deep Dive",
|
|
}
|
|
}
|