v0.17.0 ab8e178

tree.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"context"
7	"net/http"
8
9	"github.com/a-h/templ"
10
11	"bnema.dev/ermite/internal/domain"
12)
13
14// TreeRow is one entry of a directory.
15type TreeRow struct {
16	Glyph string // ▸ directory, @ symbolic link, + submodule, · file
17	Name  string
18	Href  string // "" for a submodule
19	// Expand is the URL of the rows of a directory (Href?rows); "" for any
20	// other entry.
21	Expand  string
22	Size    string
23	Subject string
24	CHref   string
25	Author  string
26	When    string
27	WhenAt  string
28	Cursor  bool
29}
30
31// TreePage is a directory.
32type TreePage struct {
33	Rows   []TreeRow
34	Readme *Readme
35	// Notes say what the listing leaves out.
36	Notes []string
37	// More, for the rows of a directory expanded in place, is the page of the
38	// directory when it has more entries than are listed; "" otherwise.
39	More string
40}
41
42// tree lists a directory of a ref, with the last commit of each entry, and
43// renders the README of the directory below.
44//
45//hexcheck:ignore suspicious-business-logic-in-adapter builds the directory page from the use case results; no rule is decided here
46func (s *Server) tree(ctx context.Context, r *http.Request) (templ.Component, int, error) {
47	d, err := s.loadRepo(ctx, r.PathValue("repo"))
48	if err != nil {
49		return nil, 0, err
50	}
51	t, ok := d.locate(r.PathValue("rest"))
52	if !ok {
53		return nil, 0, errNotFound
54	}
55	res, err := s.getTree(ctx, d.repo.Name, t.rev, t.path, true)
56	if err != nil {
57		return nil, 0, err
58	}
59	entries := res.Entries
60	now := s.cfg.Now()
61
62	rowsOnly := r.URL.Query().Has("rows")
63	p := TreePage{}
64	for i, e := range entries {
65		row := TreeRow{Glyph: "·", Name: visibleText(e.Name), Size: humanSize(e.Size), Cursor: i == 0 && !rowsOnly}
66		switch e.Kind {
67		case domain.EntryDirectory:
68			row.Glyph, row.Name, row.Size = "▸", row.Name+"/", "-"
69			row.Href = repoHref(d.name(), "tree", t.name, e.Path)
70			row.Expand = row.Href + "?rows"
71		case domain.EntrySymlink:
72			row.Glyph = "@"
73			row.Href = repoHref(d.name(), "blob", t.name, e.Path)
74		case domain.EntrySubmodule:
75			row.Glyph, row.Size = "+", "-"
76		default:
77			row.Href = repoHref(d.name(), "blob", t.name, e.Path)
78		}
79		if c := e.LastCommit; c != nil {
80			last := commitRow(d.name(), *c, now)
81			row.Subject, row.Author, row.CHref, row.When, row.WhenAt = last.Subject, last.Author, last.CHref, last.When, last.WhenAt
82		}
83		p.Rows = append(p.Rows, row)
84	}
85	if rowsOnly {
86		// The rows alone, for a directory opened in place (files.js).
87		if res.Truncated {
88			p.More = repoHref(d.name(), "tree", t.name, t.path)
89		}
90		return TreeRows(p), http.StatusOK, nil
91	}
92	if res.Truncated {
93		p.Notes = append(p.Notes, "this directory has more entries than are listed here: the first "+itoa(len(entries))+" are shown.")
94	}
95	if res.LastCommitsOmitted {
96		p.Notes = append(p.Notes, "the last commit is shown for the first entries only.")
97	}
98	rd, err := s.readmeIn(ctx, d.repo.Name, res.CommitID.String(), t.name, t.path, entries)
99	if err != nil {
100		return nil, 0, err
101	}
102	if rd != nil {
103		p.Readme = rd.readme
104	}
105
106	l := s.repoLayout(ctx, d, "tree")
107	// The finder searches the commit this page lists, whichever way the URL
108	// spelled it (an abbreviated id included): the listing knows it.
109	t.commit = res.CommitID.String()
110	pinRef(&l, t)
111	l.Crumbs = pathCrumbs(d.name(), "tree", t)
112	if t.path == "" {
113		l.Title = "tree · " + d.name() + " · " + s.instance
114	} else {
115		l.Title = visibleText(t.path) + " · " + d.name() + " · " + s.instance
116	}
117	return Tree(l, p), http.StatusOK, nil
118}

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