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