feat: filtre de recherche dans la sidebar Historique IA (#98) + menus '@' et '/' depuis le menu '+' (BUG-048)
CI / lint (push) Successful in 1m28s
CI / security (push) Successful in 58s
CI / test (push) Successful in 2m19s
CI / build (push) Successful in 54s
CI / e2e (push) Successful in 10m44s

This commit is contained in:
2026-09-16 16:11:54 -04:00
parent c9e3240ae2
commit 8e2b6203a1
19 changed files with 376 additions and 18 deletions
+5 -1
View File
@@ -1433,7 +1433,11 @@ class BooksLM {
${entry.disabled ? '<span class="bookslm-ext-soon">' + t('bookslm.soon') + '</span>' : ''}
<span class="bookslm-ext-hint">${entry.hint}</span>
`;
item.addEventListener('click', () => {
item.addEventListener('click', (e) => {
// BUG-048 — Keep the event from reaching the panel's click handler,
// which would hide a mention/command menu opened by this action right
// after it opens (the menu is rendered asynchronously).
e.stopPropagation();
if (!entry.disabled && entry.action) entry.action();
});
menu.appendChild(item);
+40 -3
View File
@@ -11,6 +11,8 @@ import { getModelCapabilities, renderCapabilityList, refreshAIPickers } from './
let _recentTimestampTimer = null;
let _recentFilesCache = [];
let _recentRefreshTimer = null;
let _aiSessionsCache = [];
let _aiQuery = "";
export async function loadRecentFiles(vaultFilter) {
const listEl = document.getElementById("recent-list");
@@ -160,6 +162,34 @@ function _formatAIDate(ts) {
}
}
/** Accent- and case-insensitive normalization for the AI history filter (#98). */
function _aiNorm(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
}
/** Apply the current sidebar query to a session list, resolving the mode label. */
function _applyAIFilter(sessions) {
const q = _aiNorm(_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);
});
}
/** Filter the sidebar AI history list by the sidebar filter input (#98). */
export function filterAIHistory(query) {
_aiQuery = (query || "").trim();
renderAIHistoryList(_applyAIFilter(_aiSessionsCache));
}
export async function loadAISessionList() {
const listEl = document.getElementById("ai-history-list");
const emptyEl = document.getElementById("ai-history-empty");
@@ -171,7 +201,8 @@ export async function loadAISessionList() {
} catch (err) {
console.error("Failed to load AI history:", err);
}
renderAIHistoryList(sessions);
_aiSessionsCache = sessions;
renderAIHistoryList(_applyAIFilter(_aiSessionsCache));
}
function renderAIHistoryList(sessions) {
@@ -181,6 +212,9 @@ function renderAIHistoryList(sessions) {
listEl.innerHTML = "";
if (!sessions || sessions.length === 0) {
if (_aiSessionsCache.length && _aiQuery) {
listEl.appendChild(el("div", { class: "sidebar-filter-empty" }, [document.createTextNode(t("bookslm.history_no_match"))]));
}
if (emptyEl) {
emptyEl.classList.remove("hidden");
safeCreateIcons();
@@ -240,13 +274,14 @@ function switchSidebarTab(tab) {
});
const filterInput = document.getElementById("sidebar-filter-input");
if (filterInput) {
const placeholders = { vaults: "Filtrer fichiers...", tags: "Filtrer tags...", recent: "", ai: "" };
const placeholders = { vaults: "Filtrer fichiers...", tags: "Filtrer tags...", recent: "", 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 === "ai") filterAIHistory(query);
}
// Auto-load recent files when switching to the recent tab
if (tab === "recent") {
@@ -254,8 +289,10 @@ function switchSidebarTab(tab) {
const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
}
// #96 — Auto-load the AI conversation history when entering its tab.
// #96/#98 — Auto-load the AI conversation history when entering its tab,
// re-applying the current sidebar filter query.
if (tab === "ai") {
if (filterInput) _aiQuery = filterInput.value.trim();
loadAISessionList();
}
}
+9 -1
View File
@@ -2,7 +2,7 @@ import { state } from './state.js';
import { safeCreateIcons, getFileIcon, flushIcons } from './utils.js';
import { api } from './auth.js';
import { populateCustomDropdown, TabManager, closeMobileSidebar, ContextMenuManager } from './ui.js';
import { _populateRecentVaultFilter, switchSidebarTab } from './config.js';
import { _populateRecentVaultFilter, switchSidebarTab, filterAIHistory } from './config.js';
import { el, icon, getVaultIcon, smallBadge, attachTreeItemActionButton, attachTreeItemLongPress, showWelcome, appendHighlightedText } from './viewer.js';
import { performAdvancedSearch } from './search.js';
import { t } from './i18n.js';
@@ -709,12 +709,16 @@ function initSidebarFilter() {
if (hasText) {
if (state.activeSidebarTab === "vaults") {
await performTreeSearch(q);
} else if (state.activeSidebarTab === "ai") {
filterAIHistory(q);
} else {
filterTagCloud(q);
}
} else {
if (state.activeSidebarTab === "vaults") {
await restoreSidebarTree();
} else if (state.activeSidebarTab === "ai") {
filterAIHistory("");
} else {
filterTagCloud("");
}
@@ -729,6 +733,8 @@ function initSidebarFilter() {
if (input.value.trim()) {
if (state.activeSidebarTab === "vaults") {
await performTreeSearch(q);
} else if (state.activeSidebarTab === "ai") {
filterAIHistory(q);
} else {
filterTagCloud(q);
}
@@ -743,6 +749,8 @@ function initSidebarFilter() {
clearTimeout(state.filterDebounce);
if (state.activeSidebarTab === "vaults") {
await restoreSidebarTree();
} else if (state.activeSidebarTab === "ai") {
filterAIHistory("");
} else {
filterTagCloud("");
}
+2
View File
@@ -1544,6 +1544,7 @@
"sidebar.expand_collapse": "Expand/Collapse",
"sidebar.file_icons": "Icons",
"sidebar.filter_instant": "Instant search",
"sidebar.filter_ai": "Filter AI history...",
"sidebar.filter_path": "Path filters",
"sidebar.filter_placeholder": "Filter files...",
"sidebar.filter_results_grouped": "Grouped results",
@@ -1826,6 +1827,7 @@
"bookslm.session_history": "Session history",
"bookslm.session_delete": "Delete session",
"bookslm.history_empty": "No saved session",
"bookslm.history_no_match": "No conversation matches the search",
"bookslm.untitled": "Untitled conversation",
"bookslm.no_vault": "No active vault to open this link",
"bookslm.copied_name": "Name copied: {name}",
+2
View File
@@ -1544,6 +1544,7 @@
"sidebar.expand_collapse": "Développer/Réduire",
"sidebar.file_icons": "Icônes",
"sidebar.filter_instant": "Recherche instantanée",
"sidebar.filter_ai": "Filtrer l'historique IA...",
"sidebar.filter_path": "Filtres de chemin",
"sidebar.filter_placeholder": "Filtrer fichiers...",
"sidebar.filter_results_grouped": "Résultats groupés",
@@ -1826,6 +1827,7 @@
"bookslm.session_history": "Historique des sessions",
"bookslm.session_delete": "Supprimer la session",
"bookslm.history_empty": "Aucune session enregistrée",
"bookslm.history_no_match": "Aucune conversation ne correspond à la recherche",
"bookslm.untitled": "Conversation sans titre",
"bookslm.no_vault": "Aucun vault actif pour ouvrir ce lien",
"bookslm.copied_name": "Nom copié : {name}",