Replace collapsible sidebar panels with tab-based navigation for vaults and tags with debounced filtering and dynamic placeholder text

This commit is contained in:
2026-03-22 22:42:35 -04:00
parent 6a782750de
commit 0d60dd8acc
3 changed files with 162 additions and 250 deletions
+65 -66
View File
@@ -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 {