v0.27.0 0039612

tree.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5import (
6	"context"
7	"net/http"
8	"path"
9
10	"github.com/a-h/templ"
11
12	"bnema.dev/ermite/internal/domain"
13)
14
15// Entry kinds of a TreeRow; the stylesheet draws the node of a row by its kind.
16const (
17	kindDir    = "dir"
18	kindFile   = "file"
19	kindLink   = "link"
20	kindModule = "module"
21)
22
23// TreeRow is one entry of a directory.
24type TreeRow struct {
25	Kind string // kindDir, kindFile, kindLink or kindModule
26	Name string
27	Href string // "" for a submodule
28	// Expand is the URL of the rows of a directory (Href?rows); "" for any
29	// other entry.
30	Expand string
31	// Hint says what a directory, a link or a submodule is, in words, for the
32	// screen reader (the node drawn for it is aria-hidden) and the tooltip; ""
33	// for a file.
34	Hint    string
35	Size    string
36	Subject string
37	CHref   string
38	Author  string
39	When    string
40	WhenAt  string
41	Cursor  bool
42	// Pos and Set are the aria-posinset and aria-setsize of the row among the
43	// entries of its directory; Set is -1 when the listing is cut, as the
44	// directory has more entries than are listed.
45	Pos, Set int
46}
47
48// TreePage is a directory.
49type TreePage struct {
50	Rows []TreeRow
51	// Path is the directory, "/" for the root.
52	Path string
53	// Up is the page of the parent directory, "" at the root: the h key of the
54	// tree goes there from a top-level entry.
55	Up string
56	// Notes say what the listing leaves out.
57	Notes []string
58	// More, for the rows of a directory expanded in place, is the page of the
59	// directory when it has more entries than are listed; "" otherwise.
60	More string
61	// MorePos is the position of the row that says there are more.
62	MorePos int
63}
64
65// Unit is the noun of the entry count in the head of the tree.
66func (p TreePage) Unit() string {
67	if len(p.Rows) == 1 {
68		return "entry"
69	}
70	return "entries"
71}
72
73// tree lists a directory of a ref, with the last commit of each entry. With
74// ?rows it answers the entries alone, for a directory opened in place.
75//
76//hexcheck:ignore suspicious-business-logic-in-adapter builds the directory page from the use case results; no rule is decided here
77func (s *Server) tree(ctx context.Context, r *http.Request) (templ.Component, int, error) {
78	d, err := s.loadRepo(ctx, r.PathValue("repo"))
79	if err != nil {
80		return nil, 0, err
81	}
82	t, ok := d.locate(r.PathValue("rest"))
83	if !ok {
84		return nil, 0, errNotFound
85	}
86	res, err := s.getTree(ctx, d.repo.Name, t.rev, t.path, true)
87	if err != nil {
88		return nil, 0, err
89	}
90	entries := res.Entries
91	now := s.cfg.Now()
92
93	rowsOnly := r.URL.Query().Has("rows")
94	p := TreePage{Path: "/"}
95	if t.path != "" {
96		p.Path = visibleText(t.path) + "/"
97	}
98	for i, e := range entries {
99		row := TreeRow{Kind: kindFile, Name: visibleText(e.Name), Size: humanSize(e.Size), Cursor: i == 0 && !rowsOnly}
100		switch e.Kind {
101		case domain.EntryDirectory:
102			row.Kind, row.Hint, row.Name, row.Size = kindDir, "directory", row.Name+"/", ""
103			row.Href = repoHref(d.name(), "tree", t.name, e.Path)
104			row.Expand = row.Href + "?rows"
105		case domain.EntrySymlink:
106			row.Kind, row.Hint = kindLink, "symbolic link"
107			row.Href = repoHref(d.name(), "blob", t.name, e.Path)
108		case domain.EntrySubmodule:
109			row.Kind, row.Hint, row.Size = kindModule, "submodule", ""
110		default:
111			row.Href = repoHref(d.name(), "blob", t.name, e.Path)
112		}
113		if c := e.LastCommit; c != nil {
114			last := commitRow(d.name(), *c, now)
115			row.Subject, row.Author, row.CHref, row.When, row.WhenAt = last.Subject, last.Author, last.CHref, last.When, last.WhenAt
116		}
117		p.Rows = append(p.Rows, row)
118	}
119	for i := range p.Rows {
120		p.Rows[i].Pos, p.Rows[i].Set = i+1, len(p.Rows)
121		if res.Truncated {
122			p.Rows[i].Set = -1
123		}
124	}
125	if rowsOnly {
126		// The rows alone, for a directory opened in place (files.js).
127		if res.Truncated {
128			p.More, p.MorePos = repoHref(d.name(), "tree", t.name, t.path), len(p.Rows)+1
129		}
130		return TreeRows(p), http.StatusOK, nil
131	}
132	if res.Truncated {
133		p.Notes = append(p.Notes, "this directory has more entries than are listed here: the first "+itoa(len(entries))+" are shown.")
134	}
135	if res.LastCommitsOmitted {
136		p.Notes = append(p.Notes, "the last commit is shown for the first entries only.")
137	}
138	if t.path != "" {
139		up := path.Dir(t.path)
140		if up == "." {
141			up = ""
142		}
143		p.Up = repoHref(d.name(), "tree", t.name, up)
144	}
145
146	l := s.repoLayout(ctx, d, "tree")
147	// The finder searches the commit this page lists, whichever way the URL
148	// spelled it (an abbreviated id included): the listing knows it.
149	t.commit = res.CommitID.String()
150	pinRef(&l, t)
151	l.Crumbs = pathCrumbs(d.name(), "tree", t)
152	if t.path == "" {
153		l.Title = "tree · " + d.name() + " · " + s.instance
154	} else {
155		l.Title = visibleText(t.path) + " · " + d.name() + " · " + s.instance
156	}
157	return Tree(l, p), http.StatusOK, nil
158}

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