feat(search): add breadcrumb if a folder is provided

This commit is contained in:
Edouard Vanbelle
2026-07-26 17:51:13 +02:00
parent 99e6de6fce
commit 01076029aa
2 changed files with 119 additions and 29 deletions
+67 -1
View File
@@ -11,7 +11,7 @@
import { page } from '$app/state';
import { searchResources } from '$lib/api/endpoints/search';
import { fileDownloadUrl, renameFile, deleteFile } from '$lib/api/endpoints/files';
import { renameFolder, deleteFolder } from '$lib/api/endpoints/folders';
import { renameFolder, deleteFolder, getFolder, getFolderName } from '$lib/api/endpoints/folders';
import {
addFavorite,
removeFavorite,
@@ -47,6 +47,37 @@
// this session (the pre-URL-param behaviour).
const effectiveFolder = $derived(scopeFolderId ?? filesStore.currentFolder ?? null);
// Breadcrumb — resolves the scope folder's display name so the sticky
// header can show WHICH directory the results come from ("we have no
// clue on which directory the search was done" — Ed 2026-07-26).
// `getFolderName` is a sync cache peek populated by prior /files
// listings; on a cold /search deep-link we fall back to `getFolder`
// once, cache the result, and re-render. `$state<string | null>`
// with a `$effect` primer avoids blocking the initial render.
let scopeFolderName = $state<string | null>(null);
$effect(() => {
if (!scopeFolderId) {
scopeFolderName = null;
return;
}
const cached = getFolderName(scopeFolderId);
if (cached) {
scopeFolderName = cached;
return;
}
// Cold deep-link — fire once, populate on resolve. If it fails
// (folder was deleted, caller lost Read), keep name null so the
// breadcrumb just falls back to a short UUID.
const id = scopeFolderId;
void getFolder(id)
.then((f) => {
if (scopeFolderId === id) scopeFolderName = f.name;
})
.catch(() => {
if (scopeFolderId === id) scopeFolderName = id.slice(0, 8);
});
});
// Rendered as `<h1 class="page-title">` inside ResourceList. Bakes the
// query time / result count into the title string because ResourceList
// doesn't (yet) expose a subtitle slot, and lifting the "Xms" affordance
@@ -709,6 +740,41 @@
</button>
{/if}
{/snippet}
{#snippet breadcrumb()}
<!--
Only render when the search is folder-scoped AND the URL
param is present — the sticky "Home > Photos" cue answers
the "which directory was this search done in?" question
Ed raised 2026-07-26. Hidden for scope='all' (searching
everywhere → no folder to breadcrumb) and for a fresh
`/search?q=…` with no `in=` param.
Single-segment for now (Home icon + scope folder as a
link). Full parent-chain walk is a follow-up; it needs
stepping through `parent_id` via `getFolder`, which
would be a second pass here.
-->
{#if scope === 'folder' && scopeFolderId}
<nav class="breadcrumb" aria-label={t('breadcrumb.aria', 'Breadcrumb')}>
<a
href={resolve('/files')}
class="breadcrumb-item breadcrumb-home breadcrumb-link"
title={t('breadcrumb.home', 'Home')}
data-testid="search-breadcrumb-home-link"
>
<Icon name="home" />
</a>
<span class="breadcrumb-separator">&gt;</span>
<a
href={resolve(`/files/${scopeFolderId}`)}
class="breadcrumb-item breadcrumb-current breadcrumb-link"
data-testid="search-breadcrumb-folder-link"
>
{scopeFolderName ?? '…'}
</a>
</nav>
{/if}
{/snippet}
{#snippet itemActions(item)}
<!--
Per-row "Open parent folder" quick-action — search results