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 · 22m ago 4b8c9e1d
history blame raw 9.2 KiB
1{{/* Small components shared by more than one surface: the label/assignee token
2 input, the commit list, the comment card, the file tree, and the pager. All
3 of them are rendered from a page's own data rather than by a call to a
4 handler. */}}
5{{define "token_input"}}
6<div class="label-input" data-label-input data-label-item="{{.Item}}">
7 <div class="label-tokens" data-label-tokens>
8 <input type="text" class="label-entry" value="{{join .Selected ", "}}" data-label-entry autocomplete="off" placeholder="{{.Placeholder}}" aria-label="{{.Placeholder}}">
9 </div>
10 <div class="label-suggest" data-label-suggest hidden>
11 {{range .All}}<button type="button" class="label-option" data-label-option="{{.}}">{{.}}</button>{{end}}
12 </div>
13</div>
14{{end}}
15
16{{define "commitlist"}}
17{{if .Commits}}
18<table class="commits">
19 <tbody>
20 {{range .Commits}}
21 <tr>
22 <td class="sha"><a href="/{{$.Name}}/commit/{{.SHA}}">{{short .SHA}}</a></td>
23 <td><a href="/{{$.Name}}/commit/{{.SHA}}">{{.Subject}}</a></td>
24 <td class="muted">{{.Author}}</td>
25 <td class="muted nowrap">{{since .When}}</td>
26 </tr>
27 {{end}}
28 </tbody>
29</table>
30{{else}}
31<div class="empty-state"><p>No commits yet.</p></div>
32{{end}}
33{{end}}
34
35{{/* One tree for every browser. A code row opens a blob and carries the file
36 icon for its language; a changed-file row opens the file's diff and carries
37 the A/M/D/R mark instead. The context says which is which: with Review and
38 Commit both empty this is the code browser. This template lives here
39 because the code browser owns the tree; request_files.html and commit.html
40 render it with a change or a commit. */}}
41{{define "tree_nodes"}}
42<ul class="tree-list">
43 {{range .Nodes}}
44 <li>
45 {{if eq .Type "tree"}}
46 <details class="tree-dir{{if .Active}} active{{end}}" data-path="{{.Path}}"{{if .Open}} open{{end}}>
47 <summary class="tree-row">
48 <span class="tree-chevron">{{icon "chevron-right"}}</span>
49 <span class="tree-icon">{{icon "folder"}}</span>
50 <span class="tree-name" title="{{.Path}}">{{.Name}}</span>
51 </summary>
52 <div class="tree-children">{{template "tree_nodes" ($.Branch .Children)}}</div>
53 </details>
54 {{else}}
55 {{$href := $.Href .Path}}
56 <a class="tree-row tree-file{{if .Active}} active{{end}}" data-path="{{.Path}}" href="{{$href}}" hx-get="{{$href}}" hx-target="{{if $.Diff}}#review-main{{else}}#code-main{{end}}" hx-swap="outerHTML" hx-push-url="true" title="{{.Path}}">
57 {{if $.Diff}}<span class="review-mark" data-status="{{.Status}}">{{.Status}}</span>{{else}}<span class="tree-icon {{fileclass .Name}}">{{icon (fileicon .Name)}}</span>{{end}}
58 <span class="tree-name">{{.Name}}</span>
59 </a>
60 {{end}}
61 </li>
62 {{end}}
63</ul>
64{{end}}
65
66{{/* One page control for every list that can run past a screen: issues, change
67 requests, and the instance activity feed. The handler owns the URLs and the
68 words ("Issue pages"); this only draws them. */}}
69{{/* One card for a comment, on either surface. An issue comment has no anchor, so
70 it shows no snippet; a change comment anchored to a line shows the lines it
71 was written about, read from the blob as it stood then, and marks the card as
72 stale when the file has moved on. Edit swaps .Target, so the card does not
73 need to know which thread it is in. */}}
74{{define "comment_card"}}
75<article class="comment{{if .Deleted}} deleted{{end}}" id="c-{{.ID}}">
76 {{if .Deleted}}
77 <div class="comment-deleted">{{icon "circle-x"}}Comment deleted</div>
78 {{else}}
79 <header class="comment-head">
80 {{$u := user .Author}}
81 <span class="comment-author">{{template "userlink" $u}}</span>
82 {{if .ReplyTo}}<span class="reply-context">replying to {{if .ReplyToDeleted}}<a href="#c-{{.ReplyTo}}">a deleted comment</a>{{else}}<a href="#c-{{.ReplyTo}}">{{who .ReplyToName}}</a>{{end}}</span>{{end}}
83 <span class="muted">{{since .Created.Unix}}</span>
84 {{if and .Anchor (not .Current)}}<span class="conflict">the file has changed since this comment</span>{{end}}
85 {{if and .Anchor (not .Snippet)}}<span class="muted">on <code>{{.Anchor.Path}}:{{.Anchor.Line}}</code> ({{.Anchor.Side}} side)</span>{{end}}
86 <div class="comment-menu" data-comment-menu>
87 <button type="button" class="comment-menu-toggle" data-comment-menu-toggle aria-haspopup="menu" aria-expanded="false" aria-label="Comment actions">&#8943;</button>
88 <div class="comment-menu-list" data-comment-menu-list role="menu" hidden>
89 {{if .CanReply}}<button type="button" role="menuitem" data-reply-to="{{.ID}}" data-reply-author="{{who .Author}}">Reply</button>{{end}}
90 <button type="button" role="menuitem" data-copy-query="{{urlquery .Source}}">Copy text</button>
91 <button type="button" role="menuitem" data-copy-link="{{.ID}}">Copy link</button>
92 {{if .CanEdit}}<button type="button" role="menuitem" data-comment-edit>Edit</button>{{end}}
93 {{if .CanDelete}}
94 <form method="post" action="{{.DeleteURL}}" data-confirm="Delete this comment?">
95 <input type="hidden" name="csrf" value="{{.CSRF}}">
96 <button type="submit" role="menuitem" class="menu-danger">Delete</button>
97 </form>
98 {{end}}
99 </div>
100 </div>
101 </header>
102 <div class="comment-body md">{{.Body}}</div>
103 {{if .Snippet}}
104 <details class="comment-snippet">
105 <summary>
106 <span class="snippet-chevron">{{icon "chevron-right"}}</span>
107 <code>{{.Snippet.Path}}:{{.Snippet.Line}}</code>
108 <span class="muted">({{.Snippet.Side}} side)</span>
109 {{if not .Current}}<span class="conflict">original line</span>{{end}}
110 </summary>
111 <div class="snippet-code">{{range .Snippet.Lines}}<div class="snippet-line{{if .Anchor}} anchor{{end}}"><span class="snippet-no">{{.No}}</span><span class="snippet-text">{{.Text}}</span></div>{{end}}</div>
112 <div class="snippet-actions"><a href="{{.Snippet.FilesURL}}">View in Files</a></div>
113 </details>
114 {{end}}
115 {{if .CanEdit}}
116 <form class="comment-edit-form" data-comment-edit-form hidden method="post" action="{{.EditURL}}" hx-post="{{.EditURL}}" hx-target="{{.Target}}" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']">
117 <input type="hidden" name="csrf" value="{{.CSRF}}">
118 <textarea name="body" required data-autosize>{{.Source}}</textarea>
119 <div class="form-actions">
120 <button class="secondary" type="button" data-comment-edit-cancel>Cancel</button>
121 <button type="submit">Save</button>
122 </div>
123 </form>
124 {{end}}
125 {{end}}
126</article>
127{{end}}
128
129{{/* The discussion section of an issue or a change request: heading, threaded
130 comments, one composer. The two surfaces pass the same view, so the section
131 id, the composer's URL and the heading word come from the handler rather than
132 from the template. */}}
133{{define "comment_thread"}}
134<div id="{{.Section}}">
135 <h2>{{icon "message-square"}}{{.Title}} <span class="muted">({{.CommentCount}})</span></h2>
136 {{if .ThreadError}}<p class="error">{{.ThreadError}}</p>{{end}}
137 {{with .Target}}<p class="anchor-note muted">Commenting on <code>{{.Path}}:{{.Line}}</code> ({{.Side}} side) · <a href="{{$.BackURL}}#{{$.Composer}}">cancel</a></p>{{end}}
138 {{range .Comments}}
139 {{template "comment_card" .}}
140 {{if .Replies}}
141 <details class="thread-replies" open>
142 <summary><span class="thread-chevron">{{icon "chevron-right"}}</span>{{len .Replies}} {{if eq (len .Replies) 1}}reply{{else}}replies{{end}}</summary>
143 <div class="comment-replies">{{range .Replies}}{{template "comment_card" .}}{{end}}</div>
144 </details>
145 {{end}}
146 {{else}}
147 <div class="empty-state"><p>{{.EmptyText}}</p></div>
148 {{end}}
149
150 {{if .CanDiscuss}}
151 <form id="{{.Composer}}" method="post" action="{{.PostURL}}" class="form-block discussion-composer" hx-post="{{.PostURL}}" hx-target="#{{.Section}}" hx-swap="outerHTML"{{if .PushURL}} hx-push-url="{{.PushURL}}"{{end}} hx-disabled-elt="find button[type='submit']">
152 <input type="hidden" name="csrf" value="{{.CSRF}}">
153 <input type="hidden" name="reply_to" value="" data-reply-input>
154 {{with .Target}}<input type="hidden" name="path" value="{{.Path}}"><input type="hidden" name="line" value="{{.Line}}"><input type="hidden" name="side" value="{{.Side}}">{{end}}
155 <div class="reply-target" data-reply-target hidden>
156 <span>Replying to <strong data-reply-name></strong></span>
157 <button class="link" type="button" data-reply-clear>Cancel reply</button>
158 </div>
159 <textarea name="body" required placeholder="Leave a comment. Markdown is welcome." aria-label="Comment"{{if .Target}} autofocus{{end}}></textarea>
160 <div class="form-actions"><button type="submit">{{icon "message-square"}}Comment</button></div>
161 </form>
162 {{end}}
163</div>
164{{end}}
165
166{{define "pager"}}
167{{if gt .Pages 1}}
168<nav class="pager" aria-label="{{.Label}}">
169 {{if .PrevURL}}<a class="button secondary" href="{{.PrevURL}}"{{if .Target}} hx-get="{{.PrevURL}}" hx-target="{{.Target}}" hx-swap="outerHTML" hx-push-url="true"{{end}}>Previous</a>{{else}}<span></span>{{end}}
170 <span class="muted">Showing {{.From}}–{{.To}} of {{.Total}} · page {{.Page}} of {{.Pages}}</span>
171 {{if .NextURL}}<a class="button secondary" href="{{.NextURL}}"{{if .Target}} hx-get="{{.NextURL}}" hx-target="{{.Target}}" hx-swap="outerHTML" hx-push-url="true"{{end}}>Next</a>{{else}}<span></span>{{end}}
172</nav>
173{{end}}
174{{end}}
175