diff --git a/frontend/app.js b/frontend/app.js index a612617..93ea584 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -22,10 +22,8 @@ let sidebarFilterCaseSensitive = false; let searchCaseSensitive = false; let _iconDebounceTimer = null; - const panelState = { - vault: true, - tag: true, - }; + let activeSidebarTab = "vaults"; + let filterDebounce = null; // --------------------------------------------------------------------------- // File extension → Lucide icon mapping @@ -435,7 +433,7 @@ function scrollTreeItemIntoView(element, alignToTop) { if (!element) return; - const scrollContainer = document.getElementById("vault-panel-content"); + const scrollContainer = document.getElementById("sidebar-panel-vaults"); if (!scrollContainer) return; const containerRect = scrollContainer.getBoundingClientRect(); @@ -483,7 +481,7 @@ } async function focusVaultInSidebar(vaultName) { - setPanelExpanded("vault", true); + switchSidebarTab("vaults"); 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")); @@ -567,7 +565,7 @@ } async function focusPathInSidebar(vaultName, targetPath, options) { - setPanelExpanded("vault", true); + switchSidebarTab("vaults"); const vaultItem = document.querySelector(`.vault-item[data-vault="${CSS.escape(vaultName)}"]`); if (!vaultItem) return; @@ -701,46 +699,59 @@ // --------------------------------------------------------------------------- // Sidebar filter // --------------------------------------------------------------------------- - async function initSidebarFilter() { + function initSidebarFilter() { const input = document.getElementById("sidebar-filter-input"); const caseBtn = document.getElementById("sidebar-filter-case-btn"); const clearBtn = document.getElementById("sidebar-filter-clear-btn"); - - input.addEventListener("input", async () => { + + input.addEventListener("input", () => { const hasText = input.value.length > 0; clearBtn.style.display = hasText ? "flex" : "none"; - - const q = sidebarFilterCaseSensitive ? input.value.trim() : input.value.trim().toLowerCase(); - - if (hasText) { - await performTreeSearch(q); - } else { - await restoreSidebarTree(); - } - - filterTagCloud(q); + clearTimeout(filterDebounce); + filterDebounce = setTimeout(async () => { + const q = sidebarFilterCaseSensitive ? input.value.trim() : input.value.trim().toLowerCase(); + if (hasText) { + if (activeSidebarTab === "vaults") { + await performTreeSearch(q); + } else { + filterTagCloud(q); + } + } else { + if (activeSidebarTab === "vaults") { + await restoreSidebarTree(); + } else { + filterTagCloud(""); + } + } + }, 220); }); - + caseBtn.addEventListener("click", async () => { sidebarFilterCaseSensitive = !sidebarFilterCaseSensitive; caseBtn.classList.toggle("active"); const q = sidebarFilterCaseSensitive ? input.value.trim() : input.value.trim().toLowerCase(); if (input.value.trim()) { - await performTreeSearch(q); + if (activeSidebarTab === "vaults") { + await performTreeSearch(q); + } else { + filterTagCloud(q); + } } - filterTagCloud(q); }); - - clearBtn.addEventListener("click", () => { + + clearBtn.addEventListener("click", async () => { input.value = ""; clearBtn.style.display = "none"; sidebarFilterCaseSensitive = false; caseBtn.classList.remove("active"); - restoreSidebarTree(); - filterTagCloud(""); + clearTimeout(filterDebounce); + if (activeSidebarTab === "vaults") { + await restoreSidebarTree(); + } else { + filterTagCloud(""); + } }); - - // Initially hide clear button + clearBtn.style.display = "none"; } @@ -1284,47 +1295,36 @@ } // --------------------------------------------------------------------------- - // Collapsible panels and help modal + // Sidebar tabs // --------------------------------------------------------------------------- - function initCollapsiblePanels() { - bindPanelToggle("vault", "vault-panel-toggle", "vault-panel-content"); - bindPanelToggle("tag", "tag-panel-toggle", "tag-panel-content"); - setPanelExpanded("vault", true); - setPanelExpanded("tag", true); - } - - function bindPanelToggle(panelKey, toggleId, contentId) { - const toggle = document.getElementById(toggleId); - const content = document.getElementById(contentId); - if (!toggle || !content) return; - toggle.addEventListener("click", () => { - setPanelExpanded(panelKey, !panelState[panelKey]); + function initSidebarTabs() { + document.querySelectorAll(".sidebar-tab").forEach((tab) => { + tab.addEventListener("click", () => switchSidebarTab(tab.dataset.tab)); }); } - function setPanelExpanded(panelKey, expanded) { - panelState[panelKey] = expanded; - const sidebar = document.getElementById("sidebar"); - const toggle = document.getElementById(`${panelKey}-panel-toggle`); - const content = document.getElementById(`${panelKey}-panel-content`); - if (!toggle || !content) return; - toggle.setAttribute("aria-expanded", expanded ? "true" : "false"); - content.classList.toggle("collapsed", !expanded); - const iconEl = toggle.querySelector("[data-lucide]"); - if (iconEl) { - iconEl.setAttribute("data-lucide", expanded ? "chevron-down" : "chevron-right"); + function switchSidebarTab(tab) { + activeSidebarTab = tab; + document.querySelectorAll(".sidebar-tab").forEach((btn) => { + const isActive = btn.dataset.tab === tab; + btn.classList.toggle("active", isActive); + btn.setAttribute("aria-selected", isActive ? "true" : "false"); + }); + document.querySelectorAll(".sidebar-tab-panel").forEach((panel) => { + const isActive = panel.id === `sidebar-panel-${tab}`; + panel.classList.toggle("active", isActive); + }); + const filterInput = document.getElementById("sidebar-filter-input"); + if (filterInput) { + filterInput.placeholder = tab === "vaults" ? "Filtrer fichiers..." : "Filtrer tags..."; } - if (panelKey === "tag") { - const tagSection = document.getElementById("tag-cloud-section"); - const resizeHandle = document.getElementById("tag-resize-handle"); - if (tagSection) tagSection.classList.toggle("collapsed-panel", !expanded); - if (resizeHandle) resizeHandle.classList.toggle("hidden", !expanded); + const query = filterInput + ? (sidebarFilterCaseSensitive ? filterInput.value.trim() : filterInput.value.trim().toLowerCase()) + : ""; + if (query) { + if (tab === "vaults") performTreeSearch(query); + else filterTagCloud(query); } - if (sidebar) { - sidebar.classList.toggle("vault-collapsed", !panelState.vault); - sidebar.classList.toggle("tag-collapsed", !panelState.tag); - } - safeCreateIcons(); } function initHelpModal() { @@ -2017,12 +2017,11 @@ initSearch(); initMobile(); initVaultContext(); - initCollapsiblePanels(); + initSidebarTabs(); initHelpModal(); initConfigModal(); initSidebarFilter(); initSidebarResize(); - initTagResize(); initEditor(); try { diff --git a/frontend/index.html b/frontend/index.html index 2463094..02c5201 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -158,23 +158,39 @@ diff --git a/frontend/style.css b/frontend/style.css index 3ef91d9..e9ae21c 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -548,29 +548,6 @@ select { flex-shrink: 0; } -.sidebar.vault-collapsed .tag-cloud-section { - flex: 1 1 auto; - min-height: 0; - max-height: none; - height: auto; - 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 { - flex: 1 1 auto; - min-height: 0; -} /* --- Sidebar filter --- */ .sidebar-filter { @@ -658,82 +635,77 @@ select { color: var(--accent); } -.sidebar-tree { - flex: 1; - overflow: hidden; - padding: 12px 0; - min-height: 80px; +/* --- Sidebar tabs --- */ +.sidebar-tabs { display: flex; - flex-direction: column; + flex-shrink: 0; + border-bottom: 1px solid var(--border); + padding: 0 8px; + gap: 2px; + background: var(--bg-sidebar); } -.sidebar-tree::-webkit-scrollbar { - width: 6px; -} -.sidebar-tree::-webkit-scrollbar-thumb { - background: var(--scrollbar); - border-radius: 3px; -} - -.sidebar-section-title { +.sidebar-tab { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + padding: 9px 8px 8px; + border: none; + border-bottom: 2px solid transparent; + background: transparent; + color: var(--text-muted); font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--text-muted); - padding: 0; -} - -.sidebar-panel-toggle { - width: 100%; - border: none; - background: transparent; - color: var(--text-muted); - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 8px 16px 10px; + letter-spacing: 0.07em; cursor: pointer; - transition: color 150ms ease, background 150ms ease; + transition: color 150ms ease, border-color 150ms ease; + margin-bottom: -1px; + white-space: nowrap; } -.sidebar-panel-toggle:hover { +.sidebar-tab:hover { color: var(--text-primary); - background: var(--bg-hover); } -.sidebar-panel-toggle .icon, -.sidebar-panel-toggle [data-lucide] { - color: inherit; +.sidebar-tab.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.sidebar-tab .icon { flex-shrink: 0; + color: inherit; } -.sidebar-panel-content.collapsed { - max-height: 0 !important; - opacity: 0; - overflow: hidden; - pointer-events: none; -} - -.sidebar-panel-content { - min-height: 0; - overflow: hidden; - opacity: 1; - transition: max-height 340ms ease, opacity 220ms ease; -} - -#vault-panel-content { +/* --- Sidebar tab panels --- */ +.sidebar-tab-panel { + display: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; - max-height: 1000px; scroll-behavior: smooth; + flex-direction: column; } -#tag-panel-content { - max-height: 320px; +.sidebar-tab-panel.active { + display: flex; + flex-direction: column; +} + +.sidebar-tab-panel::-webkit-scrollbar { + width: 6px; +} +.sidebar-tab-panel::-webkit-scrollbar-thumb { + background: var(--scrollbar); + border-radius: 3px; +} + +#sidebar-panel-vaults .sidebar-dropdown { + padding: 10px 12px 6px; } .tree-item { @@ -817,31 +789,32 @@ select { .filter-result-item { align-items: flex-start; - gap: 8px; - border-radius: 8px; + white-space: normal; + overflow: visible; + border-radius: 6px; } .filter-result-text { + flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; -} - -.filter-result-primary, -.filter-result-secondary { - min-width: 0; - overflow-wrap: anywhere; - word-break: break-word; + white-space: normal; } .filter-result-primary { color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .filter-result-secondary { color: var(--text-muted); - font-size: 0.78rem; + font-size: 0.75rem; + word-break: break-all; + opacity: 0.8; } .sidebar-filter-empty { @@ -857,68 +830,13 @@ select { border-radius: 4px; } -/* --- Tag resize handle --- */ -.tag-resize-handle { - height: 5px; - cursor: ns-resize; - background: transparent; - border-top: 1px solid var(--border); - flex-shrink: 0; - transition: background 150ms ease; -} -.tag-resize-handle:hover, -.tag-resize-handle.active { - background: var(--accent); - opacity: 0.5; -} - /* --- Tag Cloud --- */ -.tag-cloud-section { - padding: 12px 0 0; - height: 180px; - min-height: 60px; - max-height: 400px; - overflow: hidden; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: height 340ms ease, min-height 340ms ease, max-height 340ms ease, flex 340ms ease; -} -.tag-cloud-section.collapsed-panel { - height: auto; - min-height: 0; - max-height: none; - overflow: hidden; - flex: 0 0 auto; -} -.tag-cloud-section::-webkit-scrollbar { - width: 6px; -} -.tag-cloud-section::-webkit-scrollbar-thumb { - background: var(--scrollbar); - border-radius: 3px; -} - -.tag-cloud-title { - font-family: 'JetBrains Mono', monospace; - font-size: 0.7rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--text-muted); - margin-bottom: 0; -} - .tag-cloud { display: flex; flex-wrap: wrap; gap: 6px; - padding: 0 16px 12px; -} - -.tag-cloud-section:not(.collapsed-panel) #tag-panel-content { - overflow-y: auto; - flex: 1 1 auto; + padding: 14px 16px 16px; + align-content: flex-start; } .tag-item { @@ -939,14 +857,6 @@ select { display: none; } -.tag-resize-handle.hidden { - display: none; -} - -.sidebar.vault-collapsed .tag-panel-toggle { - border-top: 1px solid var(--border); -} - /* --- Sidebar resize handle (horizontal) --- */ .sidebar-resize-handle { width: 5px;