v0.35.1 2aca1df

components.templ RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Facts is a key/value block on an 11ch key column.
6templ Facts(facts []Fact) {
7	<dl class="kv">
8		for _, f := range facts {
9			<dt>{ f.Key }</dt>
10			@FactValue(f)
11		}
12	</dl>
13}
14
15// CloneBlock is a block of copyable shell lines; each has its own [copy]
16// button, whose label says "copied" for 1.5 s.
17templ CloneBlock(c Clone) {
18	<div class="clone" id={ c.ID }>
19		for _, line := range c.Lines {
20			<div class="clone-line">
21				<span class="mute" aria-hidden="true">$ </span><code>{ line }</code>
22				<button
23					type="button"
24					class="btn copy"
25					data-js
26					data-label="Copy"
27					aria-label={ "copy: " + line }
28					hx-on:click="ermite.copy(this)"
29				>Copy</button>
30			</div>
31		}
32	</div>
33}
34
35// HomeRowView is the row of a repository in the home list. On the left what
36// it is: name, description, topics and the pulse of its activity; on the
37// right its figures and CI state over its newest commits. Its id is the
38// target of SSE swaps; the name link comes first, `enter` follows it.
39templ HomeRowView(r HomeRow) {
40	<li id={ r.ID } class="repo" data-cursor?={ r.Cursor } data-activity?={ r.Fresh }>
41		<div class="repo-main">
42			<h2 class="repo-name">
43				<a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
44				if rel := r.Release; rel != nil {
45					<a class="pill repo-release" href={ templ.SafeURL(rel.Href) } title={ "Latest release: " + rel.Tag }>{ rel.Tag }</a>
46				}
47				if r.Private {
48					<span class="pill">private</span>
49				}
50				if lic := r.License; lic != nil {
51					<a class="pill repo-license" href={ templ.SafeURL(lic.Href) } title={ "License: " + lic.ID }>{ lic.ID }</a>
52				}
53				if r.Fresh {
54					<span class="repo-activity" title="new activity"><span class="ico i-refresh-cw" aria-hidden="true"></span><span class="sr">new activity</span></span>
55				}
56			</h2>
57			if r.Desc != "" {
58				<p class="repo-desc">{ r.Desc }</p>
59			} else {
60				<p class="repo-desc mute">no description</p>
61			}
62			if len(r.Topics) > 0 {
63				<ul class="repo-topics" aria-label={ "Topics of " + r.Name } data-fit>
64					for _, t := range r.Topics {
65						<li>{ t }</li>
66					}
67				</ul>
68			} else {
69				<div class="repo-topics" aria-hidden="true"></div>
70			}
71			if r.Stats != nil && r.Stats.Pulse != nil {
72				@pulse(r.Stats.Pulse)
73			} else {
74				<div class="repo-pulse" aria-hidden="true"></div>
75			}
76		</div>
77		<div class="repo-head">
78			<dl class="repo-stats" aria-label={ "Figures of " + r.Name }>
79				if s := r.Stats; s != nil {
80					<div><dt>commits</dt><dd>{ s.Commits }</dd></div>
81					<div><dt>last 30 days</dt><dd>{ s.Recent }</dd></div>
82				}
83				<div class="rs-active">
84					<dt>last activity</dt>
85					if r.ActiveAt != "" {
86						<dd><time datetime={ r.ActiveAt } title={ r.ActiveAt }>{ r.Active }</time></dd>
87					} else {
88						<dd class="mute">never</dd>
89					}
90				</div>
91				if s := r.Stats; s != nil && s.Tag != "" {
92					<div class="rs-tag">
93						<dt>last tag</dt>
94						if s.TagHref != "" {
95							<dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
96						} else {
97							<dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
98						}
99					</div>
100				}
101			</dl>
102			if r.CI != nil {
103				<a class="repo-ci" href={ templ.SafeURL(r.CI.Href) } title={ r.CI.Label + " · " + r.CI.Title }>
104					<span class={ "ck", "ck-" + r.CI.Kind } aria-hidden="true"></span>
105					<span class="sr">{ "CI: " + r.CI.Label }</span>
106				</a>
107			}
108		</div>
109		if len(r.Commits) > 0 {
110			<ul
111				class="repo-commits"
112				data-full?={ len(r.Commits) == homeRecent }
113				aria-label={ "Recent commits of " + r.Name }
114			>
115				for _, c := range r.Commits {
116					<li>
117						<a class="rc-sha" href={ templ.SafeURL(c.CHref) }>{ c.Short }</a>
118						<span class="rc-subject" title={ c.Subject }>{ c.Subject }</span>
119						if c.WhenAt != "" {
120							<time class="dim" datetime={ c.WhenAt } title={ c.WhenAt }>{ c.When }</time>
121						}
122					</li>
123				}
124			</ul>
125		} else if r.NoCommits {
126			<p class="repo-commits mute">no commits yet</p>
127		}
128	</li>
129}
130
131// pulse is the activity curve of a home row: a smooth line over a fading
132// area on a faint grid, and a dot at today, in colour while the repository
133// is active. Nothing moves: the page stays idle. The SVG scales evenly with
134// its column, so the dot stays round.
135templ pulse(h *Heartbeat) {
136	<figure class="repo-pulse" data-live?={ h.Live }>
137		<svg viewBox="0 0 600 64" role="img" aria-label={ h.Label } focusable="false">
138			<defs>
139				<linearGradient id={ h.GradID } x1="0" y1="0" x2="0" y2="1">
140					<stop class="pulse-stop-top" offset="0"></stop>
141					<stop class="pulse-stop-bottom" offset="1"></stop>
142				</linearGradient>
143			</defs>
144			<path class="pulse-grid" d="M0 21.5 H600 M0 42.5 H600 M0 63.5 H600" vector-effect="non-scaling-stroke"></path>
145			<path class="pulse-area" d={ h.Area } fill={ "url(#" + h.GradID + ")" }></path>
146			<path class="pulse-line" d={ h.Line } vector-effect="non-scaling-stroke"></path>
147			<circle class="pulse-dot" cx={ h.X } cy={ h.Y } r="3.5"></circle>
148		</svg>
149	</figure>
150}
151
152// Outline is the README outline; levels indent by 2ch, the h1 is bold.
153templ Outline(headings []Heading) {
154	<nav class="outline" aria-label="Outline">
155		for _, h := range headings {
156			<a class={ "l" + itoa(h.Level) } href={ templ.SafeURL("#" + h.ID) }>{ h.Text }</a>
157		}
158	</nav>
159}
160
161// ReadmeCard is a README in a card whose header names the file.
162templ ReadmeCard(r Readme, docs ...DocTab) {
163	<section class="card readme" aria-label={ r.Name }>
164		if len(docs) > 1 {
165			<nav class="card-h doc-tabs" aria-label="Repository documents">
166				for _, doc := range docs {
167					<a href={ templ.SafeURL(doc.Href) }
168						if doc.Current {
169							aria-current="page"
170						}
171					>{ doc.Name }</a>
172				}
173			</nav>
174		} else {
175			<h2 class="card-h" id="readme-h">
176				<span class="ico i-file" aria-hidden="true"></span>
177				{ r.Name }
178			</h2>
179		}
180		@ReadmeView(r)
181	</section>
182}
183
184// ReadmeView is a rendered README. Its HTML comes from the Markdown
185// renderer, which drops raw HTML and filters URLs.
186templ ReadmeView(r Readme) {
187	<article class="prose" aria-label={ r.Name } hx-ignore>
188		if r.Note != "" {
189			<p class="dim">{ r.Note }</p>
190		} else {
191			@templ.Raw(r.HTML)
192		}
193	</article>
194}
195
196// CommitRows is a cursor table of commits: sha, subject with inline refs,
197// author, relative date (absolute in the title).
198templ CommitRows(rows []CommitRow) {
199	<table class="list commits" aria-label="Commits">
200		<tbody data-rows>
201			for _, c := range rows {
202				<tr
203					if c.ID != "" {
204						id={ c.ID }
205					}
206					data-cursor?={ c.Cursor }
207				>
208					<td class="c-sha mute">{ c.Short }</td>
209					<td class="c-subject">
210						<a href={ templ.SafeURL(c.CHref) }>{ c.Subject }</a>
211						if c.Merge {
212							<span class="pill">merge</span>
213						}
214						if c.Sig != nil {
215							@signaturePill(c.Sig)
216						}
217						for _, b := range c.Branches {
218							<span class="pill">{ b }</span>
219						}
220						for _, t := range c.Tags {
221							<span class="pill tag"><span class="sr">tag </span>{ t }</span>
222						}
223					</td>
224					<td class="c-ck">@CommitChecks(c.Checks)</td>
225					<td class="c-who dim">{ c.Author }</td>
226					<td class="c-when nowrap dim"><time datetime={ c.WhenAt } title={ c.WhenAt }>{ c.When }</time></td>
227				</tr>
228			}
229		</tbody>
230	</table>
231}
232
233// signaturePill is the badge of a signed commit: the protocol and the verdict
234// as text, the reason in the tooltip.
235templ signaturePill(b *SignatureBadge) {
236	<span class={ "pill sig", b.Class } title={ b.Title }>
237		if b.Proto != "" {
238			<span class="sig-proto">{ b.Proto }</span>
239		}
240		{ b.Label }
241	</span>
242}

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 4

Tags 45

↑↓ moveenter openesc close