feat: filtre de recherche dans la sidebar Historique IA (#98) + menus '@' et '/' depuis le menu '+' (BUG-048)
This commit is contained in:
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
Reference in New Issue
Block a user