Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 26m ago 4b8c9e1d
history blame raw 24.3 KiB
1{{define "requests"}}
2{{template "header" .}}
3{{template "repohead" (nav .Page "changes")}}
4
5<div class="page-head">
6 <div class="page-head-main">
7 <h1>Changes</h1>
8 <p class="page-sub">Proposed updates to the project, reviewed and approved before they are merged.</p>
9 </div>
10 {{if .CanOpen}}<div class="page-head-actions"><a class="button" href="/{{.Name}}/changes/new">{{icon "plus"}}New change</a></div>{{end}}
11</div>
12
13{{template "change_list" .}}
14{{template "footer" .}}
15{{end}}
16
17{{define "change_status"}}<span class="row-status {{.Key}}" title="{{.Note}}">{{icon .Icon}}{{.Label}}</span>{{end}}
18
19{{define "change_list"}}
20<div id="change-list">
21{{/* One row: state chips on the left, search on the right. The chips live
22 inside the form so a search keeps the state, and the whole row is replaced
23 with the server's rendering after a search or a chip click. */}}
24<form method="get" action="/{{.Name}}/changes" class="filters" role="search" hx-get="/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">
25 <div class="chips" hx-boost="true" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">
26 <a class="chip{{if eq .State "open"}} active{{end}}" href="/{{.Name}}/changes?state=open{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Open <span class="chip-count">{{.Open}}</span></a>
27 <a class="chip{{if eq .State "merged"}} active{{end}}" href="/{{.Name}}/changes?state=merged{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Merged <span class="chip-count">{{.Merged}}</span></a>
28 <a class="chip{{if eq .State "closed"}} active{{end}}" href="/{{.Name}}/changes?state=closed{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Closed <span class="chip-count">{{.Closed}}</span></a>
29 <a class="chip{{if eq .State "all"}} active{{end}}" href="/{{.Name}}/changes?state=all{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">All <span class="chip-count">{{.Total}}</span></a>
30 </div>
31 <input type="hidden" name="state" value="{{.State}}">
32 <div class="filters-search">
33 <input name="q" type="search" placeholder="Search title, description and author" aria-label="Search title, description and author" value="{{.Query}}">
34 <button type="submit">{{icon "search"}}Search</button>
35 {{if .Filtered}}<a class="button secondary" href="/{{.Name}}/changes" hx-get="/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">{{icon "x"}}Clear</a>{{end}}
36 </div>
37</form>
38
39{{if .Rows}}
40<div class="panel">
41<table class="change-list">
42 <thead>
43 <tr><th>Status</th><th>Change</th><th>Base</th><th>Head</th><th>Updated</th></tr>
44 </thead>
45 <tbody>
46 {{range .Rows}}
47 <tr>
48 <td class="fit">{{template "change_status" .Status}}</td>
49 <td>
50 <a class="row-title" href="/{{$.Name}}/changes/{{.ID}}">{{.Title}}</a>
51 <div class="row-meta">
52 <span class="sha">{{num .Number}}</span>
53 <span>opened by {{template "userlink" (user .Author)}} {{since .Created.Unix}}</span>
54 {{if .CommentCount}}<span>{{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}</span>{{end}}
55 </div>
56 </td>
57 <td><code>{{.Base}}</code></td>
58 <td>{{if .HeadRef}}<code title="the branch this request follows">{{.HeadRef}}</code>{{else}}<span class="muted">pinned to</span> <span class="sha" title="this change does not follow a branch; it names one commit">{{short .Head}}</span>{{end}}</td>
59 <td class="muted nowrap">{{since .Updated.Unix}}</td>
60 </tr>
61 {{end}}
62 </tbody>
63</table>
64</div>
65{{template "pager" .Pager}}
66{{else if and (eq .State "open") .Total}}
67<div class="empty-state">
68 <p>No open changes.</p>
69</div>
70{{else if .Filtered}}
71<div class="empty-state">
72 <p>No changes match this filter.</p>
73</div>
74{{else}}
75<div class="empty-state">
76 <p>No changes yet.</p>
77 <p class="muted">From a clone of this repository:</p>
78 <pre>vole change new --title "..." --base {{.Base}}</pre>
79 <p class="muted">A change is also just a directory on the <code>vole</code> branch:
80 <code>changes/&lt;ulid&gt;/change.md</code> naming the base branch and the head
81 commit, plus <code>approvals/&lt;account-id&gt;.md</code> from each approver.</p>
82</div>
83{{end}}
84</div>
85{{end}}
86
87{{/* A `?` beside a section heading whose explanation would otherwise be a
88 paragraph in the panel. The text is both the tooltip and the accessible
89 name, so a pointer, a keyboard and a screen reader get the same words. */}}
90{{define "help"}}<span class="help" tabindex="0" role="note" aria-label="{{.}}" data-tip="{{.}}">?</span>{{end}}
91
92{{define "request_changes"}}
93 <section id="request-changes"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
94 <h2 class="with-more"><span>{{icon "file"}}Changes</span>{{if .FileCount}} <a class="more" href="/{{.Name}}/changes/{{.Request.ID}}/files">View changes</a>{{end}}</h2>
95 {{if .FileCount}}
96 <p class="muted">{{.FileCount}} file(s) changed{{if .Added}} · <span class="adds">+{{.Added}}</span>{{end}}{{if .Removed}} · <span class="dels">−{{.Removed}}</span>{{end}}</p>
97 {{else if .AlreadyMerged}}
98 <p class="muted">Already merged into {{.Request.Base}}.</p>
99 {{else}}
100 <p class="muted">No changes, or the head is not reachable from this repository yet.</p>
101 {{end}}
102 </section>
103{{end}}
104
105{{/* The aside is the page's live coordinator. The poll never replaces this element:
106 it returns out-of-band updates for the data-only regions, so a search, a
107 target picker, or an open composer is not thrown away. */}}
108{{define "request_aside"}}
109<div id="request-aside"
110 hx-get="/{{.Name}}/changes/{{.Request.ID}}/live"
111 hx-trigger="every 20s [document.visibilityState === 'visible']"
112 hx-sync="this:replace"
113 hx-swap="none">
114 {{template "request_sidebar" .}}
115</div>
116{{end}}
117
118{{define "request_sidebar"}}
119 <div id="request-sidebar">
120 <section id="request-review">
121 {{template "request_review_status" .}}
122 {{template "request_target" .}}
123 </section>
124
125 {{template "request_changes" .}}
126
127 {{template "request_issues" .}}
128
129 {{template "request_approvals" .}}
130
131 {{template "request_checks" .}}
132
133 {{template "request_combined" .}}
134 </div>
135{{end}}
136
137{{define "request_alert"}}
138<div id="request-alert"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
139 {{if .Error}}<p class="error">{{.Error}}</p>{{end}}
140 {{if .Notice}}<p class="muted">{{.Notice}}</p>{{end}}
141</div>
142{{end}}
143
144{{define "request_live"}}
145{{template "request_review_status" .}}
146{{template "request_changes" .}}
147{{template "request_approvals" .}}
148{{template "request_checks" .}}
149{{template "request_combined" .}}
150{{template "request_merge_note" .}}
151{{end}}
152
153{{define "request_live_write"}}
154{{template "request_alert" .}}
155{{template "request_actions" .}}
156{{template "request_live" .}}
157{{end}}
158
159{{define "request_review_status"}}
160<div id="request-review-status"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
161 <h2 class="with-more"><span>{{icon "git-branch"}}Review{{template "help" "The request follows its head branch, so a push moves the head with it. Changing the base or head starts approvals over. A conflict here blocks landing."}}</span> <a class="more sha" href="/{{.Name}}/commit/{{.Request.Head}}" title="commit under review">{{short .Request.Head}}</a></h2>
162 {{if .MergeChecked}}{{if .Conflicts}}
163 <p class="conflict">This branch no longer merges into {{.Request.Base}} cleanly:
164 {{range .Conflicts}}<code>{{.}}</code> {{end}}— rebase or merge {{.Request.Base}} before landing it.</p>
165 {{else}}
166 <p class="muted">Merges into {{.Request.Base}} cleanly.</p>
167 {{end}}{{end}}
168</div>
169{{end}}
170
171{{define "request_target"}}
172 {{if .CanRetarget}}
173 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/target" class="request-target">
174 <input type="hidden" name="csrf" value="{{.CSRF}}">
175 <div class="target-fields">
176 <label class="target-field">
177 <span class="target-label">Base branch</span>
178 <select name="base">
179 {{range .BaseOptions}}<option value="{{.Name}}"{{if .Selected}} selected{{end}}>{{.Name}}</option>{{end}}
180 </select>
181 </label>
182 <label class="target-field">
183 <span class="target-label">Head branch</span>
184 <select name="head">
185 {{if not .Request.HeadRef}}<option value="" selected>pinned commit</option>{{end}}
186 {{range .HeadOptions}}<option value="{{.Name}}"{{if .Selected}} selected{{end}}>{{.Name}}</option>{{end}}
187 </select>
188 </label>
189 </div>
190 <div class="target-actions">
191 {{if .HeadRefNote}}<p class="muted small">{{.HeadRefNote}}.</p>{{end}}
192 <button class="secondary" type="submit" title="Update base and head" aria-label="Update base and head">{{icon "rotate-ccw"}}Update</button>
193 </div>
194 </form>
195 {{else}}
196 <p class="target-summary">
197 <code>{{.Request.Base}}</code>
198 <span class="muted" aria-hidden="true">←</span>
199 {{if .Request.HeadRef}}<code>{{.Request.HeadRef}}</code>{{else}}<span class="muted">pinned commit</span>{{end}}
200 </p>
201 {{if .HeadRefNote}}<p class="muted">{{.HeadRefNote}}.</p>{{end}}
202 {{end}}
203{{end}}
204
205{{define "request_approvals"}}
206 <section id="request-approvals"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
207 <h2>{{icon "check"}}Approvals{{template "help" "Approving names the current base and head; a new base or head starts over. An approval stops counting if its author is retired or no longer maintains the repository."}}</h2>
208 <p class="muted">{{if .Required}}{{.Current}} of {{.Required}} required approval(s).{{else}}No approvals required.{{end}}</p>
209 {{if .Approvals}}
210 <ul class="approval-list">
211 {{range .Approvals}}
212 <li>
213 <span class="cname">{{.Name}}</span>
214 <span class="muted">{{if .Counts}}current{{else if .Current}}no longer counts{{else}}stale{{end}}</span>
215 </li>
216 {{end}}
217 </ul>
218 {{end}}
219 </section>
220{{end}}
221
222{{define "request_checks"}}
223 <section id="request-checks"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
224 <h2 class="with-more"><span>{{icon "circle-check"}}Checks{{template "help" "Workflow runs for the commit under review. If the base branch requires checks, a failing or missing run blocks landing."}}</span> <a class="more" href="/{{.Name}}/changes/{{.Request.ID}}/runs">View runs</a></h2>
225 {{with .ContributorCI}}
226 <p class="muted">CI is waiting for a maintainer to allow <code>{{.Ref}}</code> at this commit.</p>
227 {{end}}
228 {{if .Checks}}
229 <div class="side-list">
230 {{range .Checks}}
231 <a class="side-run" href="/{{$.Name}}/runs/{{.ID}}">
232 <span class="side-run-top">
233 <span class="status {{.Status}}">{{.Status}}</span>
234 <span class="side-subject">{{.JobTitle}}</span>
235 </span>
236 <span class="side-meta">{{if ne .JobTitle .WorkflowTitle}}<span>{{.WorkflowTitle}}</span>{{end}}<span>{{if not .Started.IsZero}}{{duration .Duration}}{{else}}{{duration .Wait}} queued{{end}}</span><span>{{since .Created.Unix}}</span></span>
237 </a>
238 {{end}}
239 </div>
240 {{else if not .ContributorCI}}
241 <p class="muted">No CI run for this commit yet.</p>
242 {{end}}
243 </section>
244{{end}}
245
246{{define "request_combined"}}
247 {{if .CombinedNeeded}}
248 <section id="request-combined"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
249 <h2 class="with-more"><span>{{icon "git-merge"}}Combined result{{template "help" "The merge that would land, built from this change and the latest base. It is tested automatically so the branch is ready when the review is. Only a conflict needs work on the branch."}}</span> {{if .CombinedSHA}}<a class="more sha" href="/{{.Name}}/commit/{{.CombinedSHA}}" title="combined result commit">{{short .CombinedSHA}}</a>{{end}}</h2>
250 {{if eq .CombinedState "passed"}}
251 <p class="muted">Passed with the latest <code>{{.Request.Base}}</code>.</p>
252 {{else if eq .CombinedState "testing"}}
253 <p class="muted">Testing the merge with the latest <code>{{.Request.Base}}</code>…</p>
254 {{else if eq .CombinedState "failed"}}
255 <p class="conflict">The combined result with <code>{{.Request.Base}}</code> failed. The runs below name the job.</p>
256 {{else if eq .CombinedState "conflict"}}
257 <p class="conflict">This change conflicts with the latest <code>{{.Request.Base}}</code>. Resolve it on the branch and push again.</p>
258 {{end}}
259 {{if .CombinedRuns}}
260 <div class="side-list">
261 {{range .CombinedRuns}}
262 <a class="side-run" href="/{{$.Name}}/runs/{{.ID}}">
263 <span class="side-run-top">
264 <span class="status {{.Status}}">{{.Status}}</span>
265 <span class="side-subject">{{.JobTitle}}</span>
266 </span>
267 <span class="side-meta">{{if ne .JobTitle .WorkflowTitle}}<span>{{.WorkflowTitle}}</span>{{end}}<span>{{if not .Started.IsZero}}{{duration .Duration}}{{else}}{{duration .Wait}} queued{{end}}</span><span>{{since .Created.Unix}}</span></span>
268 </a>
269 {{end}}
270 </div>
271 {{end}}
272 </section>
273 {{else if .Live}}
274 <section id="request-combined" hx-swap-oob="outerHTML" hidden></section>
275 {{end}}
276{{end}}
277
278{{define "request_issues"}}
279<section id="request-issues">
280 <h2>{{icon "circle-dot"}}Issues{{template "help" "Issues linked to this change close when it is merged. An open change can be relinked by its author or a maintainer."}}</h2>
281 {{if .LinkedIssues}}
282 <div class="side-list">
283 {{range .LinkedIssues}}
284 <div class="request-issue">
285 <a class="side-issue" href="/{{$.Name}}/issues/{{.ID}}">
286 <span class="side-subject"><span class="side-num">{{num .Number}}</span>{{.Title}}</span>
287 <span class="side-meta">{{who .Author}}{{if not .Created.IsZero}}<span>{{since .Created.Unix}}</span>{{end}}</span>
288 </a>
289 {{if $.CanEditIssues}}
290 <form method="post" action="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-post="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
291 <input type="hidden" name="csrf" value="{{$.CSRF}}">
292 <input type="hidden" name="issue" value="{{.ID}}">
293 <button class="link request-unlink" type="submit" name="action" value="remove" title="Unlink this issue" aria-label="Unlink {{num .Number}} from this change">{{icon "unlink"}}</button>
294 </form>
295 {{end}}
296 </div>
297 {{end}}
298 </div>
299 {{else}}
300 <p class="muted">No linked issues.</p>
301 {{end}}
302 {{if .CanEditIssues}}
303 <div class="request-issue-picker">
304 <form class="request-issue-search" hx-get="/{{.Name}}/changes/{{.Request.ID}}/issues/search" hx-trigger="input changed delay:250ms, search, submit" hx-target="#request-issue-results" hx-swap="innerHTML">
305 <input id="request-issue-search" name="q" type="search" placeholder="Search by title, #number, or ULID" aria-label="Search issues by title, number, or ULID" autocomplete="off">
306 </form>
307 <div id="request-issue-results" class="request-issue-results" aria-live="polite"></div>
308 </div>
309 {{end}}
310 {{if .IssuesError}}<p class="error">{{.IssuesError}}</p>{{end}}
311</section>
312{{end}}
313
314{{define "request_issue_search"}}
315<div class="request-issue-popover">
316{{if .Error}}
317<p class="error">{{.Error}}</p>
318{{else if .Results}}
319<div class="request-issue-matches">
320 {{range .Results}}
321 <form method="post" action="/{{$.Name}}/changes/{{$.Request.ID}}/issues" class="request-issue-match" hx-post="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
322 <input type="hidden" name="csrf" value="{{$.CSRF}}">
323 <input type="hidden" name="issue" value="{{.ID}}">
324 <button type="submit" name="action" value="add" title="Link {{num .Number}}">
325 <span class="side-subject"><span class="side-num">{{num .Number}}</span>{{.Title}}</span>
326 <span class="side-meta">{{who .Author}}{{if not .Created.IsZero}}<span>{{since .Created.Unix}}</span>{{end}}</span>
327 </button>
328 </form>
329 {{end}}
330</div>
331{{else if .Query}}
332<p class="muted">No issues match “{{.Query}}”.</p>
333{{end}}
334</div>
335{{end}}
336
337{{define "request_inline_comment"}}
338<tr class="inline-comment">
339 <td colspan="4">
340 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/comments" class="inline-comment-form">
341 <input type="hidden" name="csrf" value="{{.CSRF}}">
342 <input type="hidden" name="path" value="{{.Target.Path}}">
343 <input type="hidden" name="line" value="{{.Target.Line}}">
344 <input type="hidden" name="side" value="{{.Target.Side}}">
345 <div class="inline-comment-card">
346 <div class="inline-comment-head">
347 <span>Comment on <code>{{.Target.Path}}:{{.Target.Line}}</code> <span class="muted">({{.Target.Side}} side)</span></span>
348 <button class="link" type="button" data-inline-comment-cancel>Cancel</button>
349 </div>
350 <textarea name="body" required autofocus placeholder="Leave an inline comment. Markdown is welcome." aria-label="Inline comment"></textarea>
351 </div>
352 <div class="form-actions"><button type="submit">{{icon "message-square"}}Comment</button></div>
353 </form>
354 </td>
355</tr>
356{{end}}
357
358{{define "request_title_head"}}
359<div class="page-head" id="request-head">
360 <div class="page-head-main">
361 <div class="details-title">
362 <span class="page-num">{{num .Number}}</span>
363 <h1 class="details-title-view" data-details-title-view{{if .Details.Title.Active}} hidden{{end}}>{{.Request.Title}}</h1>
364 {{if .CanEdit}}
365 <a class="title-edit-toggle" href="/{{.Name}}/changes/{{.Request.ID}}/edit"{{if .Details.Inline}} data-title-edit{{end}}{{if .Details.Title.Active}} hidden{{end}} aria-label="Edit title" title="Edit title">{{icon "pencil"}}</a>
366 {{if .Details.Inline}}
367 <form class="title-edit-form" data-title-edit-form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-head" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Title.Active}} hidden{{end}}>
368 <input type="hidden" name="csrf" value="{{.CSRF}}">
369 <input type="hidden" name="field" value="title">
370 <input class="details-title-input" type="text" name="title" required maxlength="200" aria-label="Title" value="{{if .Details.Title.Active}}{{.Details.Title.Value}}{{else}}{{.Request.Title}}{{end}}" data-title-edit-input{{if .Details.Title.Active}} autofocus{{end}}>
371 <button class="secondary" type="button" data-title-edit-cancel>Cancel</button>
372 <button type="submit">{{icon "check"}}Save</button>
373 </form>
374 {{end}}
375 {{end}}
376 </div>
377 {{if .Details.Title.Error}}<p class="error">{{.Details.Title.Error}}</p>{{end}}
378 <p class="page-sub">
379 <span class="state {{.Request.State}}">{{.Request.State}}</span>
380 · by {{template "userlink" (user .Request.Author)}} {{since .Request.Created.Unix}}
381 {{if eq .Request.State "merged"}}
382 · merged into <code>{{.Request.Base}}</code> at <span class="sha">{{short .Request.MergedHead}}</span>
383 by {{template "userlink" (user .Request.MergedBy)}} {{since .Request.MergedAt.Unix}}
384 {{end}}
385 </p>
386 </div>
387 {{template "request_actions" .}}
388 {{template "request_merge_note" .}}
389</div>
390{{end}}
391
392{{define "request_head"}}
393{{template "request_title_head" .}}
394{{template "request_alert" .}}
395{{end}}
396
397{{define "request_merge_note"}}
398<p id="request-merge-note" class="muted small"{{if .Live}} hx-swap-oob="outerHTML"{{end}}{{if not .MergeBlocked}} hidden{{end}}>{{.MergeBlocked}}</p>
399{{end}}
400
401{{define "request_actions"}}
402{{if or .CanApprove .CanClose .CanMerge .MergeArmed .ContributorCI .Live}}
403<div id="request-actions" class="page-head-actions"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
404 {{with .ContributorCI}}{{if .CanAllow}}
405 <form method="post" action="/{{$.Name}}/ci/allow">
406 <input type="hidden" name="csrf" value="{{$.CSRF}}">
407 <input type="hidden" name="request" value="{{$.Request.ID}}">
408 <input type="hidden" name="ref" value="refs/heads/{{.Ref}}">
409 <button class="warn" type="submit">{{icon "play"}}Allow CI</button>
410 </form>
411 {{end}}{{end}}
412 {{if .CanApprove}}
413 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/approve" hx-post="/{{.Name}}/changes/{{.Request.ID}}/approve" hx-swap="none" hx-disabled-elt="find button[type='submit']">
414 <input type="hidden" name="csrf" value="{{.CSRF}}">
415 <input type="hidden" name="base" value="{{.Request.Base}}">
416 <input type="hidden" name="head" value="{{.Request.Head}}">
417 <button class="success" type="submit">{{icon "check"}}Approve</button>
418 </form>
419 {{end}}
420 {{if .MergeArmed}}
421 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/merge/cancel">
422 <input type="hidden" name="csrf" value="{{.CSRF}}">
423 <button class="secondary" type="submit">{{icon "circle-x"}}Cancel merge</button>
424 </form>
425 {{else if .CanMerge}}
426 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/merge">
427 <input type="hidden" name="csrf" value="{{.CSRF}}">
428 <button class="success" type="submit"{{if .MergeBlocked}} disabled{{end}}>{{icon "git-merge"}}{{if .MergePending}}Merge when ready{{else}}Merge{{end}}</button>
429 </form>
430 {{end}}
431 {{if .CanClose}}
432 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/state">
433 <input type="hidden" name="csrf" value="{{.CSRF}}">
434 <input type="hidden" name="state" value="{{if eq .Request.State "open"}}closed{{else}}open{{end}}">
435 <button class="{{if eq .Request.State "open"}}danger{{else}}secondary{{end}}" type="submit">{{if eq .Request.State "open"}}{{icon "circle-x"}}Close{{else}}{{icon "rotate-ccw"}}Reopen{{end}}</button>
436 </form>
437 {{end}}
438</div>
439{{end}}
440{{end}}
441
442{{define "request_tabs"}}
443<div class="tabrow subtabs">
444 <nav class="tabs" aria-label="Change sections">
445 <a class="{{if eq .Tab "conversation"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}"{{if eq .Tab "conversation"}} aria-current="page"{{end}}>{{icon "message-square"}}Conversation</a>
446 <a class="{{if eq .Tab "files"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}/files"{{if eq .Tab "files"}} aria-current="page"{{end}}>{{icon "file"}}Changes <span class="chip-count">{{.FileCount}}</span></a>
447 <a class="{{if eq .Tab "runs"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}/runs"{{if eq .Tab "runs"}} aria-current="page"{{end}}>{{icon "play"}}Runs <span class="chip-count">{{len .Checks}}</span></a>
448 </nav>
449</div>
450{{end}}
451
452{{define "request_description"}}
453<article class="comment{{if .Details.Description.Active}} editing{{end}}" id="request-description">
454 <header class="comment-head">
455 {{$u := user .Request.Author}}
456 <span class="comment-author">{{template "userlink" $u}}</span>
457 <span class="muted">opened this {{since .Request.Created.Unix}}</span>
458 <div class="comment-menu" data-comment-menu>
459 <button type="button" class="comment-menu-toggle" data-comment-menu-toggle aria-haspopup="menu" aria-expanded="false" aria-label="Description actions">&#8943;</button>
460 <div class="comment-menu-list" data-comment-menu-list role="menu" hidden>
461 <button type="button" role="menuitem" data-copy-query="{{urlquery .Request.Description}}">Copy text</button>
462 {{if .CanEdit}}<button type="button" role="menuitem" data-comment-edit>Edit</button>{{end}}
463 </div>
464 </div>
465 </header>
466 <div class="comment-body md">
467 {{if .Description}}{{.Description}}{{else}}<p class="muted">No description.</p>{{end}}
468 </div>
469 {{if .CanEdit}}
470 <form class="comment-edit-form details-edit-form" data-comment-edit-form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-description" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Description.Active}} hidden{{end}}>
471 <input type="hidden" name="csrf" value="{{.CSRF}}">
472 <input type="hidden" name="field" value="body">
473 {{if .Details.Description.Error}}<p class="error">{{.Details.Description.Error}}</p>{{end}}
474 <textarea name="body" data-autosize aria-label="Description">{{if .Details.Description.Active}}{{.Details.Description.Value}}{{else}}{{.Request.Description}}{{end}}</textarea>
475 <div class="form-actions">
476 <button class="secondary" type="button" data-comment-edit-cancel>Cancel</button>
477 <button type="submit">{{icon "check"}}Save</button>
478 </div>
479 </form>
480 {{end}}
481</article>
482{{end}}
483
484{{define "request"}}
485{{template "header" .}}
486{{template "repohead" (nav .Page "changes")}}
487{{template "request_head" .}}
488{{template "request_tabs" .}}
489
490<div class="thread">
491 <div class="thread-main">
492 {{template "request_description" .}}
493
494 {{template "comment_thread" .Thread}}
495 </div>
496
497 <aside class="thread-side">
498 {{template "request_aside" .}}
499 </aside>
500</div>
501{{template "footer" .}}
502{{end}}
503