๐ (ui): Collapse account links into an Account header dropdown
Changes
4 files changed, +73 -8
MODIFY
README.md
+1 -1
@@ -14,7 +14,7 @@
14
14
- push and private read authenticate with **personal access tokens** (HTTP Basic password)
15
15
- Clone/fetch/push over `ssh://git@<host>:2222/<owner>/<repo>.git`
16
16
- public-key authentication only; keys managed per user in the UI
17
-- Web UI: an auth-aware header nav (a "Log in" button for visitors; Profile, SSH keys, Access tokens, and Logout links only for signed-in users), landing page with login CTA for visitors (`/`), repository list for authenticated users (`/`), public repository browse at `/explore`, file/tree browser with self-hosted syntax highlighting (extension-based language detection, falls back to plain text for unknown extensions and binary files), a rendered README (commonmark-java, XSS-safe) shown below the file list on the repository overview page, commit log (paginated), branches, tags (own dedicated page, separate from branches), one-time handle selection (`/onboarding`), profile settings (`/settings/profile`). Every repository sub-page shows a persistent left sidebar with repo identity, a Clone button opening the clone dialog, a pin toggle, and section navigation (Code, Commits, Branches, Tags, Issues, Merge requests, plus Collaborators for the owner) with per-section counts and active-section highlighting, and the clone panel has copy-to-clipboard buttons for the HTTP and SSH `git clone` commands. Keyboard shortcuts are an optional, progressive enhancement (`?` opens a help overlay, `Escape` closes it, `g h` goes home) โ every page works fully without JavaScript
17
+- Web UI: an auth-aware header nav (a "Log in" button for visitors; for signed-in users a top-level Following link plus an Account dropdown holding Profile, SSH keys, Access tokens, and Logout โ a JS-free `<details>` menu), landing page with login CTA for visitors (`/`), repository list for authenticated users (`/`), public repository browse at `/explore`, file/tree browser with self-hosted syntax highlighting (extension-based language detection, falls back to plain text for unknown extensions and binary files), a rendered README (commonmark-java, XSS-safe) shown below the file list on the repository overview page, commit log (paginated), branches, tags (own dedicated page, separate from branches), one-time handle selection (`/onboarding`), profile settings (`/settings/profile`). Every repository sub-page shows a persistent left sidebar with repo identity, a Clone button opening the clone dialog, a pin toggle, and section navigation (Code, Commits, Branches, Tags, Issues, Merge requests, plus Collaborators for the owner) with per-section counts and active-section highlighting, and the clone panel has copy-to-clipboard buttons for the HTTP and SSH `git clone` commands. Keyboard shortcuts are an optional, progressive enhancement (`?` opens a help overlay, `Escape` closes it, `g h` goes home) โ every page works fully without JavaScript
18
18
- Per-repository issues: title, optional description, per-repo sequential number (`#1`, `#2`, โฆ), and author; created and managed by the repo owner and collaborators, readable by anyone who can read the repo, via a dedicated "New issue" page
19
19
- Issues move through a fixed lifecycle (Planned โ In development โ Done); the repo navigation shows the open (Planned + In development) issue count, and Done issues collapse into an "Archive" section on the issues page
20
20
- Issues auto-close from pushed commit messages, GitHub-style (`close(s|d)`/`fix(es|ed)`/`resolve(s|d)` + `#<number>`, e.g. `fixes #12`), over both HTTP and SSH pushes
MODIFY
src/main/resources/META-INF/resources/shark.css
+59 -3
@@ -63,6 +63,7 @@
63
63
--success: oklch(0.55 0.12 150);
64
64
--success-deep: oklch(0.47 0.11 150);
65
65
--danger: oklch(0.55 0.17 27);
66
+ --danger-deep: oklch(0.45 0.17 27);
66
67
--danger-bg: oklch(0.965 0.02 27);
67
68
--danger-border: oklch(0.85 0.08 27);
68
69
--attention: oklch(0.58 0.1 75);
@@ -221,23 +222,77 @@
221
222
color: var(--accent-deep);
222
223
}
223
224
225
+header.site nav .user-menu {
226
+ position: relative;
227
+}
228
+
229
+header.site nav .user-menu summary {
230
+ list-style: none;
231
+ color: var(--ink);
232
+ font-size: 14px;
233
+ font-weight: 500;
234
+ cursor: pointer;
235
+ transition: color .12s;
236
+}
237
+
238
+header.site nav .user-menu summary::-webkit-details-marker {
239
+ display: none;
240
+}
241
+
242
+header.site nav .user-menu summary::after {
243
+ content: " \25BE";
244
+ font-size: 11px;
245
+}
246
+
247
+header.site nav .user-menu summary:hover {
248
+ color: var(--accent-deep);
249
+}
250
+
251
+header.site nav .user-menu summary:focus-visible {
252
+ outline: 2px solid var(--accent);
253
+ outline-offset: 2px;
254
+ border-radius: var(--radius-sm);
255
+}
256
+
257
+header.site nav .user-menu[open] summary {
258
+ color: var(--accent-deep);
259
+}
260
+
261
+header.site nav .user-menu .user-menu-items {
262
+ position: absolute;
263
+ right: 0;
264
+ top: calc(100% + var(--s2));
265
+ display: flex;
266
+ flex-direction: column;
267
+ gap: var(--s2);
268
+ min-width: 160px;
269
+ padding: var(--s3);
270
+ background: var(--surface);
271
+ border: 1px solid var(--border);
272
+ border-radius: var(--radius-sm);
273
+ box-shadow: 0 4px 12px rgba(0, 0, 0, .08);
274
+ z-index: 10;
275
+}
276
+
224
277
header.site nav .logout {
225
278
display: flex;
226
279
margin: 0;
280
+ border-top: 1px solid var(--border-soft);
281
+ padding-top: var(--s2);
227
282
}
228
283
229
284
header.site nav .logout button {
230
285
padding: 0;
231
286
border: none;
232
287
background: none;
233
- color: var(--ink);
288
+ color: var(--danger);
234
289
font: 500 14px/1 var(--font);
235
290
cursor: pointer;
236
291
transition: color .12s;
237
292
}
238
293
239
294
header.site nav .logout button:hover {
240
- color: var(--accent-deep);
295
+ color: var(--danger-deep);
241
296
}
242
297
243
298
header.site nav .logout button:focus-visible {
@@ -332,8 +387,9 @@
332
387
.ascii-bg .ascii-row { animation: none; opacity: 0.1; }
333
388
}
334
389
335
-/* keep header and content above the backdrop */
390
+/* keep header and content above the backdrop; header above main so its dropdown overlays content */
336
391
header.site, main { position: relative; z-index: 1; }
392
+header.site { z-index: 2; }
337
393
338
394
/* centered landing hero distinct from the in-app repo hero */
339
395
.landing {
MODIFY
src/main/resources/templates/layout.html
+9 -4
@@ -15,10 +15,15 @@
15
15
{#insert nav}
16
16
{#if cdi:currentUser.loggedIn}
17
17
<a href="/following">Following</a>
18
- <a href="/settings/profile">{#avatar user=cdi:currentUser.get /} Profile</a>
19
- <a href="/settings/keys">SSH keys</a>
20
- <a href="/settings/tokens">Access tokens</a>
21
- <form class="logout" method="post" action="/logout"><button type="submit">Logout</button></form>
18
+ <details class="user-menu">
19
+ <summary>Account</summary>
20
+ <div class="user-menu-items">
21
+ <a href="/settings/profile">{#avatar user=cdi:currentUser.get /} Profile</a>
22
+ <a href="/settings/keys">SSH keys</a>
23
+ <a href="/settings/tokens">Access tokens</a>
24
+ <form class="logout" method="post" action="/logout"><button type="submit">Logout</button></form>
25
+ </div>
26
+ </details>
22
27
{#else}
23
28
<a class="btn btn-primary" href="/login">Log in</a>
24
29
{/if}
MODIFY
src/test/java/de/workaround/web/WebUiTest.java
+4 -0
@@ -522,8 +522,12 @@
522
522
.when().get("/explore")
523
523
.then()
524
524
.statusCode(200)
525
+ // account links are collapsed into a single dropdown menu...
526
+ .body(containsString("class=\"user-menu\""))
525
527
.body(containsString("Access tokens"))
526
528
.body(containsString("Logout"))
529
+ // ...while Following stays a top-level nav link outside the menu
530
+ .body(containsString("href=\"/following\""))
527
531
.body(not(containsString("href=\"/login\"")));
528
532
}
529
533