Phase 3: Organization Public Profile Page - Pinned repositories with groups - Public members display with roles - API endpoints for pinned repos and groups Phase 4: Gitea Pages Foundation - Landing page templates (simple, docs, product, portfolio) - Custom domain support with verification - YAML configuration parser (.gitea/landing.yaml) - Repository settings UI for pages Phase 5: Enhanced Wiki System with V2 API - Full CRUD operations via v2 API - Full-text search with WikiIndex table - Link graph visualization - Wiki health metrics (orphaned, dead links, outdated) - Designed for external AI plugin integration - Developer guide for .NET integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
263 lines
7.6 KiB
Go
263 lines
7.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package pages
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// LandingConfig represents the parsed .gitea/landing.yaml configuration
|
|
type LandingConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Template string `yaml:"template"` // simple, documentation, product, portfolio
|
|
|
|
// Custom domain (optional)
|
|
Domain string `yaml:"domain,omitempty"`
|
|
|
|
// Branding
|
|
Branding BrandingConfig `yaml:"branding,omitempty"`
|
|
|
|
// Hero section
|
|
Hero HeroConfig `yaml:"hero,omitempty"`
|
|
|
|
// Features (for product template)
|
|
Features []FeatureConfig `yaml:"features,omitempty"`
|
|
|
|
// Sections
|
|
Sections []SectionConfig `yaml:"sections,omitempty"`
|
|
|
|
// Documentation settings (for documentation template)
|
|
Documentation DocumentationConfig `yaml:"documentation,omitempty"`
|
|
|
|
// Gallery settings (for portfolio template)
|
|
Gallery GalleryConfig `yaml:"gallery,omitempty"`
|
|
|
|
// Footer
|
|
Footer FooterConfig `yaml:"footer,omitempty"`
|
|
|
|
// SEO & Social
|
|
SEO SEOConfig `yaml:"seo,omitempty"`
|
|
|
|
// Analytics
|
|
Analytics AnalyticsConfig `yaml:"analytics,omitempty"`
|
|
|
|
// Advanced settings
|
|
Advanced AdvancedConfig `yaml:"advanced,omitempty"`
|
|
}
|
|
|
|
// BrandingConfig represents branding settings
|
|
type BrandingConfig struct {
|
|
Logo string `yaml:"logo,omitempty"`
|
|
LogoDark string `yaml:"logo_dark,omitempty"`
|
|
Favicon string `yaml:"favicon,omitempty"`
|
|
PrimaryColor string `yaml:"primary_color,omitempty"`
|
|
SecondaryColor string `yaml:"secondary_color,omitempty"`
|
|
AccentColor string `yaml:"accent_color,omitempty"`
|
|
DarkMode string `yaml:"dark_mode,omitempty"` // auto, light, dark, both
|
|
HeadingFont string `yaml:"heading_font,omitempty"`
|
|
BodyFont string `yaml:"body_font,omitempty"`
|
|
}
|
|
|
|
// HeroConfig represents hero section settings
|
|
type HeroConfig struct {
|
|
Title string `yaml:"title,omitempty"`
|
|
Tagline string `yaml:"tagline,omitempty"`
|
|
Background string `yaml:"background,omitempty"`
|
|
Gradient string `yaml:"gradient,omitempty"`
|
|
Color string `yaml:"color,omitempty"`
|
|
CTAPrimary CTAConfig `yaml:"cta_primary,omitempty"`
|
|
CTASecondary CTAConfig `yaml:"cta_secondary,omitempty"`
|
|
}
|
|
|
|
// CTAConfig represents a call-to-action button
|
|
type CTAConfig struct {
|
|
Text string `yaml:"text,omitempty"`
|
|
Link string `yaml:"link,omitempty"`
|
|
Style string `yaml:"style,omitempty"` // outline, ghost
|
|
}
|
|
|
|
// FeatureConfig represents a single feature item
|
|
type FeatureConfig struct {
|
|
Icon string `yaml:"icon,omitempty"`
|
|
Title string `yaml:"title,omitempty"`
|
|
Description string `yaml:"description,omitempty"`
|
|
}
|
|
|
|
// SectionConfig represents a content section
|
|
type SectionConfig struct {
|
|
Type string `yaml:"type,omitempty"` // features, screenshot, readme, releases, contributors, custom
|
|
Image string `yaml:"image,omitempty"`
|
|
Caption string `yaml:"caption,omitempty"`
|
|
File string `yaml:"file,omitempty"`
|
|
Title string `yaml:"title,omitempty"`
|
|
Limit int `yaml:"limit,omitempty"`
|
|
ShowNotes bool `yaml:"show_notes,omitempty"`
|
|
ShowCount bool `yaml:"show_count,omitempty"`
|
|
}
|
|
|
|
// DocumentationConfig represents documentation template settings
|
|
type DocumentationConfig struct {
|
|
Source string `yaml:"source,omitempty"`
|
|
Search bool `yaml:"search,omitempty"`
|
|
TOC bool `yaml:"toc,omitempty"`
|
|
Sidebar []SidebarGroup `yaml:"sidebar,omitempty"`
|
|
EditLinks EditLinksConfig `yaml:"edit_links,omitempty"`
|
|
}
|
|
|
|
// SidebarGroup represents a sidebar navigation group
|
|
type SidebarGroup struct {
|
|
Title string `yaml:"title,omitempty"`
|
|
Items []string `yaml:"items,omitempty"`
|
|
Collapsed bool `yaml:"collapsed,omitempty"`
|
|
}
|
|
|
|
// EditLinksConfig represents edit link settings
|
|
type EditLinksConfig struct {
|
|
Enabled bool `yaml:"enabled,omitempty"`
|
|
Text string `yaml:"text,omitempty"`
|
|
}
|
|
|
|
// GalleryConfig represents gallery/portfolio template settings
|
|
type GalleryConfig struct {
|
|
Source string `yaml:"source,omitempty"`
|
|
Columns int `yaml:"columns,omitempty"`
|
|
Lightbox bool `yaml:"lightbox,omitempty"`
|
|
Captions bool `yaml:"captions,omitempty"`
|
|
Items []GalleryItem `yaml:"items,omitempty"`
|
|
}
|
|
|
|
// GalleryItem represents a single gallery item
|
|
type GalleryItem struct {
|
|
Image string `yaml:"image,omitempty"`
|
|
Title string `yaml:"title,omitempty"`
|
|
Link string `yaml:"link,omitempty"`
|
|
}
|
|
|
|
// FooterConfig represents footer settings
|
|
type FooterConfig struct {
|
|
Links []FooterLinkGroup `yaml:"links,omitempty"`
|
|
Copyright string `yaml:"copyright,omitempty"`
|
|
ShowPoweredBy bool `yaml:"show_powered_by,omitempty"`
|
|
}
|
|
|
|
// FooterLinkGroup represents a group of footer links
|
|
type FooterLinkGroup struct {
|
|
Title string `yaml:"title,omitempty"`
|
|
Items []FooterLink `yaml:"items,omitempty"`
|
|
}
|
|
|
|
// FooterLink represents a single footer link
|
|
type FooterLink struct {
|
|
Text string `yaml:"text,omitempty"`
|
|
URL string `yaml:"url,omitempty"`
|
|
}
|
|
|
|
// SEOConfig represents SEO and social sharing settings
|
|
type SEOConfig struct {
|
|
Title string `yaml:"title,omitempty"`
|
|
Description string `yaml:"description,omitempty"`
|
|
Keywords []string `yaml:"keywords,omitempty"`
|
|
OGImage string `yaml:"og_image,omitempty"`
|
|
TwitterCard string `yaml:"twitter_card,omitempty"`
|
|
TwitterSite string `yaml:"twitter_site,omitempty"`
|
|
}
|
|
|
|
// AnalyticsConfig represents analytics settings
|
|
type AnalyticsConfig struct {
|
|
Plausible string `yaml:"plausible,omitempty"`
|
|
Umami UmamiConfig `yaml:"umami,omitempty"`
|
|
GoogleAnalytics string `yaml:"google_analytics,omitempty"`
|
|
}
|
|
|
|
// UmamiConfig represents Umami analytics settings
|
|
type UmamiConfig struct {
|
|
WebsiteID string `yaml:"website_id,omitempty"`
|
|
URL string `yaml:"url,omitempty"`
|
|
}
|
|
|
|
// AdvancedConfig represents advanced settings
|
|
type AdvancedConfig struct {
|
|
CustomCSS string `yaml:"custom_css,omitempty"`
|
|
CustomHead string `yaml:"custom_head,omitempty"`
|
|
Redirects map[string]string `yaml:"redirects,omitempty"`
|
|
}
|
|
|
|
// ParseLandingConfig parses a landing.yaml file content
|
|
func ParseLandingConfig(content []byte) (*LandingConfig, error) {
|
|
config := &LandingConfig{
|
|
// Set defaults
|
|
Enabled: true,
|
|
Template: "simple",
|
|
}
|
|
|
|
if err := yaml.Unmarshal(content, config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Apply defaults
|
|
if config.Template == "" {
|
|
config.Template = "simple"
|
|
}
|
|
if config.Branding.DarkMode == "" {
|
|
config.Branding.DarkMode = "auto"
|
|
}
|
|
if config.Gallery.Columns == 0 {
|
|
config.Gallery.Columns = 4
|
|
}
|
|
|
|
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: "simple",
|
|
Branding: BrandingConfig{
|
|
DarkMode: "auto",
|
|
},
|
|
Hero: HeroConfig{
|
|
CTAPrimary: CTAConfig{
|
|
Text: "Get Started",
|
|
Link: "#installation",
|
|
},
|
|
CTASecondary: CTAConfig{
|
|
Text: "View Documentation",
|
|
Link: "#readme",
|
|
},
|
|
},
|
|
Sections: []SectionConfig{
|
|
{Type: "readme"},
|
|
{Type: "releases", Limit: 3},
|
|
},
|
|
Footer: FooterConfig{
|
|
ShowPoweredBy: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// ValidTemplates returns the list of valid template names
|
|
func ValidTemplates() []string {
|
|
return []string{"simple", "documentation", "product", "portfolio"}
|
|
}
|
|
|
|
// IsValidTemplate checks if a template name is valid
|
|
func IsValidTemplate(name string) bool {
|
|
for _, t := range ValidTemplates() {
|
|
if t == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|