package templates
import (
"fmt"
"strconv"
"strings"
)
// Layout is the shared page shell. Styling is intentionally minimal in phase 1;
// the design pass (tokens + Charm chrome) lands in phase 3.
templ Layout(m Meta) {
{ pageTitle(m) }
@templ.Raw(themeResolveScript)
if m.Repo != "" {
@repoTabs(m)
@licenseBar(m)
}
{ children... }
}
templ themePicker(m Meta) {
for _, t := range Families {
if t == themeOr(m.Theme) {
{ themeLabel(t) }
} else {
{ themeLabel(t) }
}
}
}
// themeResolveScript runs synchronously in before paint: it reads the
// family cookie and the OS color scheme, then sets the concrete data-theme
// (family or family-dark; eink is fixed). This is the flash-free path for the
// system-driven light/dark axis the server can't see.
const themeResolveScript = ``
templ repoTabs(m Meta) {
if m.HasReadme {
readme
}
code
log
refs
if m.HasIssues {
issues
}
}
templ licenseBar(m Meta) {
if m.License != nil {
}
}
templ Index(p IndexPage) {
@Layout(p.Meta) {
Repositories
if len(p.Repos) == 0 {
No repositories found.
}
for _, r := range p.Repos {
{ r.Name }
if r.Description != "" {
{ r.Description }
}
if r.Last != nil {
{ r.Last.Subject } · { FmtTime(r.Last.When) }
}
}
}
}
templ Repo(p RepoPage) {
@Layout(p.Meta) {
{ p.DefaultBranch }
{ strconv.Itoa(p.Branches) } branches
{ strconv.Itoa(p.Tags) } tags
if p.Last != nil {
{ p.Last.Short }
{ p.Last.Subject }
— { p.Last.Author }, { FmtTime(p.Last.When) }
}
if len(p.Entries) > 0 {
for _, e := range p.Entries {
if e.IsDir {
{ e.Name }/
} else {
{ e.Name }
}
if !e.IsDir {
{ HumanSize(e.Size) }
}
}
}
}
}
templ Readme(p ReadmePage) {
@Layout(p.Meta) {
@templ.Raw(p.Readme)
}
}
templ Tree(p TreePage) {
@Layout(p.Meta) {
@crumbs(p.Meta, p.Crumbs)
if p.Path != "" {
..
}
for _, e := range p.Entries {
if e.IsDir {
{ e.Name }/
} else {
{ e.Name }
}
if !e.IsDir {
{ HumanSize(e.Size) }
}
}
}
}
templ Blob(p BlobPage) {
@Layout(p.Meta) {
@crumbs(p.Meta, p.Crumbs)
{ HumanSize(p.Size) }
raw
if p.IsBinary {
Binary file not shown.
} else if p.IsMarkdown {
if len(p.Frontmatter) > 0 {
for _, kv := range p.Frontmatter {
{ kv.Key }
{ kv.Value }
}
}
@templ.Raw(p.Markdown)
} else {
@templ.Raw(p.Code)
}
}
}
templ Log(p LogPage) {
@Layout(p.Meta) {
Commits
for _, c := range p.Commits {
{ c.Short }
{ c.Subject }
— { c.Author }, { FmtTime(c.When) }
}
}
}
templ Commit(p CommitPage) {
@Layout(p.Meta) {
{ p.Detail.Commit.Subject }
{ p.Detail.Commit.Hash }
{ p.Detail.Commit.Author } <{ p.Detail.Commit.Email }> · { FmtTime(p.Detail.Commit.When) }
for _, parent := range p.Detail.Parents {
parent { ShortHash(parent) }
}
if p.Detail.Commit.Message != p.Detail.Commit.Subject {
{ p.Detail.Commit.Message }
}
if len(p.Files) == 0 {
No changes.
}
{ strconv.Itoa(len(p.Files)) } files changed
for _, f := range p.Files {
{ f.Name }
+{ strconv.Itoa(f.Added) }
−{ strconv.Itoa(f.Deleted) }
if f.Binary {
Binary file.
} else if f.HTML != "" {
@templ.Raw(f.HTML)
} else {
No textual changes.
}
}
}
}
templ Refs(p RefsPage) {
@Layout(p.Meta) {
Branches
for _, b := range p.Refs.Branches {
{ b.Name }
{ ShortHash(b.Hash) }
}
Tags
if len(p.Refs.Tags) == 0 {
No tags.
}
for _, t := range p.Refs.Tags {
{ t.Name }
{ ShortHash(t.Hash) }
}
}
}
templ Issues(p IssuesPage) {
@Layout(p.Meta) {
Issues ({ strconv.Itoa(p.Total) })
if p.Total == 0 {
No issues.
}
for _, g := range p.Groups {
{ g.Status } { strconv.Itoa(len(g.Tasks)) }
for _, t := range g.Tasks {
if t.Type() != "" {
{ t.Type() }
}
{ t.ID }
{ t.Title }
if PriorityClass(t.Priority) != "" {
{ t.Priority }
}
for _, l := range t.OtherLabels() {
{ l }
}
}
}
}
}
templ Issue(p IssuePage) {
@Layout(p.Meta) {
← issues
{ p.Task.ID }
{ p.Task.Title }
{ p.Task.Status }
if p.Task.Type() != "" {
{ p.Task.Type() }
}
if PriorityClass(p.Task.Priority) != "" {
priority: { p.Task.Priority }
} else if p.Task.Priority != "" {
priority: { p.Task.Priority }
}
for _, l := range p.Task.OtherLabels() {
{ l }
}
if p.Task.Created != "" || p.Task.Updated != "" {
if p.Task.Created != "" {
created { p.Task.Created }
}
if p.Task.Updated != "" {
· updated { p.Task.Updated }
}
}
if len(p.Task.Deps) > 0 {
depends on: { strings.Join(p.Task.Deps, ", ") }
}
if p.Body != "" {
@templ.Raw(p.Body)
}
}
}
templ Error(m Meta, code int, msg string) {
@Layout(m) {
{ strconv.Itoa(code) }
{ msg }
← back to repositories
}
}
templ crumbs(m Meta, cs []Crumb) {
{ m.Repo }
for _, c := range cs {
/
{ c.Name }
}
}
// pageTitle builds the from page meta.
func pageTitle(m Meta) string {
if m.Title != "" {
return m.Title
}
if m.Repo != "" {
return m.Repo + " · humdrum codex"
}
return "humdrum codex"
}
func refOr(ref string) string {
if ref == "" {
return "HEAD"
}
return ref
}
// licenseHref links the badge to the in-repo LICENSE file.
func licenseHref(m Meta) string {
return "/r/" + m.Repo + "/blob/" + refOr(m.Ref) + "/" + m.License.Path
}
func themeOr(t string) string {
if t == "" {
return DefaultTheme
}
return t
}
// themeLabel renders a human-friendly name for the theme picker.
func themeLabel(t string) string {
switch t {
case "flexoki":
return "Flexoki"
case "flexoki-dark":
return "Flexoki Dark"
case "uchu":
return "Uchu"
case "uchu-dark":
return "Uchu Dark"
case "humdrum":
return "Humdrum"
case "humdrum-dark":
return "Humdrum Dark"
case "eink":
return "E-ink"
}
return t
}
// parentTreeURL returns the tree URL one directory up from path.
func parentTreeURL(m Meta, path string) string {
parent := ""
if i := lastSlash(path); i >= 0 {
parent = path[:i]
}
return fmt.Sprintf("/r/%s/tree/%s/%s", m.Repo, m.Ref, parent)
}
func lastSlash(s string) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' {
return i
}
}
return -1
}