package web
import (
"net/http"
"net/url"
"sort"
"strings"
"github.com/vaughan0/go-ini"
"sourcecraft.dev/bigbes/sr-ht-core/config"
"sourcecraft.dev/bigbes/sr-ht-compare/authz"
)
// navCanonical is the SourceHut service-switcher order. Services not listed here
// (including our own compare) sort alphabetically after these.
var navCanonical = []string{"hub", "git", "hg", "lists", "todo", "builds", "man", "meta"}
// navExcluded are service sections that never appear in the switcher: paste and
// pages have no top-level UI worth linking, and hub is rendered as the brand.
var navExcluded = map[string]bool{"paste": true, "pages": true, "hub": true}
// navItem is one entry in the service switcher.
type navItem struct {
Name string // short service name, e.g. "git"
Origin string // external origin URL
Active bool // true for compare.sr.ht (this service)
}
// buildNav derives the service switcher from the shared config: every section
// whose name ends in ".sr.ht" (with a configured origin) except paste/pages/hub,
// ordered by navCanonical then alphabetically, with compare.sr.ht marked active.
func buildNav(conf ini.File) []navItem {
var items []navItem
for section := range conf {
if !strings.HasSuffix(section, ".sr.ht") {
continue
}
short := strings.TrimSuffix(section, ".sr.ht")
if navExcluded[short] {
continue
}
origin := config.GetOrigin(conf, section, true)
if origin == "" {
continue
}
items = append(items, navItem{
Name: short,
Origin: origin,
Active: section == "compare.sr.ht",
})
}
sort.SliceStable(items, func(i, j int) bool {
ci, cj := canonIndex(items[i].Name), canonIndex(items[j].Name)
if ci != cj {
return ci < cj
}
return items[i].Name < items[j].Name
})
return items
}
// canonIndex returns a service's position in navCanonical, or a sentinel past
// the end for services that are not canonically ordered.
func canonIndex(name string) int {
for i, n := range navCanonical {
if n == name {
return i
}
}
return len(navCanonical)
}
// viewData is the root value every template is executed against: the chrome
// fields are common to all pages; Data carries the page-specific payload.
type viewData struct {
Title string
SiteName string
HubOrigin string // non-empty ⇒ brand links to hub instead of "/"
Nav []navItem
Username string // "" for an anonymous viewer
LoginURL string
LogoutURL string
RegisterURL string
ProfileURL string
CSSHref string
Environment string
ShowBanner bool
// ContainerClass selects the width of the page's content wrapper: the
// centered Bootstrap "container" by default, or "container-fluid" for the
// full-bleed diff views (commit / compare).
ContainerClass string
Data any
}
// chrome builds the common chrome fields for a request. Login return_to is the
// current full URL (so the viewer lands back where they were); logout return_to
// is this service's origin.
func (s *Server) chrome(r *http.Request) viewData {
username := authz.ForContext(r.Context())
current := s.compareOrigin + r.URL.RequestURI()
loginURL := s.metaOrigin + "/login?return_to=" + url.QueryEscape(current)
logoutURL := s.metaOrigin + "/logout?return_to=" + url.QueryEscape(s.compareOrigin)
profileURL := s.metaOrigin + "/profile"
if s.hubOrigin != "" && username != "" {
profileURL = s.hubOrigin + "/~" + username
}
return viewData{
ContainerClass: "container",
SiteName: s.siteName,
HubOrigin: s.hubOrigin,
Nav: s.nav,
Username: username,
LoginURL: loginURL,
LogoutURL: logoutURL,
RegisterURL: s.metaOrigin,
ProfileURL: profileURL,
CSSHref: s.cssHref,
Environment: strings.ToUpper(s.environment),
ShowBanner: s.environment != "" && s.environment != "production",
}
}