diff --git a/frontend/app.js b/frontend/app.js index 52b63b0..7c5dcbd 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -369,6 +369,61 @@ } } + async function focusPathInSidebar(vaultName, targetPath, options) { + setPanelExpanded("vault", true); + + const vaultItem = document.querySelector(`.vault-item[data-vault="${CSS.escape(vaultName)}"]`); + if (!vaultItem) return; + + document.querySelectorAll(".vault-item.focused").forEach((el) => el.classList.remove("focused")); + vaultItem.classList.add("focused"); + + const vaultContainer = document.getElementById(`vault-children-${vaultName}`); + if (!vaultContainer) return; + + if (vaultContainer.classList.contains("collapsed")) { + await toggleVault(vaultItem, vaultName, true); + } + + if (!targetPath) { + scrollTreeItemIntoView(vaultItem, options && options.alignToTop); + return; + } + + const segments = targetPath.split("/").filter(Boolean); + let currentContainer = vaultContainer; + let cumulativePath = ""; + + for (let index = 0; index < segments.length; index++) { + cumulativePath += (cumulativePath ? "/" : "") + segments[index]; + + let targetItem = null; + try { + targetItem = currentContainer.querySelector(`.tree-item[data-vault="${CSS.escape(vaultName)}"][data-path="${CSS.escape(cumulativePath)}"]`); + } catch (e) { + targetItem = null; + } + + if (!targetItem) { + return; + } + + const isLastSegment = index === segments.length - 1; + if (!isLastSegment) { + const nextContainer = document.getElementById(`dir-${vaultName}-${cumulativePath}`); + if (nextContainer && nextContainer.classList.contains("collapsed")) { + targetItem.click(); + await new Promise((resolve) => setTimeout(resolve, 0)); + } + if (nextContainer) { + currentContainer = nextContainer; + } + } + + scrollTreeItemIntoView(targetItem, Boolean(options && options.alignToTop && isLastSegment)); + } + } + async function loadDirectory(vaultName, dirPath, container) { const url = `/api/browse/${encodeURIComponent(vaultName)}?path=${encodeURIComponent(dirPath)}`; const data = await api(url); @@ -613,16 +668,22 @@ // Breadcrumb const parts = data.path.split("/"); const breadcrumbEls = []; - breadcrumbEls.push(makeBreadcrumbSpan(data.vault, () => {})); + breadcrumbEls.push(makeBreadcrumbSpan(data.vault, () => { + focusPathInSidebar(data.vault, "", { alignToTop: true }); + })); let accumulated = ""; parts.forEach((part, i) => { breadcrumbEls.push(el("span", { class: "sep" }, [document.createTextNode(" / ")])); accumulated += (accumulated ? "/" : "") + part; const p = accumulated; if (i < parts.length - 1) { - breadcrumbEls.push(makeBreadcrumbSpan(part, () => {})); + breadcrumbEls.push(makeBreadcrumbSpan(part, () => { + focusPathInSidebar(data.vault, p, { alignToTop: true }); + })); } else { - breadcrumbEls.push(el("span", {}, [document.createTextNode(part.replace(/\.md$/i, ""))])); + breadcrumbEls.push(makeBreadcrumbSpan(part.replace(/\.md$/i, ""), () => { + focusPathInSidebar(data.vault, data.path, { alignToTop: false }); + })); } }); diff --git a/frontend/style.css b/frontend/style.css index bd0eb6d..d84fd62 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -318,6 +318,18 @@ a:hover { flex: 1 1 auto; min-height: 0; max-height: none; + padding-top: 0; +} + +.sidebar.vault-collapsed .sidebar-tree { + flex: 0 0 auto; + min-height: 0; + padding-bottom: 0; +} + +.sidebar.vault-collapsed .tag-resize-handle { + border-top: none; + height: 0; } .sidebar.tag-collapsed .sidebar-tree { @@ -577,6 +589,10 @@ a:hover { display: none; } +.sidebar.vault-collapsed .tag-panel-toggle { + border-top: 1px solid var(--border); +} + /* --- Sidebar resize handle (horizontal) --- */ .sidebar-resize-handle { width: 5px;