Commit 26b59e4e18
Verified · cmc
internal/httpd/web.go +11
| @@ -105,6 +105,7 @@ type repoPage struct { | ||
| 105 | 105 | Ref string |
| 106 | 106 | CloneURL string |
| 107 | 107 | Dir string |
| 108 | Tab string // active tab in the repo header | |
| 108 | 109 | } |
| 109 | 110 | |
| 110 | 111 | // repoFor resolves the repo for a web request; false means 404 was sent. |
| @@ -221,6 +222,7 @@ func (s *Server) repoHome(w http.ResponseWriter, r *http.Request) { | ||
| 221 | 222 | if !ok { |
| 222 | 223 | return |
| 223 | 224 | } |
| 225 | p.Tab = "files" | |
| 224 | 226 | s.renderTree(w, r, p, "") |
| 225 | 227 | } |
| 226 | 228 | |
| @@ -229,6 +231,7 @@ func (s *Server) tree(w http.ResponseWriter, r *http.Request) { | ||
| 229 | 231 | if !ok { |
| 230 | 232 | return |
| 231 | 233 | } |
| 234 | p.Tab = "files" | |
| 232 | 235 | s.renderTree(w, r, p, strings.Trim(r.PathValue("path"), "/")) |
| 233 | 236 | } |
| 234 | 237 | |
| @@ -275,6 +278,7 @@ func (s *Server) blob(w http.ResponseWriter, r *http.Request) { | ||
| 275 | 278 | if !ok { |
| 276 | 279 | return |
| 277 | 280 | } |
| 281 | p.Tab = "files" | |
| 278 | 282 | filePath := strings.Trim(r.PathValue("path"), "/") |
| 279 | 283 | data, err := gitutil.ReadBlob(p.Dir, p.Ref, filePath, maxRenderBytes+1) |
| 280 | 284 | if err != nil { |
| @@ -565,6 +569,7 @@ func (s *Server) log(w http.ResponseWriter, r *http.Request) { | ||
| 565 | 569 | if !ok { |
| 566 | 570 | return |
| 567 | 571 | } |
| 572 | p.Tab = "log" | |
| 568 | 573 | const pageSize = 50 |
| 569 | 574 | shas, err := gitutil.RevList(p.Dir, p.Ref, pageSize+1) |
| 570 | 575 | if err != nil { |
| @@ -604,6 +609,7 @@ func (s *Server) commit(w http.ResponseWriter, r *http.Request) { | ||
| 604 | 609 | if !ok { |
| 605 | 610 | return |
| 606 | 611 | } |
| 612 | p.Tab = "log" | |
| 607 | 613 | sha := r.PathValue("sha") |
| 608 | 614 | full, err := gitutil.ResolveRef(p.Dir, sha) |
| 609 | 615 | if err != nil { |
| @@ -641,6 +647,7 @@ func (s *Server) issues(w http.ResponseWriter, r *http.Request) { | ||
| 641 | 647 | if !ok { |
| 642 | 648 | return |
| 643 | 649 | } |
| 650 | p.Tab = "issues" | |
| 644 | 651 | state := r.URL.Query().Get("state") |
| 645 | 652 | if state != "closed" && state != "all" { |
| 646 | 653 | state = "open" |
| @@ -662,6 +669,7 @@ func (s *Server) issue(w http.ResponseWriter, r *http.Request) { | ||
| 662 | 669 | if !ok { |
| 663 | 670 | return |
| 664 | 671 | } |
| 672 | p.Tab = "issues" | |
| 665 | 673 | n, err := strconv.ParseInt(r.PathValue("n"), 10, 64) |
| 666 | 674 | if err != nil { |
| 667 | 675 | http.NotFound(w, r) |
| @@ -690,6 +698,7 @@ func (s *Server) mrs(w http.ResponseWriter, r *http.Request) { | ||
| 690 | 698 | if !ok { |
| 691 | 699 | return |
| 692 | 700 | } |
| 701 | p.Tab = "merge requests" | |
| 693 | 702 | state := r.URL.Query().Get("state") |
| 694 | 703 | if state == "" { |
| 695 | 704 | state = "open" |
| @@ -715,6 +724,7 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { | ||
| 715 | 724 | if !ok { |
| 716 | 725 | return |
| 717 | 726 | } |
| 727 | p.Tab = "merge requests" | |
| 718 | 728 | n, err := strconv.ParseInt(r.PathValue("n"), 10, 64) |
| 719 | 729 | if err != nil { |
| 720 | 730 | http.NotFound(w, r) |
| @@ -763,6 +773,7 @@ func (s *Server) refs(w http.ResponseWriter, r *http.Request) { | ||
| 763 | 773 | if !ok { |
| 764 | 774 | return |
| 765 | 775 | } |
| 776 | p.Tab = "refs" | |
| 766 | 777 | branches, _ := gitutil.Refs(p.Dir, "heads") |
| 767 | 778 | tags, _ := gitutil.Refs(p.Dir, "tags") |
| 768 | 779 | s.render(w, "refs.html", struct { |
internal/web/static/style.css +27 −3
| @@ -122,14 +122,33 @@ td { | ||
| 122 | 122 | td:last-child { padding-right: 0; } |
| 123 | 123 | td.size, td.mode { color: var(--muted); white-space: nowrap; } |
| 124 | 124 | |
| 125 | /* repo header */ | |
| 126 | .repohead { margin-bottom: var(--sp-4); } | |
| 127 | h1.repotitle { margin: var(--sp-4) 0 var(--sp-1); font-weight: 400; } | |
| 128 | h1.repotitle a { color: var(--fg); } | |
| 129 | h1.repotitle a:last-of-type { font-weight: 650; } | |
| 130 | h1.repotitle a:hover { color: var(--accent); text-decoration: none; } | |
| 131 | h1.repotitle .sep { color: var(--muted); margin: 0 0.15em; } | |
| 132 | .repohead .desc { margin: 0 0 var(--sp-2); } | |
| 133 | ||
| 125 | 134 | /* repo tabs */ |
| 126 | nav.tabs { display: flex; flex-wrap: wrap; gap: var(--sp-1); } | |
| 135 | nav.tabs { | |
| 136 | display: flex; | |
| 137 | flex-wrap: wrap; | |
| 138 | gap: var(--sp-1); | |
| 139 | border-bottom: 1px solid var(--faint); | |
| 140 | margin-top: var(--sp-3); | |
| 141 | } | |
| 127 | 142 | nav.tabs a { |
| 128 | 143 | color: var(--muted); |
| 129 | 144 | padding: var(--sp-1) var(--sp-2); |
| 130 | border-radius: var(--r-md); | |
| 145 | border-radius: var(--r-md) var(--r-md) 0 0; | |
| 146 | border-bottom: 2px solid transparent; | |
| 147 | margin-bottom: -1px; | |
| 148 | font-size: var(--fs-2); | |
| 131 | 149 | } |
| 132 | 150 | nav.tabs a:hover { color: var(--fg); background: var(--surface); text-decoration: none; } |
| 151 | nav.tabs a.active { color: var(--fg); border-bottom-color: var(--accent); font-weight: 550; } | |
| 133 | 152 | |
| 134 | 153 | p.clone code { |
| 135 | 154 | background: var(--surface); |
| @@ -203,7 +222,7 @@ pre.diff .meta { color: var(--muted); } | ||
| 203 | 222 | /* badges: signature and check states. Semantic colors stay distinct: |
| 204 | 223 | green verified/success, amber pending/stale, red bad/failure, muted |
| 205 | 224 | unsigned. */ |
| 206 | .badge { | |
| 225 | .badge, .chip { | |
| 207 | 226 | --chip: var(--neutral); |
| 208 | 227 | display: inline-block; |
| 209 | 228 | padding: 0.05rem 0.55rem; |
| @@ -215,7 +234,12 @@ pre.diff .meta { color: var(--muted); } | ||
| 215 | 234 | border: 1px solid color-mix(in srgb, var(--chip) 35%, transparent); |
| 216 | 235 | background: color-mix(in srgb, var(--chip) 7%, transparent); |
| 217 | 236 | white-space: nowrap; |
| 237 | vertical-align: middle; | |
| 218 | 238 | } |
| 239 | .chip-open { --chip: var(--ok); } | |
| 240 | .chip-closed { --chip: var(--bad); } | |
| 241 | .chip-merged { --chip: var(--done); } | |
| 242 | .chip-neutral { --chip: var(--neutral); } | |
| 219 | 243 | .badge-verified { --chip: var(--ok); } |
| 220 | 244 | .badge-unsigned { --chip: var(--neutral); } |
| 221 | 245 | .badge-signed_unknown_key { --chip: var(--warn); } |
internal/web/templates/layout.html +8 −7
| @@ -21,17 +21,18 @@ | ||
| 21 | 21 | </html>{{end}} |
| 22 | 22 | |
| 23 | 23 | {{define "repoheader"}} |
| 24 | <h1><a href="/{{.Repo.OwnerName}}">{{.Repo.OwnerName}}</a>/<a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">{{.Repo.Name}}</a></h1> | |
| 24 | <div class="repohead"> | |
| 25 | <h1 class="repotitle"><a class="owner" href="/{{.Repo.OwnerName}}">{{.Repo.OwnerName}}</a><span class="sep">/</span><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">{{.Repo.Name}}</a>{{if eq .Repo.Visibility "private"}} <span class="chip chip-neutral">private</span>{{end}}</h1> | |
| 25 | 26 | {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}} |
| 26 | 27 | <nav class="tabs"> |
| 27 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">files</a> | |
| 28 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/log">log</a> | |
| 29 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/refs">refs</a> | |
| 30 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/issues">issues</a> | |
| 31 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs">merge requests</a> | |
| 28 | <a {{if eq .Tab "files"}}class="active" {{end}}href="/{{.Repo.OwnerName}}/{{.Repo.Name}}">files</a> | |
| 29 | <a {{if eq .Tab "log"}}class="active" {{end}}href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/log">log</a> | |
| 30 | <a {{if eq .Tab "refs"}}class="active" {{end}}href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/refs">refs</a> | |
| 31 | <a {{if eq .Tab "issues"}}class="active" {{end}}href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/issues">issues</a> | |
| 32 | <a {{if eq .Tab "merge requests"}}class="active" {{end}}href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs">merge requests</a> | |
| 32 | 33 | <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/archive/{{.Ref}}.tar.gz">archive</a> |
| 33 | 34 | </nav> |
| 34 | <p class="clone">clone: <code>git clone {{.CloneURL}}</code></p> | |
| 35 | </div> | |
| 35 | 36 | {{end}} |
| 36 | 37 | |
| 37 | 38 | {{define "sigbadge"}}<span class="badge badge-{{.State}}" title="{{.Fingerprint}}">{{.State}}{{if .Signer}} · {{.Signer}}{{end}}</span>{{end}} |
internal/web/templates/tree.html +1
| @@ -2,6 +2,7 @@ | ||
| 2 | 2 | {{define "content"}} |
| 3 | 3 | {{template "repoheader" .}} |
| 4 | 4 | <p class="crumbs">{{.Ref}}: {{range .Crumbs}}<a href="{{.URL}}">{{.Name}}</a>/{{end}}</p> |
| 5 | <p class="clone">clone: <code>git clone {{.CloneURL}}</code></p> | |
| 5 | 6 | <table class="tree"> |
| 6 | 7 | {{range .Entries}}<tr> |
| 7 | 8 | <td class="mode">{{.Mode}}</td> |