rin
Public Open-source lightweight git hosting.
base.html · 381 lines · 20.0 KB
Raw
1 <!DOCTYPE html>
2 <html lang="{{.Lang}}" data-theme="{{if .Theme}}{{.Theme}}{{else}}system{{end}}" data-copied-text="{{t .Lang "common.copied"}}">
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
7 <title>{{if .Title}}{{.Title}} · Polar{{else}}Polar{{end}}</title>
8 <meta name="description" content="{{t .Lang "app.tagline"}}">
9 <script>
10 (function(){
11 try {
12 var saved = localStorage.getItem('polar_theme');
13 if (saved && ['light','dark','system'].indexOf(saved) !== -1) {
14 document.documentElement.setAttribute('data-theme', saved);
15 }
16 var sb = localStorage.getItem('polar_sidebar');
17 if (sb === 'collapsed') {
18 document.documentElement.setAttribute('data-sidebar', 'collapsed');
19 }
20 } catch(e) {}
21 })();
22 </script>
23 <style>{{inlineCSS}}</style>
24 <link rel="stylesheet" href="/static/css/app.css?v={{assetVer}}">
25 </head>
26 <body>
27 {{sprite}}
28
29 <!-- Floating button to expand sidebar when collapsed -->
30 <button type="button" class="sidebar-expand-btn" data-toggle-sidebar title="{{t .Lang "nav.toggle_sidebar"}} (Ctrl+B)" aria-label="{{t .Lang "nav.toggle_sidebar"}}">
31 {{icon "sidebar" "size-4"}}
32 </button>
33
34 <div class="app-shell">
35 <!-- Left Sidebar -->
36 <aside class="app-sidebar" aria-label="Sidebar">
37 <div class="sidebar-inner">
38 <div class="flex flex-col gap-2 min-w-0">
39 <!-- Brand, Language & Collapse Sidebar -->
40 <div class="flex items-center justify-between px-3 py-2" style="padding:0.25rem 0.35rem;">
41 <a href="/" class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</a>
42 <div class="flex items-center gap-1">
43 {{if eq .Lang "en"}}
44 <a href="?lang=ru" class="btn btn-ghost btn-sm font-mono" title="{{t .Lang "nav.lang"}}">RU</a>
45 {{else}}
46 <a href="?lang=en" class="btn btn-ghost btn-sm font-mono" title="{{t .Lang "nav.lang"}}">EN</a>
47 {{end}}
48 <button type="button" class="btn btn-ghost btn-icon" style="width:1.65rem;height:1.65rem;" data-toggle-sidebar title="{{t .Lang "nav.toggle_sidebar"}} (Ctrl+B)" aria-label="{{t .Lang "nav.toggle_sidebar"}}">
49 {{icon "sidebar" "size-4"}}
50 </button>
51 </div>
52 </div>
53
54 <!-- Search / Command Palette Trigger -->
55 <button type="button" class="btn btn-outline w-full justify-between text-muted" style="padding:0 0.55rem;height:1.95rem;" data-open-palette="">
56 <span class="flex items-center gap-2 text-xs">
57 {{icon "search" "size-3.5"}}
58 <span>{{t .Lang "nav.search_placeholder"}}</span>
59 </span>
60 <kbd class="kbd">⌘K</kbd>
61 </button>
62
63 <!-- Primary Navigation -->
64 <nav class="flex flex-col gap-1 mt-1">
65 {{if .CurrentUser}}
66 <a href="/repos" class="nav-item">
67 <span class="flex items-center gap-2">{{icon "git-branch" "size-4"}} {{t .Lang "nav.repositories"}}</span>
68 </a>
69 {{else}}
70 <a href="/" class="nav-item">
71 <span class="flex items-center gap-2">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</span>
72 </a>
73 {{end}}
74 {{if .ExploreEnabled}}
75 <a href="/explore" class="nav-item">
76 <span class="flex items-center gap-2">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</span>
77 </a>
78 {{end}}
79 {{if .CurrentUser}}
80 <a href="/settings" class="nav-item">
81 <span class="flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</span>
82 </a>
83 {{if .CurrentUser.IsAdmin}}
84 <a href="/admin" class="nav-item">
85 <span class="flex items-center gap-2">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</span>
86 </a>
87 {{end}}
88 {{end}}
89 </nav>
90
91 <!-- Quick Repository List in Sidebar -->
92 <div class="mt-2">
93 <div class="nav-section-label">
94 <span>{{t .Lang "nav.repositories"}}</span>
95 {{if .CurrentUser}}
96 <a href="/new" class="btn btn-ghost btn-icon" style="width:1.35rem;height:1.35rem;" title="{{t .Lang "repo.new.title"}}">
97 {{icon "plus" "size-3.5"}}
98 </a>
99 {{end}}
100 </div>
101 <div class="flex flex-col gap-1">
102 {{if .PaletteRepos}}
103 {{range .PaletteRepos}}
104 <a href="/{{.OwnerUsername}}/{{.Name}}" class="nav-item" style="height:1.8rem;font-size:0.8rem;">
105 <span class="flex items-center gap-2 min-w-0 truncate">
106 {{if .IsPrivate}}
107 {{icon "lock" "size-3.5"}}
108 {{else}}
109 {{icon "folder" "size-3.5"}}
110 {{end}}
111 <span class="truncate">{{.OwnerUsername}}/<strong>{{.Name}}</strong></span>
112 </span>
113 </a>
114 {{end}}
115 {{else}}
116 {{if .CurrentUser}}
117 <a href="/new" class="nav-item text-xs">
118 <span class="flex items-center gap-1.5">{{icon "plus" "size-3.5"}} {{t .Lang "repo.new.title"}}</span>
119 </a>
120 {{end}}
121 {{end}}
122 </div>
123 </div>
124 </div>
125
126 <!-- Sidebar Bottom: 3-Theme Switcher + Account -->
127 <div class="flex flex-col gap-2 pt-2" style="border-top:1px solid hsl(var(--border));">
128 <div class="segmented" role="radiogroup" aria-label="{{t .Lang "nav.theme"}}">
129 <button type="button" class="segmented-btn {{if eq .Theme "light"}}active{{end}}" data-set-theme="light" title="{{t .Lang "theme.light"}}">
130 {{icon "sun" "size-3.5"}}
131 <span>{{t .Lang "theme.light"}}</span>
132 </button>
133 <button type="button" class="segmented-btn {{if eq .Theme "dark"}}active{{end}}" data-set-theme="dark" title="{{t .Lang "theme.dark"}}">
134 {{icon "moon" "size-3.5"}}
135 <span>{{t .Lang "theme.dark"}}</span>
136 </button>
137 <button type="button" class="segmented-btn {{if eq .Theme "system"}}active{{end}}" data-set-theme="system" title="{{t .Lang "theme.system"}}">
138 {{icon "laptop" "size-3.5"}}
139 <span>{{t .Lang "theme.system"}}</span>
140 </button>
141 </div>
142
143 {{if .CurrentUser}}
144 <div class="flex items-center justify-between gap-1" style="padding:0.15rem 0.25rem;">
145 <a href="/{{.CurrentUser.Username}}" class="flex items-center gap-2 min-w-0 truncate text-sm font-medium">
146 {{avatar .CurrentUser.Username .CurrentUser.AvatarURL}}
147 <span class="truncate">{{.CurrentUser.Username}}</span>
148 </a>
149 <div class="flex items-center gap-1">
150 <form method="post" action="/logout" style="margin:0;">
151 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
152 <button type="submit" class="btn btn-ghost btn-icon" style="width:1.65rem;height:1.65rem;" title="{{t .Lang "nav.logout"}}" aria-label="{{t .Lang "nav.logout"}}">
153 {{icon "log-out" "size-3.5"}}
154 </button>
155 </form>
156 </div>
157 </div>
158 {{else}}
159 <div class="flex flex-col gap-1.5">
160 <a href="/login" class="btn btn-outline btn-sm w-full">{{t .Lang "nav.login"}}</a>
161 {{if .RegistrationEnabled}}
162 <a href="/register" class="btn btn-primary btn-sm w-full">{{t .Lang "nav.register"}}</a>
163 {{end}}
164 </div>
165 {{end}}
166 </div>
167 </div>
168 </aside>
169
170 <!-- Main Content Column -->
171 <div class="app-col">
172 <!-- Mobile Topbar (only visible on small screens) -->
173 <div class="mobile-topbar">
174 <div class="flex items-center gap-2">
175 <button type="button" class="btn btn-ghost btn-icon" data-drawer-open aria-label="Menu">
176 {{icon "menu" "size-4"}}
177 </button>
178 <a href="/" class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</a>
179 </div>
180 <div class="flex items-center gap-1.5">
181 <button type="button" class="btn btn-ghost btn-icon" data-open-palette="" aria-label="{{t .Lang "nav.command_palette"}}">
182 {{icon "search" "size-4"}}
183 </button>
184 {{if .CurrentUser}}
185 <a href="/new" class="btn btn-primary btn-sm">{{icon "plus" "size-3.5"}}</a>
186 {{else}}
187 <a href="/login" class="btn btn-outline btn-sm">{{t .Lang "nav.login"}}</a>
188 {{end}}
189 </div>
190 </div>
191
192 <main class="app-main">
193 {{if .Flash}}
194 <div class="container mb-4">
195 <div class="card p-3 flex items-center justify-between gap-2" role="status" style="border-color:hsl(var(--primary) / 0.4);">
196 <span class="flex items-center gap-2 text-sm">{{icon "check" "size-4"}} {{.Flash}}</span>
197 </div>
198 </div>
199 {{end}}
200 {{if .Error}}
201 <div class="container mb-4">
202 <div class="card p-3 flex items-center justify-between gap-2" role="alert" style="border-color:hsl(var(--destructive) / 0.6);color:hsl(var(--destructive));">
203 <span class="flex items-center gap-2 text-sm">{{icon "alert-circle" "size-4"}} {{.Error}}</span>
204 </div>
205 </div>
206 {{end}}
207
208 {{template "content" .}}
209 </main>
210 </div>
211 </div>
212
213 <!-- Mobile Drawer -->
214 <div id="mobile-drawer" class="mobile-drawer" aria-hidden="true">
215 <div class="mobile-drawer-panel">
216 <div class="flex flex-col gap-2">
217 <div class="flex items-center justify-between" style="padding-bottom:0.5rem;border-bottom:1px solid hsl(var(--border));">
218 <span class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</span>
219 <button type="button" class="btn btn-ghost btn-icon" data-drawer-close aria-label="{{t .Lang "common.close"}}">
220 {{icon "close" "size-4"}}
221 </button>
222 </div>
223 <nav class="flex flex-col gap-1">
224 <a href="/" class="nav-item">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</a>
225 {{if .CurrentUser}}
226 <a href="/repos" class="nav-item">{{icon "git-branch" "size-4"}} {{t .Lang "nav.repositories"}}</a>
227 <a href="/new" class="nav-item">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</a>
228 <a href="/settings" class="nav-item">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</a>
229 {{if .CurrentUser.IsAdmin}}
230 <a href="/admin" class="nav-item">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</a>
231 {{end}}
232 {{end}}
233 {{if .ExploreEnabled}}
234 <a href="/explore" class="nav-item">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</a>
235 {{end}}
236 </nav>
237 </div>
238 <div class="segmented">
239 <button type="button" class="segmented-btn" data-set-theme="light">{{t .Lang "theme.light"}}</button>
240 <button type="button" class="segmented-btn" data-set-theme="dark">{{t .Lang "theme.dark"}}</button>
241 <button type="button" class="segmented-btn" data-set-theme="system">{{t .Lang "theme.system"}}</button>
242 </div>
243 </div>
244 </div>
245
246 <!-- Command Palette (Ctrl/Cmd+K) -->
247 <div id="command-palette" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "nav.command_palette"}}" aria-hidden="true">
248 <div class="palette-box">
249 <div class="palette-input-wrap">
250 {{icon "search" "size-4"}}
251 <input id="command-palette-input" type="text" class="palette-input" placeholder="{{t .Lang "palette.placeholder"}}" autocomplete="off">
252 <kbd class="kbd">ESC</kbd>
253 </div>
254 <div class="palette-list" role="listbox">
255 <div data-palette-group>
256 <div class="palette-group-title">{{t .Lang "palette.group.nav"}}</div>
257 <button type="button" class="palette-item" data-palette-item data-palette-href="/" data-palette-keywords="home главная домой">
258 <span class="flex items-center gap-2">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</span>
259 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
260 </button>
261 <button type="button" class="palette-item" data-palette-item data-palette-href="/repos" data-palette-keywords="repositories repos репозитории">
262 <span class="flex items-center gap-2">{{icon "git-branch" "size-4"}} {{t .Lang "nav.repositories"}}</span>
263 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
264 </button>
265 {{if .ExploreEnabled}}
266 <button type="button" class="palette-item" data-palette-item data-palette-href="/explore" data-palette-keywords="explore обзор публичные">
267 <span class="flex items-center gap-2">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</span>
268 </button>
269 {{end}}
270 {{if .CurrentUser}}
271 <button type="button" class="palette-item" data-palette-item data-palette-href="/new" data-palette-keywords="new create создать репозиторий">
272 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</span>
273 </button>
274 <button type="button" class="palette-item" data-palette-item data-palette-href="/settings" data-palette-keywords="settings tokens настройки токены">
275 <span class="flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</span>
276 </button>
277 {{if .CurrentUser.IsAdmin}}
278 <button type="button" class="palette-item" data-palette-item data-palette-href="/admin" data-palette-keywords="admin админка">
279 <span class="flex items-center gap-2">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</span>
280 </button>
281 {{end}}
282 {{else}}
283 <button type="button" class="palette-item" data-palette-item data-palette-href="/login" data-palette-keywords="login войти">
284 <span class="flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "nav.login"}}</span>
285 </button>
286 <button type="button" class="palette-item" data-palette-item data-palette-href="/register" data-palette-keywords="register регистрация">
287 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "nav.register"}}</span>
288 </button>
289 {{end}}
290 <button type="button" class="palette-item" data-palette-item data-palette-action="toggle-sidebar" data-palette-keywords="sidebar toggle боковая панель скрыть">
291 <span class="flex items-center gap-2">{{icon "sidebar" "size-4"}} {{t .Lang "nav.toggle_sidebar"}}</span>
292 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
293 </button>
294 </div>
295
296 {{if .PaletteFiles}}
297 <div data-palette-group>
298 <div class="palette-group-title">{{t .Lang "repo.files"}} ({{.Repo.OwnerUsername}}/{{.Repo.Name}})</div>
299 {{range .PaletteFiles}}
300 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{$.Repo.OwnerUsername}}/{{$.Repo.Name}}/blob/{{$.Ref}}/{{.}}" data-palette-keywords="file: {{.}}">
301 <span class="flex items-center gap-2 font-mono text-xs">{{icon "file-code" "size-4"}} {{.}}</span>
302 </button>
303 {{end}}
304 </div>
305 {{end}}
306
307 {{if .PaletteRepos}}
308 <div data-palette-group>
309 <div class="palette-group-title">{{t .Lang "palette.group.repos"}}</div>
310 {{range .PaletteRepos}}
311 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{.OwnerUsername}}/{{.Name}}" data-palette-keywords="repo {{.OwnerUsername}}/{{.Name}} {{.Description}}">
312 <span class="flex items-center gap-2">
313 {{if .IsPrivate}}{{icon "lock" "size-4"}}{{else}}{{icon "git-branch" "size-4"}}{{end}}
314 <span>{{.OwnerUsername}}/<strong>{{.Name}}</strong></span>
315 </span>
316 <span class="text-xs text-muted">{{if .IsPrivate}}{{t $.Lang "repo.private"}}{{else}}{{t $.Lang "repo.public"}}{{end}}</span>
317 </button>
318 {{end}}
319 </div>
320 {{end}}
321
322 <div data-palette-group>
323 <div class="palette-group-title">{{t .Lang "palette.group.themes"}}</div>
324 <button type="button" class="palette-item" data-palette-item data-palette-theme="light" data-palette-keywords="theme light светлая">
325 <span class="flex items-center gap-2">{{icon "sun" "size-4"}} {{t .Lang "theme.light"}}</span>
326 </button>
327 <button type="button" class="palette-item" data-palette-item data-palette-theme="dark" data-palette-keywords="theme dark темная тёмная">
328 <span class="flex items-center gap-2">{{icon "moon" "size-4"}} {{t .Lang "theme.dark"}}</span>
329 </button>
330 <button type="button" class="palette-item" data-palette-item data-palette-theme="system" data-palette-keywords="theme system авто системная">
331 <span class="flex items-center gap-2">{{icon "laptop" "size-4"}} {{t .Lang "theme.system"}}</span>
332 </button>
333 </div>
334 </div>
335 </div>
336 </div>
337
338 <!-- Keyboard Shortcuts Dialog (?) -->
339 <div id="shortcuts-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "shortcuts.title"}}" aria-hidden="true">
340 <div class="dialog-box">
341 <div class="card-header">
342 <span class="font-semibold flex items-center gap-2">{{icon "keyboard" "size-4"}} {{t .Lang "shortcuts.title"}}</span>
343 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">
344 {{icon "close" "size-4"}}
345 </button>
346 </div>
347 <div class="card-body flex flex-col gap-2 text-sm">
348 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
349 <span>{{t .Lang "shortcuts.palette"}}</span>
350 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">K</kbd></span>
351 </div>
352 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
353 <span>{{t .Lang "shortcuts.sidebar"}}</span>
354 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
355 </div>
356 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
357 <span>{{t .Lang "shortcuts.search"}}</span>
358 <kbd class="kbd">/</kbd>
359 </div>
360 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
361 <span>{{t .Lang "shortcuts.home"}}</span>
362 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
363 </div>
364 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
365 <span>{{t .Lang "shortcuts.repos"}}</span>
366 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
367 </div>
368 <div class="flex items-center justify-between py-2">
369 <span>{{t .Lang "shortcuts.files"}}</span>
370 <kbd class="kbd">t</kbd>
371 </div>
372 </div>
373 </div>
374 </div>
375
376 <div id="toast-container" class="toast-container" aria-live="polite"></div>
377
378 <script src="/static/js/htmx.min.js" defer></script>
379 <script src="/static/js/app.js?v={{assetVer}}" defer></script>
380 </body>
381 </html>