Shell shortcuts: /, D, [ ], Ctrl+K palette + neighbors endpoint

Backend:
- GET /api/files/{md5}/neighbors/ returns previous/next items in the
  current ordering (name, size, created_at) for keyboard navigation

Frontend:
- / focuses the library search input
- D downloads the file on the detail page
- [ and ] navigate to the previous/next item; hint shown on the page
- Ctrl/Cmd+K opens a command palette (navigation, toggle filters,
  focus search, log in/out); Escape closes it
- Sort order now persists alongside the other library filters so
  prev/next stays consistent
This commit is contained in:
2026-09-17 08:33:22 -05:00
parent 063305c552
commit a09ab8a160
8 changed files with 324 additions and 10 deletions
+15 -2
View File
@@ -1,8 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { LogOut, Menu } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
import { CommandPalette } from "@/components/CommandPalette";
import { StatusFooter } from "@/components/StatusFooter";
import { StatusPill } from "@/components/StatusPill";
import { api } from "@/lib/api";
@@ -21,6 +22,7 @@ export function AppShell() {
const logout = useAuth((state) => state.logout);
const toggleSidebar = useUi((state) => state.toggleSidebar);
const location = useLocation();
const [paletteOpen, setPaletteOpen] = useState(false);
const statusQuery = useQuery({
queryKey: ["status"],
@@ -32,9 +34,19 @@ export function AppShell() {
useEffect(() => {
function handleKeyDown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "b") {
const modifier = event.ctrlKey || event.metaKey;
if (modifier && event.key.toLowerCase() === "b") {
event.preventDefault();
toggleSidebar();
return;
}
if (modifier && event.key.toLowerCase() === "k") {
event.preventDefault();
setPaletteOpen((open) => !open);
return;
}
if (event.key === "Escape") {
setPaletteOpen(false);
}
}
window.addEventListener("keydown", handleKeyDown);
@@ -122,6 +134,7 @@ export function AppShell() {
</main>
<StatusFooter status={status} />
<CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} />
</div>
);
}