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 · 25m ago 4b8c9e1d
history blame raw 12.8 KiB
1{{define "issues"}}
2{{template "header" .}}
3{{template "repohead" (nav .Page "issues")}}
4
5<div class="page-head">
6 <div class="page-head-main">
7 <h1>Issues</h1>
8 <p class="page-sub">Problems and suggestions from the people who use this project.</p>
9 </div>
10 {{if .CanDiscuss}}<div class="page-head-actions"><a class="button" href="/{{.Name}}/issues/new">{{icon "plus"}}New issue</a></div>{{end}}
11</div>
12
13{{template "issue_list" .}}
14{{template "footer" .}}
15{{end}}
16
17{{/* The list's status is the same shape as a change's: a reader scanning two
18 lists should not have to learn two visual languages. */}}
19{{define "issue_status"}}{{if eq . "open"}}<span class="row-status open">{{icon "circle-dot"}}Open</span>{{else}}<span class="row-status closed">{{icon "circle-check"}}Closed</span>{{end}}{{end}}
20
21{{define "issue_list"}}
22<div id="issue-list">
23{{/* One row: the state chips and the dropdowns on the left, the search field on
24 the right. The form wraps the chips so a search submits the state too, and
25 so a swap replaces the row with the values the server rendered. */}}
26<form method="get" action="/{{.Name}}/issues" class="filters" role="search" hx-get="/{{.Name}}/issues" hx-target="#issue-list" hx-swap="outerHTML" hx-push-url="true">
27 <div class="chips" hx-boost="true" hx-target="#issue-list" hx-swap="outerHTML" hx-push-url="true">
28 <a class="chip{{if eq .State "open"}} active{{end}}" href="/{{.Name}}/issues?state=open{{if .Filter.Query}}&amp;q={{urlquery .Filter.Query}}{{end}}{{if .Filter.Label}}&amp;label={{urlquery .Filter.Label}}{{end}}{{if .Filter.Assignee}}&amp;assignee={{urlquery .Filter.Assignee}}{{end}}">Open <span class="chip-count">{{.Open}}</span></a>
29 <a class="chip{{if eq .State "closed"}} active{{end}}" href="/{{.Name}}/issues?state=closed{{if .Filter.Query}}&amp;q={{urlquery .Filter.Query}}{{end}}{{if .Filter.Label}}&amp;label={{urlquery .Filter.Label}}{{end}}{{if .Filter.Assignee}}&amp;assignee={{urlquery .Filter.Assignee}}{{end}}">Closed <span class="chip-count">{{.Closed}}</span></a>
30 <a class="chip{{if eq .State "all"}} active{{end}}" href="/{{.Name}}/issues?state=all{{if .Filter.Query}}&amp;q={{urlquery .Filter.Query}}{{end}}{{if .Filter.Label}}&amp;label={{urlquery .Filter.Label}}{{end}}{{if .Filter.Assignee}}&amp;assignee={{urlquery .Filter.Assignee}}{{end}}">All <span class="chip-count">{{.Total}}</span></a>
31 </div>
32 <input type="hidden" name="state" value="{{.State}}">
33 {{if .Labels}}
34 <label class="filter-field">
35 <span class="filter-label">Label</span>
36 <select name="label" data-autosubmit>
37 <option value="">All</option>
38 {{range .Labels}}<option value="{{.}}"{{if eq . $.Filter.Label}} selected{{end}}>{{.}}</option>{{end}}
39 </select>
40 </label>
41 {{end}}
42 {{if .Assignees}}
43 <label class="filter-field">
44 <span class="filter-label">Assignee</span>
45 <select name="assignee" data-autosubmit>
46 <option value="">All</option>
47 {{range .Assignees}}<option value="{{.}}"{{if eq . $.Filter.Assignee}} selected{{end}}>@{{.}}</option>{{end}}
48 </select>
49 </label>
50 {{end}}
51 <div class="filters-search">
52 <input name="q" type="search" placeholder="Search title and body" aria-label="Search title and body" value="{{.Filter.Query}}">
53 <button type="submit">{{icon "search"}}Search</button>
54 {{if .Filtered}}<a class="button secondary" href="/{{.Name}}/issues" hx-get="/{{.Name}}/issues" hx-target="#issue-list" hx-swap="outerHTML" hx-push-url="true">{{icon "x"}}Clear</a>{{end}}
55 </div>
56</form>
57
58{{if .Issues}}
59<div class="panel">
60<table class="issue-list">
61 <thead>
62 <tr><th>Status</th><th>Issue</th><th>Labels</th><th>Assignees</th><th>Updated</th></tr>
63 </thead>
64 <tbody>
65 {{range .Issues}}
66 <tr>
67 <td class="fit">{{template "issue_status" .State}}</td>
68 <td>
69 <a class="row-title" href="/{{$.Name}}/issues/{{.ID}}">{{.Title}}</a>
70 <div class="row-meta">
71 <span class="sha">{{num .Number}}</span>
72 <span>opened by {{template "userlink" (user .Author)}} {{since .Created.Unix}}</span>
73 {{if .Comments}}<span>{{.Comments}} comment{{if ne .Comments 1}}s{{end}}</span>{{end}}
74 </div>
75 </td>
76 <td><div class="label-row">{{range .Labels}}<span class="label">{{.}}</span>{{else}}<span class="muted">—</span>{{end}}</div></td>
77 <td class="nowrap">{{range $i, $a := .Assignees}}{{if $i}}, {{end}}{{template "userlink" (user $a)}}{{else}}<span class="muted">—</span>{{end}}</td>
78 <td class="muted nowrap">{{since .Updated.Unix}}</td>
79 </tr>
80 {{end}}
81 </tbody>
82</table>
83</div>
84{{template "pager" .Pager}}
85{{else if and (eq .State "open") .Closed}}
86<div class="empty-state">
87 <p>No open issues.</p>
88</div>
89{{else if .Filtered}}
90<div class="empty-state">
91 <p>No issues match this filter.</p>
92</div>
93{{else}}
94<div class="empty-state">
95 <p>No issues yet.</p>
96 <p class="muted">From a clone of this repository:</p>
97 <pre>vole issue new --title "..." -m "..."</pre>
98 <p class="muted">An issue is also just a file on the <code>vole</code> branch:
99 <code>issues/&lt;ulid&gt;/issue.md</code> — frontmatter with the title, state and labels,
100 then the body as markdown.</p>
101</div>
102{{end}}
103</div>
104{{end}}
105
106{{define "issue_labels"}}
107<section id="issue-labels">
108 <h2>{{icon "tag"}}Labels</h2>
109 {{if .Error}}<p class="error">{{.Error}}</p>{{end}}
110 {{if .CanEditLabels}}
111 <form method="post" action="/{{.Name}}/issues/{{.Issue.ID}}/labels" class="label-form" hx-post="/{{.Name}}/issues/{{.Issue.ID}}/labels" hx-target="#issue-labels" hx-swap="outerHTML" hx-trigger="token-change" hx-sync="this:replace">
112 <input type="hidden" name="csrf" value="{{.CSRF}}">
113 {{template "token_input" (tokenPicker .Issue.Labels .AllLabels "label" "Add labels…")}}
114 </form>
115 {{else}}
116 {{if .Issue.Labels}}
117 <div class="label-row">{{range .Issue.Labels}}<span class="label">{{.}}</span>{{end}}</div>
118 {{else}}
119 <p class="muted">No labels.</p>
120 {{end}}
121 {{end}}
122</section>
123{{end}}
124
125{{define "issue_assignees"}}
126<section id="issue-assignees">
127 <h2>{{icon "users"}}Assignees</h2>
128 {{if .Error}}<p class="error">{{.Error}}</p>{{end}}
129 {{if .CanEditAssignees}}
130 <form method="post" action="/{{.Name}}/issues/{{.Issue.ID}}/assignees" class="label-form" hx-post="/{{.Name}}/issues/{{.Issue.ID}}/assignees" hx-target="#issue-assignees" hx-swap="outerHTML" hx-trigger="token-change" hx-sync="this:replace">
131 <input type="hidden" name="csrf" value="{{.CSRF}}">
132 {{template "token_input" (tokenPicker .Issue.Assignees .Assignable "assignee" "Add assignees…")}}
133 </form>
134 {{else}}
135 {{if .Issue.Assignees}}
136 <div class="label-row">{{range .Issue.Assignees}}<a class="label" href="/{{$.Name}}/issues?assignee={{urlquery .}}">@{{.}}</a>{{end}}</div>
137 {{else}}
138 <p class="muted">No assignees.</p>
139 {{end}}
140 {{end}}
141</section>
142{{end}}
143
144{{define "issue_head"}}
145<div class="page-head" id="issue-head">
146 <div class="page-head-main">
147 <div class="details-title">
148 <span class="page-num">{{num .Number}}</span>
149 <h1 class="details-title-view" data-details-title-view{{if .Details.Title.Active}} hidden{{end}}>{{.Issue.Title}}</h1>
150 {{if .CanEdit}}
151 <a class="title-edit-toggle" href="/{{.Name}}/issues/{{.Issue.ID}}/edit" data-title-edit{{if .Details.Title.Active}} hidden{{end}} aria-label="Edit title" title="Edit title">{{icon "pencil"}}</a>
152 <form class="title-edit-form" data-title-edit-form method="post" action="/{{.Name}}/issues/{{.Issue.ID}}/edit" hx-post="/{{.Name}}/issues/{{.Issue.ID}}/edit" hx-target="#issue-head" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Title.Active}} hidden{{end}}>
153 <input type="hidden" name="csrf" value="{{.CSRF}}">
154 <input type="hidden" name="field" value="title">
155 <input class="details-title-input" type="text" name="title" required maxlength="200" aria-label="Title" value="{{if .Details.Title.Active}}{{.Details.Title.Value}}{{else}}{{.Issue.Title}}{{end}}" data-title-edit-input{{if .Details.Title.Active}} autofocus{{end}}>
156 <button class="secondary" type="button" data-title-edit-cancel>Cancel</button>
157 <button type="submit">{{icon "check"}}Save</button>
158 </form>
159 {{end}}
160 </div>
161 {{if .Details.Title.Error}}<p class="error">{{.Details.Title.Error}}</p>{{end}}
162 <p class="page-sub">
163 <span class="state {{.Issue.State}}">{{.Issue.State}}</span>
164 · opened by {{template "userlink" (user .Issue.Author)}} {{since .Issue.Created.Unix}}
165 </p>
166 </div>
167 {{if .CanClose}}
168 <div class="page-head-actions">
169 <form method="post" action="/{{.Name}}/issues/{{.Issue.ID}}/state">
170 <input type="hidden" name="csrf" value="{{.CSRF}}">
171 <input type="hidden" name="state" value="{{if eq .Issue.State "open"}}closed{{else}}open{{end}}">
172 <button class="{{if eq .Issue.State "open"}}danger{{else}}secondary{{end}}" type="submit">{{if eq .Issue.State "open"}}{{icon "circle-x"}}Close{{else}}{{icon "rotate-ccw"}}Reopen{{end}}</button>
173 </form>
174 </div>
175 {{end}}
176</div>
177{{end}}
178
179{{define "issue_description"}}
180<article class="comment{{if .Details.Description.Active}} editing{{end}}" id="issue-description">
181 <header class="comment-head">
182 {{$u := user .Issue.Author}}
183 <span class="comment-author">{{template "userlink" $u}}</span>
184 <span class="muted">opened this {{since .Issue.Created.Unix}}</span>
185 <div class="comment-menu" data-comment-menu>
186 <button type="button" class="comment-menu-toggle" data-comment-menu-toggle aria-haspopup="menu" aria-expanded="false" aria-label="Description actions">&#8943;</button>
187 <div class="comment-menu-list" data-comment-menu-list role="menu" hidden>
188 <button type="button" role="menuitem" data-copy-query="{{urlquery .Issue.Description}}">Copy text</button>
189 {{if .CanEdit}}<button type="button" role="menuitem" data-comment-edit>Edit</button>{{end}}
190 </div>
191 </div>
192 </header>
193 <div class="comment-body md">
194 {{if .Description}}{{.Description}}{{else}}<p class="muted">No description.</p>{{end}}
195 </div>
196 {{if .CanEdit}}
197 <form class="comment-edit-form details-edit-form" data-comment-edit-form method="post" action="/{{.Name}}/issues/{{.Issue.ID}}/edit" hx-post="/{{.Name}}/issues/{{.Issue.ID}}/edit" hx-target="#issue-description" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Description.Active}} hidden{{end}}>
198 <input type="hidden" name="csrf" value="{{.CSRF}}">
199 <input type="hidden" name="field" value="body">
200 {{if .Details.Description.Error}}<p class="error">{{.Details.Description.Error}}</p>{{end}}
201 <textarea name="body" data-autosize aria-label="Description">{{if .Details.Description.Active}}{{.Details.Description.Value}}{{else}}{{.Issue.Description}}{{end}}</textarea>
202 <div class="form-actions">
203 <button class="secondary" type="button" data-comment-edit-cancel>Cancel</button>
204 <button type="submit">{{icon "check"}}Save</button>
205 </div>
206 </form>
207 {{end}}
208</article>
209{{end}}
210
211{{/* A change request that names the issue in its closes list is a reference the
212 issue can show back. The rows are the compact shape the overview and the
213 change sidebar already use, so the reader does not learn a second one. */}}
214{{define "issue_linked_changes"}}
215{{if .LinkedChanges}}
216<section id="issue-changes">
217 <h2>{{icon "git-pull-request"}}Referenced in changes</h2>
218 <div class="side-list">
219 {{range .LinkedChanges}}
220 <a class="side-change" href="/{{$.Name}}/changes/{{.ID}}">
221 <span class="side-subject"><span class="side-num">{{num .Number}}</span>{{.Title}}</span>
222 <span class="side-meta"><span class="state {{.State}}">{{.State}}</span>{{who .Author}}{{if not .Created.IsZero}}<span>{{since .Created.Unix}}</span>{{end}}</span>
223 </a>
224 {{end}}
225 </div>
226</section>
227{{end}}
228{{end}}
229
230{{/* Commits are the other way a branch mentions an issue. git does the search,
231 so the section shows the same rows the full-width table did, in the side
232 column's compact shape. */}}
233{{define "issue_backlinks"}}
234{{if .Backlinks}}
235<section id="issue-commits">
236 <h2>{{icon "git-commit-horizontal"}}Referenced in commits</h2>
237 <div class="side-list">
238 {{range .Backlinks}}
239 <a class="side-commit" href="/{{$.Name}}/commit/{{.SHA}}">
240 <span class="side-subject">{{.Subject}}</span>
241 <span class="side-meta"><span class="sha">{{short .SHA}}</span>{{if .Author}}<span>{{.Author}}</span>{{end}}<span>{{since .When}}</span></span>
242 </a>
243 {{end}}
244 </div>
245</section>
246{{end}}
247{{end}}
248
249{{define "issue"}}
250{{template "header" .}}
251{{template "repohead" (nav .Page "issues")}}
252
253{{template "issue_head" .}}
254
255<div class="thread">
256 <div class="thread-main">
257 {{template "issue_description" .}}
258
259 {{template "comment_thread" .Thread}}
260 </div>
261
262 <aside class="thread-side">
263 {{template "issue_labels" .}}
264 {{template "issue_assignees" .}}
265 {{template "issue_linked_changes" .}}
266 {{template "issue_backlinks" .}}
267 </aside>
268</div>
269{{template "footer" .}}
270{{end}}
271