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 }>
41 <div class="repo-main">
42 <h2 class="repo-name">
43 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
44 if r.Private {
45 <span class="pill">private</span>
46 }
47 if r.Fresh {
48 <span class="pill new">new push</span>
49 }
50 </h2>
51 if r.Desc != "" {
52 <p class="repo-desc">{ r.Desc }</p>
53 } else {
54 <p class="repo-desc mute">no description</p>
55 }
56 if len(r.Topics) > 0 {
57 <ul class="repo-topics" aria-label={ "Topics of " + r.Name } data-fit>
58 for _, t := range r.Topics {
59 <li>{ t }</li>
60 }
61 </ul>
62 } else {
63 <div class="repo-topics" aria-hidden="true"></div>
64 }
65 if r.Stats != nil && r.Stats.Pulse != nil {
66 @pulse(r.Stats.Pulse)
67 } else {
68 <div class="repo-pulse" aria-hidden="true"></div>
69 }
70 </div>
71 <div class="repo-head">
72 <dl class="repo-stats" aria-label={ "Figures of " + r.Name }>
73 if s := r.Stats; s != nil {
74 <div><dt>commits</dt><dd>{ s.Commits }</dd></div>
75 <div><dt>last 30 days</dt><dd>{ s.Recent }</dd></div>
76 }
77 <div class="rs-active">
78 <dt>last activity</dt>
79 if r.ActiveAt != "" {
80 <dd><time datetime={ r.ActiveAt } title={ r.ActiveAt }>{ r.Active }</time></dd>
81 } else {
82 <dd class="mute">never</dd>
83 }
84 </div>
85 if s := r.Stats; s != nil && s.Tag != "" {
86 <div class="rs-tag">
87 <dt>last tag</dt>
88 if s.TagHref != "" {
89 <dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
90 } else {
91 <dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
92 }
93 </div>
94 }
95 </dl>
96 if r.CI != nil {
97 <a class="repo-ci" href={ templ.SafeURL(r.CI.Href) } title={ r.CI.Title }>
98 <span class={ "ck", "ck-" + r.CI.Kind } aria-hidden="true"></span>
99 <span class="sr">CI: </span><span class="ci-label">{ r.CI.Label }</span>
100 </a>
101 }
102 </div>
103 if len(r.Commits) > 0 {
104 <ul
105 class="repo-commits"
106 data-full?={ len(r.Commits) == homeRecent }
107 aria-label={ "Recent commits of " + r.Name }
108 >
109 for _, c := range r.Commits {
110 <li>
111 <a class="rc-sha" href={ templ.SafeURL(c.CHref) }>{ c.Short }</a>
112 <span class="rc-subject" title={ c.Subject }>{ c.Subject }</span>
113 if c.WhenAt != "" {
114 <time class="dim" datetime={ c.WhenAt } title={ c.WhenAt }>{ c.When }</time>
115 }
116 </li>
117 }
118 </ul>
119 } else if r.NoCommits {
120 <p class="repo-commits mute">no commits yet</p>
121 }
122 </li>
123}
124
125// pulse is the activity curve of a home row: a smooth line over a fading
126// area on a faint grid, and a dot at today, in colour while the repository
127// is active. Nothing moves: the page stays idle. The SVG scales evenly with
128// its column, so the dot stays round.
129templ pulse(h *Heartbeat) {
130 <figure class="repo-pulse" data-live?={ h.Live }>
131 <svg viewBox="0 0 600 64" role="img" aria-label={ h.Label } focusable="false">
132 <defs>
133 <linearGradient id={ h.GradID } x1="0" y1="0" x2="0" y2="1">
134 <stop class="pulse-stop-top" offset="0"></stop>
135 <stop class="pulse-stop-bottom" offset="1"></stop>
136 </linearGradient>
137 </defs>
138 <path class="pulse-grid" d="M0 21.5 H600 M0 42.5 H600 M0 63.5 H600" vector-effect="non-scaling-stroke"></path>
139 <path class="pulse-area" d={ h.Area } fill={ "url(#" + h.GradID + ")" }></path>
140 <path class="pulse-line" d={ h.Line } vector-effect="non-scaling-stroke"></path>
141 <circle class="pulse-dot" cx={ h.X } cy={ h.Y } r="3.5"></circle>
142 </svg>
143 </figure>
144}
145
146// Outline is the README outline; levels indent by 2ch, the h1 is bold.
147templ Outline(headings []Heading) {
148 <nav class="outline" aria-label="Outline">
149 for _, h := range headings {
150 <a class={ "l" + itoa(h.Level) } href={ templ.SafeURL("#" + h.ID) }>{ h.Text }</a>
151 }
152 </nav>
153}
154
155// ReadmeCard is a README in a card whose header names the file.
156templ ReadmeCard(r Readme) {
157 <section class="card readme" aria-labelledby="readme-h">
158 <h2 class="card-h" id="readme-h">
159 <span class="ico i-file" aria-hidden="true"></span>
160 { r.Name }
161 </h2>
162 @ReadmeView(r)
163 </section>
164}
165
166// ReadmeView is a rendered README. Its HTML comes from the Markdown
167// renderer, which drops raw HTML and filters URLs.
168templ ReadmeView(r Readme) {
169 <article class="prose" aria-label={ r.Name } hx-ignore>
170 if r.Note != "" {
171 <p class="dim">{ r.Note }</p>
172 } else {
173 @templ.Raw(r.HTML)
174 }
175 </article>
176}
177
178// CommitRows is a cursor table of commits: sha, subject with inline refs,
179// author, relative date (absolute in the title).
180templ CommitRows(rows []CommitRow) {
181 <table class="list commits" aria-label="Commits">
182 <tbody data-rows>
183 for _, c := range rows {
184 <tr
185 if c.ID != "" {
186 id={ c.ID }
187 }
188 data-cursor?={ c.Cursor }
189 >
190 <td class="c-sha mute">{ c.Short }</td>
191 <td class="c-subject">
192 <a href={ templ.SafeURL(c.CHref) }>{ c.Subject }</a>
193 if c.Merge {
194 <span class="pill">merge</span>
195 }
196 if c.Sig != nil {
197 @signaturePill(c.Sig)
198 }
199 for _, b := range c.Branches {
200 <span class="pill">{ b }</span>
201 }
202 for _, t := range c.Tags {
203 <span class="pill tag"><span class="sr">tag </span>{ t }</span>
204 }
205 </td>
206 <td class="c-ck">@CommitChecks(c.Checks)</td>
207 <td class="c-who dim">{ c.Author }</td>
208 <td class="c-when nowrap dim"><time datetime={ c.WhenAt } title={ c.WhenAt }>{ c.When }</time></td>
209 </tr>
210 }
211 </tbody>
212 </table>
213}
214
215// signaturePill is the badge of a signed commit: the protocol and the verdict
216// as text, the reason in the tooltip.
217templ signaturePill(b *SignatureBadge) {
218 <span class={ "pill sig", b.Class } title={ b.Title }>
219 if b.Proto != "" {
220 <span class="sig-proto">{ b.Proto }</span>
221 }
222 { b.Label }
223 </span>
224}