Add collapsible sidebar panels, vault quick-select dropdown, help modal, and vault focus feature with improved navigation UX

This commit is contained in:
2026-03-21 23:09:32 -04:00
parent b73aa19c51
commit a42f61e59a
3 changed files with 321 additions and 17 deletions
+138 -8
View File
@@ -18,6 +18,10 @@
let editorVault = null;
let editorPath = null;
let fallbackEditorEl = null;
const panelState = {
vault: true,
tag: true,
};
// ---------------------------------------------------------------------------
// File extension → Lucide icon mapping
@@ -148,6 +152,14 @@
});
}
function closeHeaderMenu() {
const menuBtn = document.getElementById("header-menu-btn");
const menuDropdown = document.getElementById("header-menu-dropdown");
if (!menuBtn || !menuDropdown) return;
menuBtn.classList.remove("active");
menuDropdown.classList.remove("active");
}
// ---------------------------------------------------------------------------
// API helpers
// ---------------------------------------------------------------------------
@@ -188,14 +200,36 @@
// ---------------------------------------------------------------------------
function initVaultContext() {
const filter = document.getElementById("vault-filter");
const quickSelect = document.getElementById("vault-quick-select");
if (!filter || !quickSelect) return;
filter.addEventListener("change", async () => {
selectedContextVault = filter.value;
showingSource = false;
cachedRawSource = null;
await refreshSidebarForContext();
await refreshTagsForContext();
showWelcome();
await setSelectedVaultContext(filter.value, { focusVault: filter.value !== "all" });
});
quickSelect.addEventListener("change", async () => {
await setSelectedVaultContext(quickSelect.value, { focusVault: quickSelect.value !== "all" });
});
}
async function setSelectedVaultContext(vaultName, options) {
selectedContextVault = vaultName;
showingSource = false;
cachedRawSource = null;
syncVaultSelectors();
await refreshSidebarForContext();
await refreshTagsForContext();
showWelcome();
if (options && options.focusVault && vaultName !== "all") {
await focusVaultInSidebar(vaultName);
}
}
function syncVaultSelectors() {
const filter = document.getElementById("vault-filter");
const quickSelect = document.getElementById("vault-quick-select");
if (filter) filter.value = selectedContextVault;
if (quickSelect) quickSelect.value = selectedContextVault;
}
async function refreshSidebarForContext() {
@@ -223,6 +257,19 @@
safeCreateIcons();
}
async function focusVaultInSidebar(vaultName) {
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 childContainer = document.getElementById(`vault-children-${vaultName}`);
if (childContainer && childContainer.classList.contains("collapsed")) {
await toggleVault(vaultItem, vaultName, true);
}
vaultItem.scrollIntoView({ block: "nearest" });
}
async function refreshTagsForContext() {
const vaultParam = selectedContextVault === "all" ? "" : `?vault=${encodeURIComponent(selectedContextVault)}`;
const data = await api(`/api/tags${vaultParam}`);
@@ -237,7 +284,10 @@
allVaults = vaults;
const container = document.getElementById("vault-tree");
const filter = document.getElementById("vault-filter");
const quickSelect = document.getElementById("vault-quick-select");
container.innerHTML = "";
filter.innerHTML = '<option value="all">Tous les vaults</option>';
quickSelect.innerHTML = '<option value="all">Tous les vaults</option>';
vaults.forEach((v) => {
// Sidebar tree entry
@@ -258,16 +308,24 @@
opt.value = v.name;
opt.textContent = v.name;
filter.appendChild(opt);
const quickOpt = document.createElement("option");
quickOpt.value = v.name;
quickOpt.textContent = v.name;
quickSelect.appendChild(quickOpt);
});
syncVaultSelectors();
safeCreateIcons();
}
async function toggleVault(itemEl, vaultName) {
async function toggleVault(itemEl, vaultName, forceExpand) {
const childContainer = document.getElementById(`vault-children-${vaultName}`);
if (!childContainer) return;
if (childContainer.classList.contains("collapsed")) {
const shouldExpand = forceExpand || childContainer.classList.contains("collapsed");
if (shouldExpand) {
// Expand — load children if empty
if (childContainer.children.length === 0) {
await loadDirectory(vaultName, "", childContainer);
@@ -668,6 +726,76 @@
area.scrollTop = 0;
}
// ---------------------------------------------------------------------------
// Collapsible panels and help modal
// ---------------------------------------------------------------------------
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 setPanelExpanded(panelKey, expanded) {
panelState[panelKey] = expanded;
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");
}
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);
}
safeCreateIcons();
}
function initHelpModal() {
const openBtn = document.getElementById("help-open-btn");
const closeBtn = document.getElementById("help-close");
const modal = document.getElementById("help-modal");
if (!openBtn || !closeBtn || !modal) return;
openBtn.addEventListener("click", () => {
modal.classList.add("active");
closeHeaderMenu();
safeCreateIcons();
});
closeBtn.addEventListener("click", closeHelpModal);
modal.addEventListener("click", (e) => {
if (e.target === modal) {
closeHelpModal();
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("active")) {
closeHelpModal();
}
});
}
function closeHelpModal() {
const modal = document.getElementById("help-modal");
if (modal) modal.classList.remove("active");
}
// ---------------------------------------------------------------------------
// Search
// ---------------------------------------------------------------------------
@@ -1112,6 +1240,8 @@
initSearch();
initMobile();
initVaultContext();
initCollapsiblePanels();
initHelpModal();
initSidebarFilter();
initSidebarResize();
initTagResize();