✨ (nav): Dismiss the account menu on escape or an outside click
Changes
4 files changed, +47 -2
MODIFY
README.md
+1 -1
@@ -24,7 +24,7 @@
24
24
- public-key authentication only; keys managed per user in the UI
25
25
- the server binds an unprivileged port (default 2222); the port shown in URLs is set
26
26
separately via `GITSHARK_SSH_EXTERNAL_PORT` (default 22) to match your host mapping
27
-- 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; Markdown files render to HTML by default with a Rendered/Code toggle), a rendered README (commonmark-java with GFM tables, 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, Releases, Issues, Merge requests, plus Settings 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
27
+- 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 `<details>` menu that needs no JavaScript and, where JavaScript runs, closes on `Escape` or a click outside), 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; Markdown files render to HTML by default with a Rendered/Code toggle), a rendered README (commonmark-java with GFM tables, 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, Releases, Issues, Merge requests, plus Settings 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 or the open account menu, `g h` goes home) — every page works fully without JavaScript
28
28
- Per-repository issues: title, optional description (rendered as Markdown, XSS-safe), per-repo sequential number (`#1`, `#2`, …), an author, and an optional assignee (any local user, set by username; blank clears it); created and managed by the repo owner and collaborators, readable by anyone who can read the repo, via a dedicated "New issue" page; title and description can be edited afterwards via an "Edit issue" page. Issue pages are addressed by number (`…/issues/1`); old UUID URLs redirect permanently to the number form
29
29
- 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
30
30
- 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-hotkeys.js
+31 -0
@@ -3,6 +3,8 @@
3
3
"use strict";
4
4
5
5
var SEQUENCE_TIMEOUT_MS = 1000;
6
+ // <details> popovers that must behave like a menu: dismissed by Escape or a click outside.
7
+ var MENU_SELECTOR = "details.user-menu";
6
8
var pendingKey = null;
7
9
var pendingTimer = null;
8
10
@@ -18,6 +20,19 @@
18
20
return document.getElementById("hotkey-help");
19
21
}
20
22
23
+ function openMenu() {
24
+ return document.querySelector(MENU_SELECTOR + "[open]");
25
+ }
26
+
27
+ function closeMenus(except) {
28
+ var menus = document.querySelectorAll(MENU_SELECTOR + "[open]");
29
+ for (var i = 0; i < menus.length; i++) {
30
+ if (menus[i] !== except) {
31
+ menus[i].removeAttribute("open");
32
+ }
33
+ }
34
+ }
35
+
21
36
function clearPending() {
22
37
pendingKey = null;
23
38
if (pendingTimer) {
@@ -34,10 +49,20 @@
34
49
var dialog = helpDialog();
35
50
36
51
if (event.key === "Escape") {
52
+ var menu = openMenu();
37
53
if (dialog && dialog.open) {
38
54
dialog.close();
39
55
event.preventDefault();
40
56
}
57
+ else if (menu) {
58
+ closeMenus();
59
+ // focus would otherwise stay on the now-hidden menu item
60
+ var summary = menu.querySelector("summary");
61
+ if (summary) {
62
+ summary.focus();
63
+ }
64
+ event.preventDefault();
65
+ }
41
66
clearPending();
42
67
return;
43
68
}
@@ -63,6 +88,12 @@
63
88
}
64
89
});
65
90
91
+ // Any click outside an open menu dismisses it; the menu that was clicked keeps its native toggle.
92
+ document.addEventListener("click", function (event) {
93
+ var target = event.target;
94
+ closeMenus(target.closest ? target.closest(MENU_SELECTOR) : null);
95
+ });
96
+
66
97
// Generic dialog opener: [data-open-dialog="id"] shows the matching <dialog> as a modal.
67
98
document.addEventListener("click", function (event) {
68
99
var trigger = event.target.closest("[data-open-dialog]");
MODIFY
src/main/resources/templates/layout.html
+1 -1
@@ -42,7 +42,7 @@
42
42
<table>
43
43
<tr><td><kbd>?</kbd></td><td>Show this help</td></tr>
44
44
<tr><td><kbd>g</kbd> <kbd>h</kbd></td><td>Go home</td></tr>
45
- <tr><td><kbd>Esc</kbd></td><td>Close dialog</td></tr>
45
+ <tr><td><kbd>Esc</kbd></td><td>Close dialog or menu</td></tr>
46
46
</table>
47
47
</dialog>
48
48
{#insert scripts}{/}
MODIFY
src/test/java/de/workaround/web/WebUiTest.java
+14 -0
@@ -18,6 +18,7 @@
18
18
import static io.restassured.RestAssured.given;
19
19
import static org.hamcrest.CoreMatchers.containsString;
20
20
import static org.hamcrest.CoreMatchers.not;
21
+import static org.junit.jupiter.api.Assertions.assertTrue;
21
22
22
23
@QuarkusTest
23
24
class WebUiTest
@@ -605,6 +606,19 @@
605
606
}
606
607
607
608
@Test
609
+ void accountMenuIsDismissedByEscapeAndByClickingOutside()
610
+ {
611
+ String script = given().when().get("/shark-hotkeys.js")
612
+ .then().statusCode(200)
613
+ .extract().body().asString();
614
+
615
+ assertTrue(script.contains("details.user-menu"),
616
+ "the account dropdown must be wired for dismissal in shark-hotkeys.js");
617
+ assertTrue(script.contains("closeMenus"),
618
+ "shark-hotkeys.js must close open menus (escape handler and outside click)");
619
+ }
620
+
621
+ @Test
608
622
@TestSecurity(user = "ui-mallory")
609
623
void privateRepositoryHiddenFromStranger() throws Exception
610
624
{