mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
feat(web): scoped insights/history keep the file or folder selected in the tree
On /insights/<path> and /history/<path> the tree highlights (and unfolds to) the target; Dashboard/History menu items light up only for their root, project-wide views. 50/50 e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbiaaVM2ACxeRi8ySG9ybc
This commit is contained in:
co-authored by
Claude Fable 5
parent
d18ad1fa91
commit
7ddb211fcf
@@ -9,6 +9,9 @@ test("insights via ⋯ scopes to the open file", async ({ page }) => {
|
||||
await page.click("#more-menu .more-item:has-text('Insights')");
|
||||
await expect(page).toHaveURL(`/${pid}/insights/notes/readme.md`);
|
||||
await expect(page.locator(".in-title .in-scope")).toContainText("notes/readme.md");
|
||||
// The subject stays selected in the tree; Dashboard does NOT light up.
|
||||
await expect(page.locator('#tree .row[data-path="notes/readme.md"]')).toHaveClass(/active/);
|
||||
await expect(page.locator("#nav-dashboard")).not.toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("insights via ⋯ scopes to the selected folder", async ({ page }) => {
|
||||
@@ -19,4 +22,14 @@ test("insights via ⋯ scopes to the selected folder", async ({ page }) => {
|
||||
await page.click("#more-menu .more-item:has-text('Insights')");
|
||||
await expect(page).toHaveURL(`/${pid}/insights/notes`);
|
||||
await expect(page.locator(".in-title .in-scope")).toContainText("notes");
|
||||
await expect(page.locator('#tree .row[data-path="notes"]')).toHaveClass(/active/);
|
||||
await expect(page.locator("#nav-dashboard")).not.toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("root Dashboard still lights the menu, not the tree", async ({ page }) => {
|
||||
await login(page);
|
||||
await wikiId(page);
|
||||
await page.click("#nav-dashboard");
|
||||
await expect(page.locator("#nav-dashboard")).toHaveClass(/active/);
|
||||
await expect(page.locator("#tree .row.active")).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -59,6 +59,9 @@ export default function Browser(props: {
|
||||
}, [insightsOpen, apiBase, qc]);
|
||||
|
||||
const path = route.path;
|
||||
// On scoped view routes (/insights/<p>, /history/<p>) the subject of the
|
||||
// page is the target — the tree highlights it, not a menu item.
|
||||
const treePath = path || (route.view === "insights" || route.view === "history" ? route.viewTarget || "" : "");
|
||||
const isDir = !!path && dirIndex.has(path);
|
||||
// A file only counts as one when the tree actually contains it — a
|
||||
// missing path gets the not-found view, not a broken file view.
|
||||
@@ -78,18 +81,19 @@ export default function Browser(props: {
|
||||
if (rootDirs.length === 1) setExpanded((s) => new Set(s).add(rootDirs[0].path));
|
||||
}, [tree]);
|
||||
useEffect(() => {
|
||||
// Opening any path (tree click, palette, wikilink, deep link) unfolds
|
||||
// the way to it; a selected folder itself opens too.
|
||||
if (!path || !loaded) return;
|
||||
// Opening any path (tree click, palette, wikilink, deep link — or a
|
||||
// scoped insights/history view of it) unfolds the way to it; a selected
|
||||
// folder itself opens too.
|
||||
if (!treePath || !loaded) return;
|
||||
setExpanded((s) => {
|
||||
const next = new Set(s);
|
||||
for (const a of ancestorsOf(path)) next.add(a);
|
||||
if (dirIndex.has(path)) next.add(path);
|
||||
for (const a of ancestorsOf(treePath)) next.add(a);
|
||||
if (dirIndex.has(treePath)) next.add(treePath);
|
||||
return next;
|
||||
});
|
||||
const row = document.querySelector(`#tree .row[data-path="${CSS.escape(path)}"]`);
|
||||
const row = document.querySelector(`#tree .row[data-path="${CSS.escape(treePath)}"]`);
|
||||
if (row) row.scrollIntoView({ block: "nearest" });
|
||||
}, [path, loaded, dirIndex]);
|
||||
}, [treePath, loaded, dirIndex]);
|
||||
const onToggle = useCallback((p: string) => {
|
||||
setExpanded((s) => {
|
||||
const next = new Set(s);
|
||||
@@ -420,7 +424,7 @@ export default function Browser(props: {
|
||||
root={tree}
|
||||
expanded={expanded}
|
||||
onToggle={onToggle}
|
||||
currentPath={path}
|
||||
currentPath={treePath}
|
||||
listingShowing={listingShowing}
|
||||
onOpen={openPath}
|
||||
/>
|
||||
|
||||
@@ -192,13 +192,16 @@ export default function HubApp({ config }: { config: ServerConfig }) {
|
||||
projects={projects}
|
||||
currentId={current.id}
|
||||
menu={{
|
||||
// Scoped views (/insights/<path>, /history/<path>) belong to
|
||||
// the file/folder — the tree carries the selection, no menu
|
||||
// item lights up.
|
||||
active: panel
|
||||
? null
|
||||
: route.view === "insights"
|
||||
: route.view === "insights" && !route.viewTarget
|
||||
? "dashboard"
|
||||
: route.view === "install"
|
||||
? "install"
|
||||
: route.view === "history"
|
||||
: route.view === "history" && !route.viewTarget
|
||||
? "history"
|
||||
: route.view === "settings"
|
||||
? "settings"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BearDrive</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐻</text></svg>">
|
||||
<script type="module" crossorigin src="/assets/index-BOZlnkUa.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-D_qY6bcM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ch-p2wBE.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user