rin
Публичный Open-source lightweight git hosting.
base.html · 358 строк · 19.3 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 <a href="/" class="nav-item">
66 <span class="flex items-center gap-2">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</span>
67 </a>
68 {{if .ExploreEnabled}}
69 <a href="/explore" class="nav-item">
70 <span class="flex items-center gap-2">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</span>
71 </a>
72 {{end}}
73 {{if .CurrentUser}}
74 <a href="/settings" class="nav-item">
75 <span class="flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</span>
76 </a>
77 {{if .CurrentUser.IsAdmin}}
78 <a href="/admin" class="nav-item">
79 <span class="flex items-center gap-2">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</span>
80 </a>
81 {{end}}
82 {{end}}
83 </nav>
84
85 </div>
86
87 <!-- Sidebar Bottom: 3-Theme Switcher + Account -->
88 <div class="flex flex-col gap-2 pt-2" style="border-top:1px solid hsl(var(--border));">
89 <div class="segmented" role="radiogroup" aria-label="{{t .Lang "nav.theme"}}">
90 <button type="button" class="segmented-btn {{if eq .Theme "light"}}active{{end}}" data-set-theme="light" title="{{t .Lang "theme.light"}}">
91 {{icon "sun" "size-3.5"}}
92 <span>{{t .Lang "theme.light"}}</span>
93 </button>
94 <button type="button" class="segmented-btn {{if eq .Theme "dark"}}active{{end}}" data-set-theme="dark" title="{{t .Lang "theme.dark"}}">
95 {{icon "moon" "size-3.5"}}
96 <span>{{t .Lang "theme.dark"}}</span>
97 </button>
98 <button type="button" class="segmented-btn {{if eq .Theme "system"}}active{{end}}" data-set-theme="system" title="{{t .Lang "theme.system"}}">
99 {{icon "laptop" "size-3.5"}}
100 <span>{{t .Lang "theme.system"}}</span>
101 </button>
102 </div>
103
104 {{if .CurrentUser}}
105 <div class="flex items-center justify-between gap-1" style="padding:0.15rem 0.25rem;">
106 <a href="/{{.CurrentUser.Username}}" class="flex items-center gap-2 min-w-0 truncate text-sm font-medium">
107 {{avatar .CurrentUser.Username .CurrentUser.AvatarURL}}
108 <span class="truncate">{{.CurrentUser.Username}}</span>
109 </a>
110 <div class="flex items-center gap-1">
111 <form method="post" action="/logout" style="margin:0;">
112 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
113 <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"}}">
114 {{icon "log-out" "size-3.5"}}
115 </button>
116 </form>
117 </div>
118 </div>
119 {{else}}
120 <div class="flex flex-col gap-1.5">
121 <a href="/login" class="btn btn-outline btn-sm w-full">{{t .Lang "nav.login"}}</a>
122 {{if .RegistrationEnabled}}
123 <a href="/register" class="btn btn-primary btn-sm w-full">{{t .Lang "nav.register"}}</a>
124 {{end}}
125 </div>
126 {{end}}
127 <div style="padding:0 0.35rem;">
128 <a href="/agents" class="muted-link flex items-center gap-1.5 text-xs" style="padding:0.1rem 0;">
129 {{icon "book-open" "size-3.5"}}
130 <span>{{t .Lang "nav.agents"}}</span>
131 </a>
132 </div>
133 </div>
134 </div>
135 </aside>
136
137 <!-- Main Content Column -->
138 <div class="app-col">
139 <!-- Mobile Topbar (only visible on small screens) -->
140 <div class="mobile-topbar">
141 <div class="flex items-center gap-2">
142 <button type="button" class="btn btn-ghost btn-icon" data-drawer-open aria-label="Menu">
143 {{icon "menu" "size-4"}}
144 </button>
145 <a href="/" class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</a>
146 </div>
147 <div class="flex items-center gap-1.5">
148 <button type="button" class="btn btn-ghost btn-icon" data-open-palette="" aria-label="{{t .Lang "nav.command_palette"}}">
149 {{icon "search" "size-4"}}
150 </button>
151 {{if eq .Lang "en"}}
152 <a href="?lang=ru" class="btn btn-ghost btn-sm font-mono" title="{{t .Lang "nav.lang"}}" aria-label="{{t .Lang "nav.lang"}}">RU</a>
153 {{else}}
154 <a href="?lang=en" class="btn btn-ghost btn-sm font-mono" title="{{t .Lang "nav.lang"}}" aria-label="{{t .Lang "nav.lang"}}">EN</a>
155 {{end}}
156 {{if .CurrentUser}}
157 <a href="/new" class="btn btn-primary btn-sm">{{icon "plus" "size-3.5"}}</a>
158 {{else}}
159 <a href="/login" class="btn btn-outline btn-sm">{{t .Lang "nav.login"}}</a>
160 {{end}}
161 </div>
162 </div>
163
164 <main class="app-main">
165 {{if .Flash}}
166 <div class="container mb-4">
167 <div class="card p-3 flex items-center justify-between gap-2" role="status" style="border-color:hsl(var(--primary) / 0.4);">
168 <span class="flex items-center gap-2 text-sm">{{icon "check" "size-4"}} {{.Flash}}</span>
169 </div>
170 </div>
171 {{end}}
172 {{if .Error}}
173 <div class="container mb-4">
174 <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));">
175 <span class="flex items-center gap-2 text-sm">{{icon "alert-circle" "size-4"}} {{.Error}}</span>
176 </div>
177 </div>
178 {{end}}
179
180 {{template "content" .}}
181 </main>
182 </div>
183 </div>
184
185 <!-- Mobile Drawer -->
186 <div id="mobile-drawer" class="mobile-drawer" aria-hidden="true">
187 <div class="mobile-drawer-panel">
188 <div class="flex flex-col gap-2">
189 <div class="flex items-center justify-between" style="padding-bottom:0.5rem;border-bottom:1px solid hsl(var(--border));">
190 <span class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</span>
191 <button type="button" class="btn btn-ghost btn-icon" data-drawer-close aria-label="{{t .Lang "common.close"}}">
192 {{icon "close" "size-4"}}
193 </button>
194 </div>
195 <nav class="flex flex-col gap-1">
196 <a href="/" class="nav-item">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</a>
197 {{if .CurrentUser}}
198 <a href="/new" class="nav-item">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</a>
199 <a href="/settings" class="nav-item">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</a>
200 {{if .CurrentUser.IsAdmin}}
201 <a href="/admin" class="nav-item">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</a>
202 {{end}}
203 {{end}}
204 {{if .ExploreEnabled}}
205 <a href="/explore" class="nav-item">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</a>
206 {{end}}
207 </nav>
208 </div>
209 <div class="flex flex-col gap-2">
210 <div class="segmented" role="group" aria-label="{{t .Lang "nav.lang"}}">
211 <a href="?lang=ru" class="segmented-btn {{if eq .Lang "ru"}}active{{end}}">RU</a>
212 <a href="?lang=en" class="segmented-btn {{if eq .Lang "en"}}active{{end}}">EN</a>
213 </div>
214 <div class="segmented" role="group" aria-label="{{t .Lang "nav.theme"}}">
215 <button type="button" class="segmented-btn {{if eq .Theme "light"}}active{{end}}" data-set-theme="light">{{t .Lang "theme.light"}}</button>
216 <button type="button" class="segmented-btn {{if eq .Theme "dark"}}active{{end}}" data-set-theme="dark">{{t .Lang "theme.dark"}}</button>
217 <button type="button" class="segmented-btn {{if eq .Theme "system"}}active{{end}}" data-set-theme="system">{{t .Lang "theme.system"}}</button>
218 </div>
219 </div>
220 </div>
221 </div>
222
223 <!-- Command Palette (Ctrl/Cmd+K) -->
224 <div id="command-palette" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "nav.command_palette"}}" aria-hidden="true">
225 <div class="palette-box">
226 <div class="palette-input-wrap">
227 {{icon "search" "size-4"}}
228 <input id="command-palette-input" type="text" class="palette-input" placeholder="{{t .Lang "palette.placeholder"}}" autocomplete="off">
229 <kbd class="kbd">ESC</kbd>
230 </div>
231 <div class="palette-list" role="listbox">
232 <div data-palette-group>
233 <div class="palette-group-title">{{t .Lang "palette.group.nav"}}</div>
234 <button type="button" class="palette-item" data-palette-item data-palette-href="/" data-palette-keywords="home главная домой">
235 <span class="flex items-center gap-2">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</span>
236 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
237 </button>
238 {{if .ExploreEnabled}}
239 <button type="button" class="palette-item" data-palette-item data-palette-href="/explore" data-palette-keywords="explore обзор публичные">
240 <span class="flex items-center gap-2">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</span>
241 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
242 </button>
243 {{end}}
244 <button type="button" class="palette-item" data-palette-item data-palette-href="/agents" data-palette-keywords="agents ai api guide агенты документация">
245 <span class="flex items-center gap-2">{{icon "book-open" "size-4"}} {{t .Lang "nav.agents"}}</span>
246 </button>
247 {{if .CurrentUser}}
248 <button type="button" class="palette-item" data-palette-item data-palette-href="/new" data-palette-keywords="new create создать репозиторий">
249 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</span>
250 </button>
251 <button type="button" class="palette-item" data-palette-item data-palette-href="/settings" data-palette-keywords="settings tokens настройки токены">
252 <span class="flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</span>
253 </button>
254 {{if .CurrentUser.IsAdmin}}
255 <button type="button" class="palette-item" data-palette-item data-palette-href="/admin" data-palette-keywords="admin админка">
256 <span class="flex items-center gap-2">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</span>
257 </button>
258 {{end}}
259 {{else}}
260 <button type="button" class="palette-item" data-palette-item data-palette-href="/login" data-palette-keywords="login войти">
261 <span class="flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "nav.login"}}</span>
262 </button>
263 <button type="button" class="palette-item" data-palette-item data-palette-href="/register" data-palette-keywords="register регистрация">
264 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "nav.register"}}</span>
265 </button>
266 {{end}}
267 <button type="button" class="palette-item" data-palette-item data-palette-action="toggle-sidebar" data-palette-keywords="sidebar toggle боковая панель скрыть">
268 <span class="flex items-center gap-2">{{icon "sidebar" "size-4"}} {{t .Lang "nav.toggle_sidebar"}}</span>
269 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
270 </button>
271 </div>
272
273 {{if .PaletteFiles}}
274 <div data-palette-group>
275 <div class="palette-group-title">{{t .Lang "repo.files"}} ({{.Repo.OwnerUsername}}/{{.Repo.Name}})</div>
276 {{range .PaletteFiles}}
277 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{$.Repo.OwnerUsername}}/{{$.Repo.Name}}/blob/{{$.Ref}}/{{.}}" data-palette-keywords="file: {{.}}">
278 <span class="flex items-center gap-2 font-mono text-xs">{{icon "file-code" "size-4"}} {{.}}</span>
279 </button>
280 {{end}}
281 </div>
282 {{end}}
283
284 {{if .PaletteRepos}}
285 <div data-palette-group>
286 <div class="palette-group-title">{{t .Lang "palette.group.repos"}}</div>
287 {{range .PaletteRepos}}
288 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{.OwnerUsername}}/{{.Name}}" data-palette-keywords="repo {{.OwnerUsername}}/{{.Name}} {{.Description}}">
289 <span class="flex items-center gap-2">
290 {{if .IsPrivate}}{{icon "lock" "size-4"}}{{else}}{{icon "git-branch" "size-4"}}{{end}}
291 <span>{{.OwnerUsername}}/<strong>{{.Name}}</strong></span>
292 </span>
293 <span class="text-xs text-muted">{{if .IsPrivate}}{{t $.Lang "repo.private"}}{{else}}{{t $.Lang "repo.public"}}{{end}}</span>
294 </button>
295 {{end}}
296 </div>
297 {{end}}
298
299 <div data-palette-group>
300 <div class="palette-group-title">{{t .Lang "palette.group.themes"}}</div>
301 <button type="button" class="palette-item" data-palette-item data-palette-theme="light" data-palette-keywords="theme light светлая">
302 <span class="flex items-center gap-2">{{icon "sun" "size-4"}} {{t .Lang "theme.light"}}</span>
303 </button>
304 <button type="button" class="palette-item" data-palette-item data-palette-theme="dark" data-palette-keywords="theme dark темная тёмная">
305 <span class="flex items-center gap-2">{{icon "moon" "size-4"}} {{t .Lang "theme.dark"}}</span>
306 </button>
307 <button type="button" class="palette-item" data-palette-item data-palette-theme="system" data-palette-keywords="theme system авто системная">
308 <span class="flex items-center gap-2">{{icon "laptop" "size-4"}} {{t .Lang "theme.system"}}</span>
309 </button>
310 </div>
311 </div>
312 </div>
313 </div>
314
315 <!-- Keyboard Shortcuts Dialog (?) -->
316 <div id="shortcuts-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "shortcuts.title"}}" aria-hidden="true">
317 <div class="dialog-box">
318 <div class="card-header">
319 <span class="font-semibold flex items-center gap-2">{{icon "keyboard" "size-4"}} {{t .Lang "shortcuts.title"}}</span>
320 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">
321 {{icon "close" "size-4"}}
322 </button>
323 </div>
324 <div class="card-body flex flex-col gap-2 text-sm">
325 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
326 <span>{{t .Lang "shortcuts.palette"}}</span>
327 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">K</kbd></span>
328 </div>
329 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
330 <span>{{t .Lang "shortcuts.sidebar"}}</span>
331 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
332 </div>
333 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
334 <span>{{t .Lang "shortcuts.search"}}</span>
335 <kbd class="kbd">/</kbd>
336 </div>
337 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
338 <span>{{t .Lang "shortcuts.home"}}</span>
339 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
340 </div>
341 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
342 <span>{{t .Lang "nav.explore"}}</span>
343 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
344 </div>
345 <div class="flex items-center justify-between py-2">
346 <span>{{t .Lang "shortcuts.files"}}</span>
347 <kbd class="kbd">t</kbd>
348 </div>
349 </div>
350 </div>
351 </div>
352
353 <div id="toast-container" class="toast-container" aria-live="polite"></div>
354
355 <script src="/static/js/htmx.min.js" defer></script>
356 <script src="/static/js/app.js?v={{assetVer}}" defer></script>
357 </body>
358 </html>