feat(sidebar+assistant): filtre Recents/Sauvegardes (#99), pastille Deep Research (#100) et icone du bouton + (BUG-049)
CI / lint (push) Failing after 1m20s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Successful in 59s

This commit is contained in:
2026-09-16 20:16:41 -04:00
parent 8f26a418a9
commit 4231f2e929
22 changed files with 491 additions and 80 deletions
+48 -11
View File
@@ -1,7 +1,7 @@
// config.js — extracted from app.js (3872-4865)
import { api, AuthManager, initMfaSettings } from './auth.js';
import { state } from './state.js';
import { el, icon, openFile } from './viewer.js';
import { el, icon, openFile, filterSavedSearches } from './viewer.js';
import { syncVaultSelectors, setSelectedVaultContext, refreshSidebarForContext, loadVaults, loadVaultSettings, loadTags, TagFilterService, refreshSidebarTreePreservingState } from './sidebar.js';
import { escapeHtml, safeCreateIcons } from './utils.js';
import { showToast, closeHeaderMenu, closeMobileSidebar } from './ui.js';
@@ -11,6 +11,7 @@ import { getModelCapabilities, renderCapabilityList, refreshAIPickers } from './
let _recentTimestampTimer = null;
let _recentFilesCache = [];
let _recentRefreshTimer = null;
let _recentQuery = "";
let _aiSessionsCache = [];
let _aiQuery = "";
@@ -24,7 +25,7 @@ export async function loadRecentFiles(vaultFilter) {
try {
const data = await api(url);
_recentFilesCache = data.files || [];
renderRecentList(_recentFilesCache);
renderRecentList(_applyRecentFilter(_recentFilesCache));
} catch (err) {
console.error("Failed to load recent files:", err);
listEl.innerHTML = "";
@@ -41,6 +42,9 @@ function renderRecentList(files) {
listEl.innerHTML = "";
if (!files || files.length === 0) {
if (_recentFilesCache.length && _recentQuery) {
listEl.appendChild(el("div", { class: "sidebar-filter-empty" }, [document.createTextNode(t("sidebar.no_results"))]));
}
if (emptyEl) {
emptyEl.classList.remove("hidden");
safeCreateIcons();
@@ -162,25 +166,45 @@ function _formatAIDate(ts) {
}
}
/** Accent- and case-insensitive normalization for the AI history filter (#98). */
function _aiNorm(value) {
/** Accent- and case-insensitive normalization for the sidebar filters (#98/#99). */
function _sidebarNorm(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
}
/** Apply the current sidebar query to the recent-files list (#99). */
function _applyRecentFilter(files) {
const q = _sidebarNorm(_recentQuery);
if (!q) return files;
return (files || []).filter((f) => {
const tags = Array.isArray(f.tags) ? f.tags.join(" ") : "";
return _sidebarNorm(f.title).includes(q)
|| _sidebarNorm(f.path).includes(q)
|| _sidebarNorm(f.vault).includes(q)
|| _sidebarNorm(f.preview).includes(q)
|| _sidebarNorm(tags).includes(q);
});
}
/** Filter the sidebar recent-files list by the sidebar filter input (#99). */
export function filterRecentFiles(query) {
_recentQuery = (query || "").trim();
renderRecentList(_applyRecentFilter(_recentFilesCache));
}
/** Apply the current sidebar query to a session list, resolving the mode label. */
function _applyAIFilter(sessions) {
const q = _aiNorm(_aiQuery);
const q = _sidebarNorm(_aiQuery);
if (!q) return sessions;
return (sessions || []).filter((s) => {
const modeLabel = t("bookslm.mode_" + (s.mode || "general"));
return _aiNorm(s.title).includes(q)
|| _aiNorm(s.preview).includes(q)
|| _aiNorm(s.directory).includes(q)
|| _aiNorm(s.context).includes(q)
|| _aiNorm(modeLabel).includes(q);
return _sidebarNorm(s.title).includes(q)
|| _sidebarNorm(s.preview).includes(q)
|| _sidebarNorm(s.directory).includes(q)
|| _sidebarNorm(s.context).includes(q)
|| _sidebarNorm(modeLabel).includes(q);
});
}
@@ -274,21 +298,34 @@ function switchSidebarTab(tab) {
});
const filterInput = document.getElementById("sidebar-filter-input");
if (filterInput) {
const placeholders = { vaults: "Filtrer fichiers...", tags: "Filtrer tags...", recent: "", ai: t("sidebar.filter_ai") };
const placeholders = {
vaults: "Filtrer fichiers...",
tags: "Filtrer tags...",
recent: t("sidebar.filter_recent"),
saved: t("sidebar.filter_saved"),
ai: t("sidebar.filter_ai"),
};
filterInput.placeholder = placeholders[tab] || "";
}
const query = filterInput ? (state.sidebarFilterCaseSensitive ? filterInput.value.trim() : filterInput.value.trim().toLowerCase()) : "";
if (query) {
if (tab === "vaults") performTreeSearch(query);
else if (tab === "tags") filterTagCloud(query);
else if (tab === "recent") filterRecentFiles(query);
else if (tab === "saved") filterSavedSearches(query);
else if (tab === "ai") filterAIHistory(query);
}
// Auto-load recent files when switching to the recent tab
if (tab === "recent") {
_populateRecentVaultFilter();
if (filterInput) filterRecentFiles(filterInput.value.trim());
const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
}
// #99 — Re-apply the current sidebar query on the saved-searches tab.
if (tab === "saved" && filterInput) {
filterSavedSearches(filterInput.value.trim());
}
// #96/#98 — Auto-load the AI conversation history when entering its tab,
// re-applying the current sidebar filter query.
if (tab === "ai") {