diff --git a/frontend/app.js b/frontend/app.js index 9916e51..0734f78 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -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 = ''; + quickSelect.innerHTML = ''; 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(); diff --git a/frontend/index.html b/frontend/index.html index 6bc9927..a95b3c3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -102,6 +102,14 @@ Clair +
+ @@ -122,16 +130,32 @@ObsiGate vous permet d'explorer vos notes, de naviguer rapidement entre les vaults et d'utiliser les tags pour filtrer ou retrouver vos contenus.
+ +L'application est organisée autour de trois zones principales : l'en-tête avec la recherche et les options, la barre latérale avec les vaults et les tags, et la zone centrale pour consulter, rechercher et modifier les fichiers.
+ +La section Vaults regroupe les espaces suivants : Recettes, IT, Main, Workout, Sessions et Bruno. Vous pouvez ouvrir un vault pour explorer son arborescence, puis sélectionner un fichier pour l'afficher.
+La section Tags affiche les étiquettes disponibles comme #serveur, #TDM, #local, #linux, #debian, #docker, #None, #documentation, #network, #fiche-equipement, #fiche-infra, #fiche-reseau, #ssh et #bruno. Cliquez sur un tag pour lancer un filtrage rapide.
Le menu déroulant en haut de l'arborescence vous permet de vous focaliser instantanément sur un vault précis ou de revenir à l'affichage global avec Tous les vaults.
Les sections Vaults et Tags peuvent être réduites ou développées depuis leur en-tête pour gagner de l'espace dans la barre latérale.
+ +Ouvrez un fichier puis utilisez l'action d'édition pour modifier son contenu. Le mode source et la sauvegarde sont accessibles depuis la zone de lecture.
+Utilisez la barre de recherche en haut pour retrouver des notes par texte. Vous pouvez combiner la recherche avec un vault sélectionné et un ou plusieurs tags.
+Dépliez les dossiers et vaults dans la barre latérale, ou utilisez directement la sélection rapide pour aller au bon contexte plus vite.
+ +Le thème clair/sombre est disponible dans le menu Options. Vous pouvez aussi redimensionner la barre latérale et la zone des tags sur desktop pour adapter l'affichage à votre usage.
+