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.

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}}