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.
Build out the forge web UI
f09ef39efe44de693748053a7b596ef328fcf06b · Keenan Nemetz · 15d ago · parent 70b70264
Build out the forge web UI Turn the web surface into a compact, server-rendered forge with HTMX as the progressive dynamic layer. - Split templates and assets into embedded files; add a tokenised dark theme, Lucide icons, and vendored HTMX. - Add the repository Overview page and a separate chronological Activity feed with type filters and HTMX fragment updates. - Finish the issue and change workflows: create pages, comments, close/reopen, labels and assignees, approval, discussion, line-anchored review comments, and HTMX list filters. - Add syntax-highlighted blobs, contributor/statistics derivation, branch-aware code browsing, and a repository Runs list. - Add a loopback-only development login for local write testing, with explicit configuration and listener checks. - Extend tests for the new pages, fragments, labels, assignees, anchors, and development login.
server/web/templates/code.html +182
| @@ -0,0 +1,182 @@ | |||
| 1 | {{define "code"}} | ||
| 2 | {{template "header" .}} | ||
| 3 | {{template "repohead" (nav .Name "code")}} | ||
| 4 | |||
| 5 | <div class="repo-sub"> | ||
| 6 | <form method="get" action="/r/{{.Name}}/code" class="inline"> | ||
| 7 | <select name="ref" data-autosubmit aria-label="Branch"> | ||
| 8 | {{range .Branches}}<option value="{{.}}"{{if eq . $.Ref}} selected{{end}}>{{.}}</option>{{end}} | ||
| 9 | </select> | ||
| 10 | <noscript><button class="secondary" type="submit">Go</button></noscript> | ||
| 11 | </form> | ||
| 12 | <span class="item">{{.Count}} commit(s)</span> | ||
| 13 | <details class="clone"> | ||
| 14 | <summary>{{icon "copy"}}Clone</summary> | ||
| 15 | <div class="clone-pop"> | ||
| 16 | <p class="muted">Read only, no credentials:</p> | ||
| 17 | <code>{{.CloneURL}}</code> | ||
| 18 | <p class="muted">Write over SSH with a registered key:</p> | ||
| 19 | <code>git clone {{.SSHURL}}</code> | ||
| 20 | <p><button class="secondary" type="button" data-copy="{{.CloneURL}}">{{icon "copy"}}Copy HTTPS URL</button></p> | ||
| 21 | </div> | ||
| 22 | </details> | ||
| 23 | <a class="button secondary" href="/r/{{.Name}}/commits/{{esc .Ref}}">{{icon "history"}}History</a> | ||
| 24 | {{if .Site}}<a class="item" href="{{.Site}}">Site</a>{{end}} | ||
| 25 | </div> | ||
| 26 | |||
| 27 | {{range .Latest}} | ||
| 28 | <div class="latest"> | ||
| 29 | <a href="/r/{{$.Name}}/commit/{{.SHA}}">{{.Subject}}</a> | ||
| 30 | <span class="muted">{{.Author}} · {{since .When}}</span> | ||
| 31 | <a class="sha" href="/r/{{$.Name}}/commit/{{.SHA}}">{{short .SHA}}</a> | ||
| 32 | </div> | ||
| 33 | {{end}} | ||
| 34 | |||
| 35 | <h2>Files{{if .Ref}} <span class="muted">on {{.Ref}}</span>{{end}}</h2> | ||
| 36 | {{if .Entries}} | ||
| 37 | <div class="panel"> | ||
| 38 | <table> | ||
| 39 | <tbody> | ||
| 40 | {{range .Entries}} | ||
| 41 | <tr> | ||
| 42 | {{if eq .Type "tree"}} | ||
| 43 | <td><a class="dir" href="/r/{{$.Name}}/tree/{{esc $.Ref}}/{{.Name}}">{{.Name}}/</a></td> | ||
| 44 | {{else}} | ||
| 45 | <td><a href="/r/{{$.Name}}/blob/{{esc $.Ref}}/{{.Name}}">{{.Name}}</a></td> | ||
| 46 | {{end}} | ||
| 47 | </tr> | ||
| 48 | {{end}} | ||
| 49 | </tbody> | ||
| 50 | </table> | ||
| 51 | </div> | ||
| 52 | {{else}} | ||
| 53 | <p class="muted">This repository is empty. Push a branch to fill it:</p> | ||
| 54 | <pre>git push origin HEAD:refs/heads/{{.Ref}}</pre> | ||
| 55 | {{end}} | ||
| 56 | |||
| 57 | {{if .Readme}} | ||
| 58 | <div class="filehead"> | ||
| 59 | <span>{{.ReadmeName}}</span> | ||
| 60 | <a href="/r/{{.Name}}/blob/{{esc .Ref}}/{{.ReadmeName}}">view source</a> | ||
| 61 | </div> | ||
| 62 | <article class="md readme">{{.Readme}}</article> | ||
| 63 | {{end}} | ||
| 64 | |||
| 65 | {{if .ContributorBranches}} | ||
| 66 | <h2>Contributor branches</h2> | ||
| 67 | <p class="muted">CI does not run for these branches until a maintainer allows the current commit.</p> | ||
| 68 | <div class="panel"> | ||
| 69 | <table> | ||
| 70 | <thead><tr><th>Branch</th><th>CI</th></tr></thead> | ||
| 71 | <tbody> | ||
| 72 | {{range .ContributorBranches}} | ||
| 73 | <tr> | ||
| 74 | <td><code>{{.}}</code></td> | ||
| 75 | <td> | ||
| 76 | {{if $.CanAllowCI}} | ||
| 77 | <form method="post" action="/r/{{$.Name}}/ci/allow"> | ||
| 78 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> | ||
| 79 | <input type="hidden" name="ref" value="refs/heads/{{.}}"> | ||
| 80 | <button type="submit">Allow CI for this tip</button> | ||
| 81 | </form> | ||
| 82 | {{else}}<span class="muted">waiting for a maintainer to allow it</span>{{end}} | ||
| 83 | </td> | ||
| 84 | </tr> | ||
| 85 | {{end}} | ||
| 86 | </tbody> | ||
| 87 | </table> | ||
| 88 | </div> | ||
| 89 | {{end}} | ||
| 90 | |||
| 91 | {{template "footer" .}} | ||
| 92 | {{end}} | ||
| 93 | |||
| 94 | {{define "tree"}} | ||
| 95 | {{template "header" .}} | ||
| 96 | {{template "repohead" (nav .Name "code")}} | ||
| 97 | <p class="crumbs"> | ||
| 98 | <a href="/r/{{.Name}}/code?ref={{esc .Ref}}">{{.Ref}}</a> | ||
| 99 | {{range .Crumbs}} / <a href="/r/{{$.Name}}/tree/{{esc $.Ref}}/{{.Path}}">{{.Name}}</a>{{end}} | ||
| 100 | </p> | ||
| 101 | {{if .Entries}} | ||
| 102 | <div class="panel"> | ||
| 103 | <table> | ||
| 104 | <tbody> | ||
| 105 | {{range .Entries}} | ||
| 106 | <tr> | ||
| 107 | {{if eq .Type "tree"}} | ||
| 108 | <td><a class="dir" href="/r/{{$.Name}}/tree/{{esc $.Ref}}/{{$.Path}}{{if $.Path}}/{{end}}{{.Name}}">{{.Name}}/</a></td> | ||
| 109 | {{else}} | ||
| 110 | <td><a href="/r/{{$.Name}}/blob/{{esc $.Ref}}/{{$.Path}}{{if $.Path}}/{{end}}{{.Name}}">{{.Name}}</a></td> | ||
| 111 | {{end}} | ||
| 112 | </tr> | ||
| 113 | {{end}} | ||
| 114 | </tbody> | ||
| 115 | </table> | ||
| 116 | </div> | ||
| 117 | {{else}} | ||
| 118 | <p class="muted">This directory is empty.</p> | ||
| 119 | {{end}} | ||
| 120 | {{template "footer" .}} | ||
| 121 | {{end}} | ||
| 122 | |||
| 123 | {{define "blob"}} | ||
| 124 | {{template "header" .}} | ||
| 125 | {{template "repohead" (nav .Name "code")}} | ||
| 126 | <h1 class="path-title">{{.Path}}</h1> | ||
| 127 | <p class="crumbs"> | ||
| 128 | <a href="/r/{{.Name}}/code?ref={{esc .Ref}}">{{.Ref}}</a> | ||
| 129 | {{range .Crumbs}} / <a href="/r/{{$.Name}}/tree/{{esc $.Ref}}/{{.Path}}">{{.Name}}</a>{{end}} | ||
| 130 | <span class="crumb-actions"> | ||
| 131 | <a href="/r/{{.Name}}/raw/{{esc .Ref}}/{{.Path}}">raw</a> | ||
| 132 | <span class="muted">{{bytes .Size}}</span> | ||
| 133 | </span> | ||
| 134 | </p> | ||
| 135 | {{if .Binary}} | ||
| 136 | <div class="card"> | ||
| 137 | <p class="muted">Binary file · {{bytes .Size}}. It is not shown inline.</p> | ||
| 138 | <p><a class="button secondary" href="/r/{{.Name}}/raw/{{esc .Ref}}/{{.Path}}">Download</a></p> | ||
| 139 | </div> | ||
| 140 | {{else}} | ||
| 141 | {{if .Truncated}}<p class="muted">Showing the first 1 MiB.</p>{{end}} | ||
| 142 | {{if .LineCut}}<p class="muted">Showing the first 5,000 lines.</p>{{end}} | ||
| 143 | {{if .Markdown}}<div class="md filebody">{{.Markdown}}</div> | ||
| 144 | {{else if .Highlight}}<div class="highlight">{{.Highlight}}</div> | ||
| 145 | {{else}} | ||
| 146 | <div class="panel blob"> | ||
| 147 | <table class="code"> | ||
| 148 | <tbody> | ||
| 149 | {{range $i, $line := .Lines}} | ||
| 150 | <tr><td class="ln">{{inc $i}}</td><td class="code">{{$line}}</td></tr> | ||
| 151 | {{end}} | ||
| 152 | </tbody> | ||
| 153 | </table> | ||
| 154 | </div> | ||
| 155 | {{end}} | ||
| 156 | {{end}} | ||
| 157 | {{template "footer" .}} | ||
| 158 | {{end}} | ||
| 159 | |||
| 160 | {{define "commits"}} | ||
| 161 | {{template "header" .}} | ||
| 162 | {{template "repohead" (nav .Name "code")}} | ||
| 163 | <h1>History</h1> | ||
| 164 | <p class="crumbs"><a href="/r/{{.Name}}/code?ref={{esc .Ref}}">{{.Ref}}</a> · newest first</p> | ||
| 165 | <div class="panel"> | ||
| 166 | {{template "commitlist" .}} | ||
| 167 | </div> | ||
| 168 | {{template "footer" .}} | ||
| 169 | {{end}} | ||
| 170 | |||
| 171 | {{define "commit"}} | ||
| 172 | {{template "header" .}} | ||
| 173 | {{template "repohead" (nav .Name "code")}} | ||
| 174 | <h1>{{.Commit.Subject}}</h1> | ||
| 175 | <p class="muted"> | ||
| 176 | <span class="sha">{{.Commit.SHA}}</span> · {{.Commit.Author}} · {{since .Commit.When}} | ||
| 177 | {{if .Parents}} · parent {{range .Parents}}<a class="sha" href="/r/{{$.Name}}/commit/{{.}}">{{short .}}</a> {{end}}{{end}} | ||
| 178 | </p> | ||
| 179 | {{if and .Body (ne .Body .Commit.Subject)}}<pre>{{.Body}}</pre>{{end}} | ||
| 180 | {{template "diff" .}} | ||
| 181 | {{template "footer" .}} | ||
| 182 | {{end}} | ||