rin
Public Open-source lightweight git hosting.
base.html · 347 lines · 18.5 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 .CurrentUser}}
152 <a href="/new" class="btn btn-primary btn-sm">{{icon "plus" "size-3.5"}}</a>
153 {{else}}
154 <a href="/login" class="btn btn-outline btn-sm">{{t .Lang "nav.login"}}</a>
155 {{end}}
156 </div>
157 </div>
158
159 <main class="app-main">
160 {{if .Flash}}
161 <div class="container mb-4">
162 <div class="card p-3 flex items-center justify-between gap-2" role="status" style="border-color:hsl(var(--primary) / 0.4);">
163 <span class="flex items-center gap-2 text-sm">{{icon "check" "size-4"}} {{.Flash}}</span>
164 </div>
165 </div>
166 {{end}}
167 {{if .Error}}
168 <div class="container mb-4">
169 <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));">
170 <span class="flex items-center gap-2 text-sm">{{icon "alert-circle" "size-4"}} {{.Error}}</span>
171 </div>
172 </div>
173 {{end}}
174
175 {{template "content" .}}
176 </main>
177 </div>
178 </div>
179
180 <!-- Mobile Drawer -->
181 <div id="mobile-drawer" class="mobile-drawer" aria-hidden="true">
182 <div class="mobile-drawer-panel">
183 <div class="flex flex-col gap-2">
184 <div class="flex items-center justify-between" style="padding-bottom:0.5rem;border-bottom:1px solid hsl(var(--border));">
185 <span class="font-semibold text-base tracking-tight" style="letter-spacing:-0.02em;">Polar</span>
186 <button type="button" class="btn btn-ghost btn-icon" data-drawer-close aria-label="{{t .Lang "common.close"}}">
187 {{icon "close" "size-4"}}
188 </button>
189 </div>
190 <nav class="flex flex-col gap-1">
191 <a href="/" class="nav-item">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</a>
192 {{if .CurrentUser}}
193 <a href="/new" class="nav-item">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</a>
194 <a href="/settings" class="nav-item">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</a>
195 {{if .CurrentUser.IsAdmin}}
196 <a href="/admin" class="nav-item">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</a>
197 {{end}}
198 {{end}}
199 {{if .ExploreEnabled}}
200 <a href="/explore" class="nav-item">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</a>
201 {{end}}
202 </nav>
203 </div>
204 <div class="segmented">
205 <button type="button" class="segmented-btn" data-set-theme="light">{{t .Lang "theme.light"}}</button>
206 <button type="button" class="segmented-btn" data-set-theme="dark">{{t .Lang "theme.dark"}}</button>
207 <button type="button" class="segmented-btn" data-set-theme="system">{{t .Lang "theme.system"}}</button>
208 </div>
209 </div>
210 </div>
211
212 <!-- Command Palette (Ctrl/Cmd+K) -->
213 <div id="command-palette" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "nav.command_palette"}}" aria-hidden="true">
214 <div class="palette-box">
215 <div class="palette-input-wrap">
216 {{icon "search" "size-4"}}
217 <input id="command-palette-input" type="text" class="palette-input" placeholder="{{t .Lang "palette.placeholder"}}" autocomplete="off">
218 <kbd class="kbd">ESC</kbd>
219 </div>
220 <div class="palette-list" role="listbox">
221 <div data-palette-group>
222 <div class="palette-group-title">{{t .Lang "palette.group.nav"}}</div>
223 <button type="button" class="palette-item" data-palette-item data-palette-href="/" data-palette-keywords="home главная домой">
224 <span class="flex items-center gap-2">{{icon "home" "size-4"}} {{t .Lang "nav.home"}}</span>
225 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
226 </button>
227 {{if .ExploreEnabled}}
228 <button type="button" class="palette-item" data-palette-item data-palette-href="/explore" data-palette-keywords="explore обзор публичные">
229 <span class="flex items-center gap-2">{{icon "globe" "size-4"}} {{t .Lang "nav.explore"}}</span>
230 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
231 </button>
232 {{end}}
233 <button type="button" class="palette-item" data-palette-item data-palette-href="/agents" data-palette-keywords="agents ai api guide агенты документация">
234 <span class="flex items-center gap-2">{{icon "book-open" "size-4"}} {{t .Lang "nav.agents"}}</span>
235 </button>
236 {{if .CurrentUser}}
237 <button type="button" class="palette-item" data-palette-item data-palette-href="/new" data-palette-keywords="new create создать репозиторий">
238 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "repo.new.title"}}</span>
239 </button>
240 <button type="button" class="palette-item" data-palette-item data-palette-href="/settings" data-palette-keywords="settings tokens настройки токены">
241 <span class="flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "nav.settings"}}</span>
242 </button>
243 {{if .CurrentUser.IsAdmin}}
244 <button type="button" class="palette-item" data-palette-item data-palette-href="/admin" data-palette-keywords="admin админка">
245 <span class="flex items-center gap-2">{{icon "shield" "size-4"}} {{t .Lang "nav.admin"}}</span>
246 </button>
247 {{end}}
248 {{else}}
249 <button type="button" class="palette-item" data-palette-item data-palette-href="/login" data-palette-keywords="login войти">
250 <span class="flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "nav.login"}}</span>
251 </button>
252 <button type="button" class="palette-item" data-palette-item data-palette-href="/register" data-palette-keywords="register регистрация">
253 <span class="flex items-center gap-2">{{icon "plus" "size-4"}} {{t .Lang "nav.register"}}</span>
254 </button>
255 {{end}}
256 <button type="button" class="palette-item" data-palette-item data-palette-action="toggle-sidebar" data-palette-keywords="sidebar toggle боковая панель скрыть">
257 <span class="flex items-center gap-2">{{icon "sidebar" "size-4"}} {{t .Lang "nav.toggle_sidebar"}}</span>
258 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
259 </button>
260 </div>
261
262 {{if .PaletteFiles}}
263 <div data-palette-group>
264 <div class="palette-group-title">{{t .Lang "repo.files"}} ({{.Repo.OwnerUsername}}/{{.Repo.Name}})</div>
265 {{range .PaletteFiles}}
266 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{$.Repo.OwnerUsername}}/{{$.Repo.Name}}/blob/{{$.Ref}}/{{.}}" data-palette-keywords="file: {{.}}">
267 <span class="flex items-center gap-2 font-mono text-xs">{{icon "file-code" "size-4"}} {{.}}</span>
268 </button>
269 {{end}}
270 </div>
271 {{end}}
272
273 {{if .PaletteRepos}}
274 <div data-palette-group>
275 <div class="palette-group-title">{{t .Lang "palette.group.repos"}}</div>
276 {{range .PaletteRepos}}
277 <button type="button" class="palette-item" data-palette-item data-palette-href="/{{.OwnerUsername}}/{{.Name}}" data-palette-keywords="repo {{.OwnerUsername}}/{{.Name}} {{.Description}}">
278 <span class="flex items-center gap-2">
279 {{if .IsPrivate}}{{icon "lock" "size-4"}}{{else}}{{icon "git-branch" "size-4"}}{{end}}
280 <span>{{.OwnerUsername}}/<strong>{{.Name}}</strong></span>
281 </span>
282 <span class="text-xs text-muted">{{if .IsPrivate}}{{t $.Lang "repo.private"}}{{else}}{{t $.Lang "repo.public"}}{{end}}</span>
283 </button>
284 {{end}}
285 </div>
286 {{end}}
287
288 <div data-palette-group>
289 <div class="palette-group-title">{{t .Lang "palette.group.themes"}}</div>
290 <button type="button" class="palette-item" data-palette-item data-palette-theme="light" data-palette-keywords="theme light светлая">
291 <span class="flex items-center gap-2">{{icon "sun" "size-4"}} {{t .Lang "theme.light"}}</span>
292 </button>
293 <button type="button" class="palette-item" data-palette-item data-palette-theme="dark" data-palette-keywords="theme dark темная тёмная">
294 <span class="flex items-center gap-2">{{icon "moon" "size-4"}} {{t .Lang "theme.dark"}}</span>
295 </button>
296 <button type="button" class="palette-item" data-palette-item data-palette-theme="system" data-palette-keywords="theme system авто системная">
297 <span class="flex items-center gap-2">{{icon "laptop" "size-4"}} {{t .Lang "theme.system"}}</span>
298 </button>
299 </div>
300 </div>
301 </div>
302 </div>
303
304 <!-- Keyboard Shortcuts Dialog (?) -->
305 <div id="shortcuts-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "shortcuts.title"}}" aria-hidden="true">
306 <div class="dialog-box">
307 <div class="card-header">
308 <span class="font-semibold flex items-center gap-2">{{icon "keyboard" "size-4"}} {{t .Lang "shortcuts.title"}}</span>
309 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">
310 {{icon "close" "size-4"}}
311 </button>
312 </div>
313 <div class="card-body flex flex-col gap-2 text-sm">
314 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
315 <span>{{t .Lang "shortcuts.palette"}}</span>
316 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">K</kbd></span>
317 </div>
318 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
319 <span>{{t .Lang "shortcuts.sidebar"}}</span>
320 <span class="flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><kbd class="kbd">B</kbd></span>
321 </div>
322 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
323 <span>{{t .Lang "shortcuts.search"}}</span>
324 <kbd class="kbd">/</kbd>
325 </div>
326 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
327 <span>{{t .Lang "shortcuts.home"}}</span>
328 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">h</kbd></span>
329 </div>
330 <div class="flex items-center justify-between py-2" style="border-bottom:1px solid hsl(var(--border));">
331 <span>{{t .Lang "nav.explore"}}</span>
332 <span class="flex items-center gap-1"><kbd class="kbd">g</kbd><kbd class="kbd">r</kbd></span>
333 </div>
334 <div class="flex items-center justify-between py-2">
335 <span>{{t .Lang "shortcuts.files"}}</span>
336 <kbd class="kbd">t</kbd>
337 </div>
338 </div>
339 </div>
340 </div>
341
342 <div id="toast-container" class="toast-container" aria-live="polite"></div>
343
344 <script src="/static/js/htmx.min.js" defer></script>
345 <script src="/static/js/app.js?v={{assetVer}}" defer></script>
346 </body>
347 </html>