v0.5.0 bfab294

home.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"context"
7	"net/http"
8	"sort"
9	"strings"
10	"time"
11
12	"connectrpc.com/connect"
13	"github.com/a-h/templ"
14
15	pb "bnema.dev/ermite-api/gen/ermite"
16)
17
18// maxRepos bounds the repositories one page lists.
19const maxRepos = 1000
20
21// pageSize is the page size asked from the list RPCs: the largest the API
22// accepts.
23const pageSize = 100
24
25// listRepos reads the repositories the visitor may see, ordered by name.
26func (s *Server) listRepos(ctx context.Context) ([]*pb.Repo, error) {
27	var repos []*pb.Repo
28	token := ""
29	for len(repos) < maxRepos {
30		res, err := s.cfg.Repos.ListRepos(ctx, connect.NewRequest(&pb.ListReposRequest{PageSize: pageSize, PageToken: token}))
31		if err != nil {
32			return nil, err
33		}
34		repos = append(repos, res.Msg.GetRepos()...)
35		if token = res.Msg.GetNextPageToken(); token == "" {
36			break
37		}
38	}
39	return repos, nil
40}
41
42// homeCols are the sortable columns of the home list, in order.
43var homeCols = []struct{ key, label, class string }{
44	{"name", "name", "c-name"},
45	{"desc", "description", "c-desc"},
46	{"push", "last push", "c-when"},
47	{"branch", "branch", "c-ref"},
48}
49
50// sortRepos orders repos by the column key; ties keep the order by name.
51func sortRepos(repos []*pb.Repo, key string, desc bool) {
52	less := func(a, b *pb.Repo) bool {
53		switch key {
54		case "desc":
55			return strings.ToLower(a.GetDescription()) < strings.ToLower(b.GetDescription())
56		case "branch":
57			return a.GetDefaultBranch() < b.GetDefaultBranch()
58		case "push":
59			return a.GetPushedAt().AsTime().Before(b.GetPushedAt().AsTime())
60		}
61		return a.GetName() < b.GetName()
62	}
63	sort.SliceStable(repos, func(i, j int) bool {
64		if desc {
65			return less(repos[j], repos[i])
66		}
67		return less(repos[i], repos[j])
68	})
69}
70
71// home lists the repositories: /?sort=<col>&dir=asc|desc, newest push first
72// by default.
73func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, int, error) {
74	repos, err := s.listRepos(ctx)
75	if err != nil {
76		return nil, 0, err
77	}
78	key, desc := "push", true
79	for _, c := range homeCols {
80		if r.URL.Query().Get("sort") == c.key {
81			key, desc = c.key, c.key == "push"
82		}
83	}
84	if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {
85		desc = d == "desc"
86	}
87	sortRepos(repos, key, desc)
88
89	now := s.cfg.Now()
90	p := HomePage{}
91	for _, c := range homeCols {
92		sc := SortCol{Label: c.label, Class: c.class, Dir: "none"}
93		next := "asc"
94		if c.key == key {
95			sc.Dir, sc.Mark = "ascending", "▲"
96			if desc {
97				sc.Dir, sc.Mark = "descending", "▼"
98			} else {
99				next = "desc"
100			}
101		}
102		sc.Href = "/?sort=" + c.key + "&dir=" + next
103		p.Cols = append(p.Cols, sc)
104	}
105	for i, repo := range repos {
106		p.Rows = append(p.Rows, homeRow(repo, now, i == 0))
107	}
108
109	l := s.base()
110	l.Title = s.instance
111	l.Live, l.LiveURL = true, liveURL("home", "")
112	l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...)
113	return Home(l, p), http.StatusOK, nil
114}
115
116// homeRow is the row of a repository in the home list.
117func homeRow(r *pb.Repo, now time.Time, cursor bool) HomeRow {
118	row := HomeRow{
119		ID: "repo-" + r.GetName(), Name: r.GetName(), Href: repoHref(r.GetName()),
120		Desc: r.GetDescription(), Pushed: "never", Branch: r.GetDefaultBranch(), Cursor: cursor,
121	}
122	if r.GetPushedAt().IsValid() && r.GetPushedAt().GetSeconds() != 0 {
123		t := r.GetPushedAt().AsTime()
124		row.Pushed, row.PushedAt = ago(now, t), rfc3339(t)
125	}
126	return row
127}
128
129// repoCmds are the repositories as targets of the ":" command line, but the
130// one being viewed.
131func repoCmds(repos []*pb.Repo, except string) []CmdItem {
132	var out []CmdItem
133	for _, r := range repos {
134		if r.GetName() != except {
135			out = append(out, CmdItem{Kind: "repo", Label: r.GetName(), Href: repoHref(r.GetName())})
136		}
137	}
138	return out
139}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close