vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Blame · server/web/templates/components.html
Line attribution on main · 174 line(s)
|
c717d4eb
Keenan Nemetz
13d ago
|
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> | |
|
306f36fc
Keenan Nemetz
12d ago
|
8 | <input type="text" class="label-entry" value="{{join .Selected ", "}}" data-label-entry autocomplete="off" placeholder="{{.Placeholder}}" aria-label="{{.Placeholder}}"> |
|
c717d4eb
Keenan Nemetz
13d ago
|
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> | |
|
ed03b3d1
Keenan Nemetz
6h ago
|
22 | <td class="sha"><a href="/{{$.Name}}/commit/{{.SHA}}">{{short .SHA}}</a></td> |
| 23 | <td><a href="/{{$.Name}}/commit/{{.SHA}}">{{.Subject}}</a></td> | |
|
c717d4eb
Keenan Nemetz
13d ago
|
24 | <td class="muted">{{.Author}}</td> |
| 25 | <td class="muted nowrap">{{since .When}}</td> | |
| 26 | </tr> | |
| 27 | {{end}} | |
| 28 | </tbody> | |
| 29 | </table> | |
| 30 | {{else}} | |
|
c7b9094f
Keenan Nemetz
1d ago
|
31 | <div class="empty-state"><p>No commits yet.</p></div> |
|
c717d4eb
Keenan Nemetz
13d ago
|
32 | {{end}} |
| 33 | {{end}} | |
| 34 | ||
|
628d4523
Keenan Nemetz
11d ago
|
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. */}} | |
|
c717d4eb
Keenan Nemetz
13d ago
|
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> | |
|
628d4523
Keenan Nemetz
11d ago
|
52 | <div class="tree-children">{{template "tree_nodes" ($.Branch .Children)}}</div> |
|
c717d4eb
Keenan Nemetz
13d ago
|
53 | </details> |
| 54 | {{else}} | |
|
628d4523
Keenan Nemetz
11d ago
|
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}} | |
|
c717d4eb
Keenan Nemetz
13d ago
|
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"> | |
|
a6b42433
Keenan Nemetz
8d ago
|
80 | {{$u := user .Author}} |
| 81 | <span class="comment-author">{{template "userlink" $u}}</span> | |
|
c717d4eb
Keenan Nemetz
13d ago
|
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">⋯</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}} | |
|
802e0e52
Keenan Nemetz
5d ago
|
90 | <button type="button" role="menuitem" data-copy-query="{{urlquery .Source}}">Copy text</button> |
|
c717d4eb
Keenan Nemetz
13d ago
|
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}}"> | |
|
802e0e52
Keenan Nemetz
5d ago
|
118 | <textarea name="body" required data-autosize>{{.Source}}</textarea> |
|
c717d4eb
Keenan Nemetz
13d ago
|
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}} | |
|
c7b9094f
Keenan Nemetz
1d ago
|
147 | <div class="empty-state"><p>{{.EmptyText}}</p></div> |
|
c717d4eb
Keenan Nemetz
13d ago
|
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}} |