pages.templ RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Home is the repository list.
6templ Home(l Layout, p HomePage) {
7 @Shell(l, nil) {
8 <div class="filter">
9 <label for="home-filter" class="sr">filter</label>
10 <input
11 id="home-filter"
12 type="search"
13 data-filter="#repo-rows > tr"
14 placeholder="Filter repositories…"
15 autocomplete="off"
16 hx-on:input="ermite.filter(this)"
17 />
18 </div>
19 <table class="list home" aria-label="Repositories">
20 <thead>
21 <tr>
22 <th class="cur"></th>
23 for _, c := range p.Cols {
24 <th class={ c.Class } aria-sort={ c.Dir }>
25 <a href={ templ.SafeURL(c.Href) }>
26 { c.Label }
27 if c.Mark != "" {
28 <span aria-hidden="true">{ c.Mark }</span>
29 }
30 </a>
31 </th>
32 }
33 </tr>
34 </thead>
35 <tbody id="repo-rows" data-rows>
36 for _, r := range p.Rows {
37 @HomeRowView(r)
38 }
39 </tbody>
40 </table>
41 if len(p.Rows) == 0 {
42 <p class="lede dim">no repositories yet.</p>
43 }
44 }
45}
46
47// Summary is the summary of a repository: the README reads in the main
48// column at the reading measure; what one acts on sits in the rail.
49templ Summary(l Layout, p SummaryPage) {
50 @Shell(l, summaryRail(p)) {
51 if p.Readme != nil {
52 @ReadmeCard(*p.Readme)
53 }
54 }
55}
56
57templ summaryRail(p SummaryPage) {
58 <section aria-labelledby="r-clone"><h2 id="r-clone">Clone</h2>@CloneBlock(p.Clone)
59</section>
60 <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts)
61</section>
62 if p.Latest != nil {
63 <section aria-labelledby="r-tag">
64 <h2 id="r-tag">Tags <span class="count">{ itoa(p.Latest.Count) }</span> <a href={ templ.SafeURL(p.Latest.RefsHref) }>View all</a></h2>
65 if p.Latest.Name != "" {
66 <div class="latest-tag">
67 <a class="tag-name" href={ templ.SafeURL(p.Latest.Href) }><span class="ico i-tag" aria-hidden="true"></span>{ p.Latest.Name }</a>
68 <span class="pill">latest</span>
69 <time class="dim" datetime={ p.Latest.WhenAt } title={ p.Latest.WhenAt }>{ p.Latest.When }</time>
70 if p.Latest.Subject != "" {
71 <p class="tag-sub dim">{ p.Latest.Subject }</p>
72 }
73 </div>
74 } else {
75 <p class="dim">{ plural(p.Latest.Count, "tag", "tags") }</p>
76 }
77 </section>
78 }
79 if len(p.Recent) > 0 {
80 <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent)
81</section>
82 }
83 if len(p.Outline) > 0 && p.Readme != nil {
84 <section aria-labelledby="r-outline"><h2 id="r-outline">On this page</h2>@Outline(p.Outline)
85</section>
86 }
87}
88
89// Refs lists the branches and tags of a repository.
90templ Refs(l Layout, p RefsPage) {
91 @Shell(l, refsRail(p)) {
92 <div class="filter">
93 <label for="ref-filter" class="sr">filter</label>
94 <input
95 id="ref-filter"
96 type="search"
97 data-filter="#ref-rows > tr"
98 placeholder="Filter branches and tags…"
99 autocomplete="off"
100 hx-on:input="ermite.filter(this)"
101 />
102 </div>
103 <table class="list refs" aria-label="Branches and tags">
104 <thead>
105 <tr><th class="cur"></th><th class="c-kind">kind</th><th class="c-name">name</th><th class="c-sha">commit</th><th class="c-sub">subject</th><th class="c-when">date</th></tr>
106 </thead>
107 @RefsBody(p)
108 </table>
109 if p.Note != "" {
110 <p class="note">{ p.Note }</p>
111 }
112 }
113}
114
115templ refRow(kind string, r RefRow) {
116 <tr data-cursor?={ r.Cursor }>
117 <td class="cur" aria-hidden="true"></td>
118 <td class="c-kind mute">{ kind }</td>
119 <td class="c-name">
120 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
121 if r.Default {
122 <span class="pill">default</span>
123 }
124 </td>
125 <td class="c-sha">
126 if r.CHref != "" {
127 <a href={ templ.SafeURL(r.CHref) }>{ r.Short }</a>
128 } else {
129 { r.Short }
130 }
131 @CommitChecks(r.Checks)
132 </td>
133 <td class="c-sub">{ r.Subject }</td>
134 <td class="c-when dim">
135 if r.WhenAt != "" {
136 <time datetime={ r.WhenAt } title={ r.WhenAt }>{ r.When }</time>
137 }
138 </td>
139 </tr>
140}
141
142templ refsRail(p RefsPage) {
143 <section aria-labelledby="r-refs">
144 <h2 id="r-refs">Overview</h2>
145 @RefsKV(p)
146 </section>
147}
148
149// State is a 404, 500 or empty-repository page inside the normal shell.
150templ State(l Layout, p StatePage) {
151 @Shell(l, nil) {
152 <section class="state" aria-labelledby="state-title">
153 <p class="eyebrow">{ p.Code }</p>
154 <h1 id="state-title">{ p.Title }</h1>
155 for _, line := range p.Lines {
156 <p class="dim">{ line }</p>
157 }
158 if p.Clone != nil {
159 @CloneBlock(*p.Clone)
160 }
161 <p class="state-back">
162 for _, b := range p.Back {
163 <a href={ templ.SafeURL(b.Href) }>{ b.Label }</a>
164 }
165 </p>
166 </section>
167 }
168}
169
170// Login asks the owner to confirm a login link. Opening the link consumes
171// nothing; only this form does. It is a state page with a button.
172templ Login(l Layout, p LoginPage) {
173 @Shell(l, nil) {
174 <section class="state" aria-labelledby="state-title">
175 <h1 id="state-title">{ p.Title }</h1>
176 for _, line := range p.Lines {
177 <p class="dim">{ line }</p>
178 }
179 <form class="state-act" method="post" action={ templ.SafeURL(p.Action) }>
180 <button type="submit" class="btn primary">{ p.Button }</button>
181 </form>
182 <p class="state-back"><a href="/">Back to home</a></p>
183 </section>
184 }
185}
186
187// PasskeyLogin is the sign-in page: a button the script (passkey.js) drives.
188// Without script the button is hidden and the page says what to do instead.
189templ PasskeyLogin(l Layout, p PasskeyLoginPage) {
190 @Shell(l, nil) {
191 <section class="state" aria-labelledby="state-title">
192 <h1 id="state-title">{ p.Title }</h1>
193 <form class="state-act" data-js data-passkey-form>
194 <button type="submit" class="btn primary" data-passkey="login" data-begin={ p.Begin } data-finish={ p.Finish }>{ p.Button }</button>
195 </form>
196 <p class="dim" role="status" aria-live="polite" data-passkey-status></p>
197 for _, line := range p.Lines {
198 <p class="dim">{ line }</p>
199 }
200 <p class="state-back"><a href="/">Back to home</a></p>
201 </section>
202 }
203}
204
205// Passkeys lists the owner's passkeys, removes them and adds one.
206templ Passkeys(l Layout, p PasskeysPage) {
207 @Shell(l, nil) {
208 <h2 class="rule-h"><span>Passkeys</span><span class="count">{ itoa(len(p.Rows)) }</span></h2>
209 <p class="dim">A passkey signs you in from this device or your phone, without a terminal. Removing one signs every browser out.</p>
210 if len(p.Rows) > 0 {
211 <table class="list passkeys" aria-label="Passkeys">
212 <thead>
213 <tr>
214 <th class="cur"></th>
215 <th class="c-name">Name</th>
216 <th class="c-when">Created</th>
217 <th class="c-when">Last used</th>
218 <th class="c-sync">Synced</th>
219 <th class="c-act"><span class="sr">Remove</span></th>
220 </tr>
221 </thead>
222 <tbody>
223 for _, r := range p.Rows {
224 <tr>
225 <td class="cur" aria-hidden="true"></td>
226 <td class="c-name">{ r.Name }</td>
227 <td class="c-when nowrap">
228 <time
229 if r.CreatedAt != "" {
230 datetime={ r.CreatedAt }
231 }
232 >{ r.Created }</time>
233 </td>
234 <td class="c-when nowrap">
235 <time
236 if r.LastUsedAt != "" {
237 datetime={ r.LastUsedAt }
238 }
239 >{ r.LastUsed }</time>
240 </td>
241 <td class="c-sync">
242 if r.Synced {
243 yes
244 } else {
245 no
246 }
247 </td>
248 <td class="c-act">
249 <form method="post" action={ templ.SafeURL(r.Remove) }>
250 <button type="submit" class="btn danger" aria-label={ "Remove passkey " + r.Name }>Remove</button>
251 </form>
252 </td>
253 </tr>
254 }
255 </tbody>
256 </table>
257 } else {
258 <p class="lede dim">no passkeys yet.</p>
259 }
260 if p.Full {
261 <p class="dim">{ itoa(p.Max) } passkeys are registered, the most there can be. Remove one to add another.</p>
262 } else {
263 <form class="passkey-add" data-js data-passkey-form>
264 <label for="passkey-name" class="sr">Name of the new passkey</label>
265 <input id="passkey-name" type="text" placeholder="Name, for example Pixel 9" autocomplete="off" data-passkey-name/>
266 <button type="submit" class="btn primary" data-passkey="register" data-begin={ p.Begin } data-finish={ p.Finish }>Add a passkey</button>
267 </form>
268 }
269 <p class="dim" role="status" aria-live="polite" data-passkey-status></p>
270 }
271}