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 · 27m ago 4b8c9e1d
history blame raw 11.4 KiB
1{{/* Shared page chrome: the document shell, the global bar and account menu,
2 the repository header with its tabs, and the one error page every 404 and
3 500 renders through. Every page template opens with {{template "header" .}}
4 and closes with {{template "footer" .}}. */}}
5{{define "header"}}<!doctype html>
6<html lang="en" data-theme="everforest-dark-medium" data-font-scale="medium"{{if .InstanceThemes}} data-themes="{{range $i, $t := .InstanceThemes}}{{if $i}},{{end}}{{$t.ID}}{{end}}"{{end}}>
7<head>
8<meta charset="utf-8">
9<meta name="viewport" content="width=device-width,initial-scale=1">
10<meta name="csrf" content="{{.CSRF}}">
11<meta name="color-scheme" content="dark light">
12{{if .NoIndex}}<meta name="robots" content="noindex">{{end}}
13<meta name="htmx-config" content='{"includeIndicatorStyles":false}'>
14<title>{{.Title}} · {{.InstanceName}}</title>
15<link rel="icon" type="image/png" sizes="32x32" href="/-/static/favicon-32.png" media="(prefers-color-scheme: light)">
16<link rel="icon" type="image/png" sizes="32x32" href="/-/static/favicon-32-dark.png" media="(prefers-color-scheme: dark)">
17<link rel="icon" type="image/png" sizes="16x16" href="/-/static/favicon-16.png" media="(prefers-color-scheme: light)">
18<link rel="icon" type="image/png" sizes="16x16" href="/-/static/favicon-16-dark.png" media="(prefers-color-scheme: dark)">
19<link rel="icon" type="image/svg+xml" href="/-/static/favicon.svg" sizes="any">
20{{if .InstanceFavicon}}<link rel="icon" type="image/svg+xml" href="{{.InstanceFavicon}}" sizes="any">{{end}}
21<script src="/-/static/theme.js"></script>
22<link rel="stylesheet" href="/-/static/vole.css">
23<link rel="stylesheet" href="/-/static/everforest.css">
24<link rel="stylesheet" href="/-/static/palettes.css">
25{{range .InstanceThemes}}<link rel="stylesheet" href="{{.URL}}">
26{{end}}{{if .InstanceCSS}}<link rel="stylesheet" href="{{.InstanceCSS}}">{{end}}
27<script src="/-/static/htmx.min.js" defer></script>
28<script src="/-/static/auth.js" defer></script>
29<script src="/-/static/app.js" defer></script>
30</head>
31<body>
32<header class="topbar">
33 <a class="brand" href="/" aria-label="{{.InstanceName}} home"><span class="mark" aria-hidden="true"></span></a>
34 <nav class="topnav" aria-label="Global">
35 <a class="{{if eq .Section "home"}}active{{end}}" href="/"{{if eq .Section "home"}} aria-current="page"{{end}}>{{icon "house"}}Home</a>
36 <a class="{{if eq .Section "activity"}}active{{end}}" href="/-/activity"{{if eq .Section "activity"}} aria-current="page"{{end}}>{{icon "activity"}}Activity</a>
37 <a class="{{if eq .Section "people"}}active{{end}}" href="/-/u"{{if eq .Section "people"}} aria-current="page"{{end}}>{{icon "users"}}Users</a>
38 {{if .InstanceAdmin}}<a class="{{if eq .Section "admin"}}active{{end}}" href="/-/admin"{{if eq .Section "admin"}} aria-current="page"{{end}}>{{icon "sliders-horizontal"}}Admin</a>{{end}}
39 </nav>
40 <div class="topsearch" role="search">
41 <button type="button" class="topsearch-box" data-palette-open aria-haspopup="dialog" aria-controls="search-palette" aria-keyshortcuts="Control+K Meta+K">
42 {{icon "search"}}
43 <span class="topsearch-label">Search issues and changes</span>
44 <kbd class="topsearch-key" data-palette-key aria-hidden="true">Ctrl K</kbd>
45 </button>
46 </div>
47 <div class="topright">
48 {{if .LoggedIn}}
49 <details class="account-menu">
50 <summary>
51 {{icon "user"}}<span class="account-name">{{.User}}</span>
52 {{if .DevLogin}}<span class="label" title="Development sign-in">dev</span>{{end}}
53 <span class="account-caret">{{icon "chevron-right"}}</span>
54 </summary>
55 <div class="account-pop">
56 <a href="/-/account">{{icon "user"}}Account</a>
57 <div class="account-sep"></div>
58 <form method="post" action="/-/logout"><input type="hidden" name="csrf" value="{{.CSRF}}"><button type="submit">{{icon "log-out"}}Sign out</button></form>
59 </div>
60 </details>
61 {{else}}
62 {{if .DevLogin}}<a href="/-/dev/login">{{icon "user"}}Dev sign-in</a>{{end}}
63 <a href="/-/login">{{icon "user"}}Sign in</a>
64 {{end}}
65 </div>
66</header>
67{{/* The command palette is the whole search surface: one field over every
68 issue, change request and comment the reader can see. The launcher in the
69 global bar opens it; the dialog itself never navigates to a page. */}}
70<dialog class="palette" id="search-palette" aria-label="Search issues, changes and comments">
71 <form class="palette-search" role="search"
72 hx-get="/-/search/palette" hx-trigger="input changed delay:250ms, search"
73 hx-target="#palette-results" hx-swap="innerHTML" hx-sync="this:replace">
74 <span aria-hidden="true">{{icon "search"}}</span>
75 <input type="search" name="q" id="search-palette-input"
76 placeholder="Search issues, changes and comments"
77 aria-label="Search issues, changes and comments" autocomplete="off"
78 role="combobox" aria-expanded="true" aria-controls="palette-results" aria-autocomplete="list">
79 <button type="button" class="palette-close" data-palette-close aria-label="Close search">{{icon "x"}}</button>
80 </form>
81 <div class="palette-results" id="palette-results" role="listbox" aria-label="Search results">
82 {{template "palette_hint"}}
83 </div>
84 <div class="palette-foot" aria-hidden="true">
85 <span><kbd>↑</kbd> <kbd>↓</kbd> move</span>
86 <span><kbd>Enter</kbd> open</span>
87 <span><kbd>Esc</kbd> close</span>
88 </div>
89</dialog>
90<a class="skip-link" href="#main">Skip to content</a>
91<main class="page" id="main">
92{{if .Error}}<div class="flash bad">{{.Error}}</div>{{end}}
93{{end}}
94
95{{define "footer"}}
96</main>
97</body>
98</html>
99{{end}}
100
101{{/* The open counts beside the Issues and Changes tabs. They are templates of
102 their own so the header's poll can replace the badges and nothing else: the
103 tab the reader is on, the clone menu and any open form stay where they are.
104 The ids are what the out-of-band swap matches, so the page and the poll
105 agree on where a count goes. */}}
106{{define "repo_issue_count"}}<span class="chip-count" id="repo-issue-count"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>{{.OpenIssues}}</span>{{end}}
107{{define "repo_request_count"}}<span class="chip-count" id="repo-request-count"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>{{.OpenRequests}}</span>{{end}}
108{{define "repo_counts_live"}}
109{{template "repo_issue_count" .}}
110{{template "repo_request_count" .}}
111{{end}}
112
113{{define "repohead"}}
114<div class="repohead">
115 <div class="repo-title">
116 <div class="repo-title-row">
117 <div class="repo-name"><span class="repo-vis" title="{{if .Private}}Private repository{{else}}Public repository{{end}}">{{if .Private}}{{icon "lock"}}{{else}}{{icon "globe"}}{{end}}</span>{{.Name}}</div>
118 <div class="repo-title-actions">
119 {{if .Site}}<a class="button secondary" href="{{.Site}}">{{icon "external-link"}}Site</a>{{end}}
120 <details class="clone">
121 <summary>{{icon "copy"}}Clone</summary>
122 <div class="clone-pop">
123 <p class="muted">Read only, no credentials:</p>
124 <div class="clone-field">
125 <code>{{.CloneURL}}</code>
126 <button class="secondary" type="button" data-copy="{{.CloneURL}}" title="Copy HTTPS URL" aria-label="Copy HTTPS URL">{{icon "copy"}}</button>
127 </div>
128 <p class="muted">Write over SSH with a registered key:</p>
129 <div class="clone-field">
130 <code>git clone {{.SSHURL}}</code>
131 <button class="secondary" type="button" data-copy="git clone {{.SSHURL}}" title="Copy SSH command" aria-label="Copy SSH command">{{icon "copy"}}</button>
132 </div>
133 </div>
134 </details>
135 </div>
136 </div>
137 {{if .Description}}<p class="repo-desc">{{.Description}}</p>{{end}}
138 </div>
139 {{/* One poll for the header's counts. The response is out-of-band updates to
140 the two badges, so the tabs are not re-rendered and the page keeps its
141 state. It pauses while the tab is hidden, so a forgotten page does not
142 keep asking. */}}
143 <nav class="tabs" aria-label="Repository"
144 hx-get="/{{.Name}}/counts"
145 hx-trigger="every 30s [document.visibilityState === 'visible']"
146 hx-sync="this:replace"
147 hx-swap="none">
148 <a class="repotab{{if eq .Tab "overview"}} active{{end}}" href="/{{.Name}}"{{if eq .Tab "overview"}} aria-current="page"{{end}}>{{icon "layout-dashboard"}}Overview</a>
149 <a href="/{{.Name}}/activity"{{if eq .Tab "activity"}} class="active" aria-current="page"{{end}}>{{icon "activity"}}Activity</a>
150 <a href="/{{.Name}}/code"{{if eq .Tab "code"}} class="active" aria-current="page"{{end}}>{{icon "code"}}Code</a>
151 <a href="/{{.Name}}/issues"{{if eq .Tab "issues"}} class="active" aria-current="page"{{end}}>{{icon "circle-dot"}}Issues {{template "repo_issue_count" .}}</a>
152 <a href="/{{.Name}}/changes"{{if eq .Tab "changes"}} class="active" aria-current="page"{{end}}>{{icon "git-pull-request"}}Changes {{template "repo_request_count" .}}</a>
153 <a href="/{{.Name}}/releases"{{if eq .Tab "releases"}} class="active" aria-current="page"{{end}}>{{icon "tag"}}Releases</a>
154 <a href="/{{.Name}}/runs"{{if eq .Tab "runs"}} class="active" aria-current="page"{{end}}>{{icon "play"}}Runs</a>
155 <a href="/{{.Name}}/locks"{{if eq .Tab "locks"}} class="active" aria-current="page"{{end}}>{{icon "lock"}}Locks</a>
156 {{if .CanAdmin}}<a href="/{{.Name}}/settings"{{if eq .Tab "settings"}} class="active" aria-current="page"{{end}}>{{icon "sliders-horizontal"}}Settings</a>{{end}}
157 </nav>
158</div>
159{{end}}
160
161{{define "errorpage"}}
162{{template "header" .}}
163<div class="empty-page">
164 <p class="eyebrow">Error</p>
165 <h1>{{.Title}}</h1>
166 <p class="muted">{{.Message}}</p>
167 <p><a class="button" href="/">Back to repositories</a></p>
168</div>
169{{template "footer" .}}
170{{end}}
171
172{{define "notfound"}}{{template "errorpage" .}}{{end}}
173
174{{define "servererror"}}{{template "errorpage" .}}{{end}}
175
176{{/* An account name, as a link to the profile it belongs to. `user` returns an
177 href only when the name belongs to an account, so a commit author who never
178 registered, and a typo in a mention, stay text rather than becoming a link
179 that leads to a 404. Use `who` instead where a link would be wrong: inside
180 another link, inside a button, in an <option>, or in an attribute. */}}
181{{define "userlink"}}{{if .Known}}<a class="user" href="{{.Href}}">{{.Name}}</a>{{else}}{{.Name}}{{end}}{{end}}
182{{/* The command palette's initial state and the rows HTMX swaps in. The dialog
183 shell above carries both, so they live beside the chrome that owns them. */}}
184{{define "palette_hint"}}
185<p class="palette-empty" role="option" aria-disabled="true">Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.</p>
186{{end}}
187
188{{define "palette_results"}}
189{{if .Message}}
190<p class="palette-empty" role="option" aria-disabled="true">{{.Message}}</p>
191{{else if not .Query}}
192{{template "palette_hint"}}
193{{else if .Results}}
194{{range .Results}}
195<a class="palette-row" data-palette-row role="option" aria-selected="false" href="{{.URL}}">
196 <span class="palette-row-title">{{.Title}}</span>
197 <span class="palette-row-where">{{.KindLabel}} · {{.Repo}}{{if .Number}} · <span class="sha">{{num .Number}}</span>{{end}}</span>
198 {{if .Snippet}}<span class="search-snippet">{{.Snippet}}</span>{{end}}
199</a>
200{{end}}
201{{if .Limited}}<p class="palette-empty" role="option" aria-disabled="true">Showing the first results. Add a word to narrow the search.</p>{{end}}
202{{else}}
203<p class="palette-empty" role="option" aria-disabled="true">Nothing matches “{{.Query}}”.</p>
204{{end}}
205{{end}}
206