From 5350738c2204e20f159075175a8d552c404b315f Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Mon, 7 Sep 2026 13:59:00 -0400 Subject: [PATCH] feat(bookslm): bouton toggle cacher/afficher la sidebar dans le header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Même mécanisme que le sidebar de navigation gauche : - Bouton .bookslm-btn-toggle dans le header (icône panel-right-close/open) - Toggle la classe .hidden sur le panel, persiste dans localStorage - État restauré au prochain open() - Le panel reste monté (pas de teardown), juste slid off-screen - CSS : .bookslm-panel.open.hidden → translateX(100%) - CSS : .bookslm-btn-toggle suit le pattern .sidebar-toggle-btn i18n : bookslm.toggle_sidebar ajouté (fr + en) Import safeCreateIcons ajouté pour rafraîchir l'icône Lucide au toggle. Vérifié : - pytest : 504 passed (zéro régression) - ruff : All checks passed - frontend unit : 7 passed - validate-imports : 30 modules / 204 exports - pane-manager JSDOM : 9/9 --- frontend/js/bookslm.js | 46 ++++++++++++++++++++++++++++++++++++++++ frontend/locales/en.json | 1 + frontend/locales/fr.json | 1 + frontend/style.css | 13 ++++++++++++ 4 files changed, 61 insertions(+) diff --git a/frontend/js/bookslm.js b/frontend/js/bookslm.js index 4039ede..d2ec5b3 100644 --- a/frontend/js/bookslm.js +++ b/frontend/js/bookslm.js @@ -1,6 +1,7 @@ // BooksLM — Directory-scoped AI chat panel (style NotebookLM) import { t } from './i18n.js'; import { buildAIPickerUI } from './ai.js'; +import { safeCreateIcons } from './utils.js'; class BooksLM { constructor() { @@ -43,6 +44,14 @@ class BooksLM { // Open with animation requestAnimationFrame(() => { this._panel.classList.add('open'); + // Apply persisted hidden state (user toggled off in a previous session) + try { + const savedHidden = localStorage.getItem('obsigate-bookslm-hidden'); + if (savedHidden === 'true') { + this._panel.classList.add('hidden'); + this._updateToggleIcon(); + } + } catch { /* */ } }); this._isOpen = true; @@ -77,6 +86,39 @@ class BooksLM { } } + /** + * Toggle the BooksLM sidebar visibility (collapsed/hidden to the right edge + * while the panel stays mounted). The state is persisted in localStorage so + * the user choice survives page reloads — same UX as the left sidebar toggle. + * + * The panel stays in the DOM (no teardown); only a CSS transform is applied, + * which is what makes this fast and matches the right-sidebar pattern in + * ObsiGate. + */ + _toggleSidebar() { + if (!this._panel) return; + const isHidden = this._panel.classList.toggle('hidden'); + try { + localStorage.setItem('obsigate-bookslm-hidden', isHidden ? 'true' : 'false'); + } catch { /* */ } + this._updateToggleIcon(); + } + + /** + * Swap the toggle icon between "panel-right-close" (visible) and + * "panel-right-open" (hidden) so the button visually reflects the state. + */ + _updateToggleIcon() { + if (!this._panel) return; + const btn = this._panel.querySelector('.bookslm-btn-toggle'); + if (!btn) return; + const isHidden = this._panel.classList.contains('hidden'); + const icon = btn.querySelector('i[data-lucide]'); + if (!icon) return; + icon.setAttribute('data-lucide', isHidden ? 'panel-right-open' : 'panel-right-close'); + if (typeof safeCreateIcons === 'function') safeCreateIcons(); + } + newConversation() { this._messages = []; this._saveHistory(); @@ -117,6 +159,9 @@ class BooksLM { panel.className = 'bookslm-panel'; panel.innerHTML = `
+ 📚 @@ -144,6 +189,7 @@ class BooksLM { panel.querySelector('.bookslm-btn-close').addEventListener('click', () => this.close()); panel.querySelector('.bookslm-btn-new').addEventListener('click', () => this.newConversation()); panel.querySelector('.bookslm-btn-export').addEventListener('click', () => this.exportConversation()); + panel.querySelector('.bookslm-btn-toggle').addEventListener('click', () => this._toggleSidebar()); panel.querySelector('.bookslm-btn-fullscreen').addEventListener('click', () => { this._isFullscreen = !this._isFullscreen; panel.classList.toggle('fullscreen', this._isFullscreen); diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 5d2e59e..ee5ac4a 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -1589,6 +1589,7 @@ "bookslm.send": "Send", "bookslm.new_conversation": "New conversation", "bookslm.export": "Export conversation", + "bookslm.toggle_sidebar": "Hide/Show AI sidebar", "bookslm.copy": "Copy", "bookslm.regenerate": "Regenerate", "bookslm.suggestion_summary": "Summarize this directory", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index d3371f5..84b7839 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -1589,6 +1589,7 @@ "bookslm.send": "Envoyer", "bookslm.new_conversation": "Nouvelle conversation", "bookslm.export": "Exporter la conversation", + "bookslm.toggle_sidebar": "Masquer/Afficher la sidebar AI", "bookslm.copy": "Copier", "bookslm.regenerate": "Régénérer", "bookslm.suggestion_summary": "Résume ce répertoire", diff --git a/frontend/style.css b/frontend/style.css index c308df7..18dcb1b 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -9021,6 +9021,19 @@ body.popup-mode .content-area { box-shadow: -4px 0 20px rgba(0,0,0,0.3); color: var(--text-primary); } .bookslm-panel.open { transform: translateX(0); } +/* The `.hidden` class is applied by the header toggle button to collapse the + panel while keeping it mounted (so the toggle icon stays usable). When + hidden, the panel is slid fully off-screen and the open animation no + longer overrides the translateX. */ +.bookslm-panel.open.hidden { transform: translateX(100%); } +.bookslm-btn-toggle { + display: flex; align-items: center; justify-content: center; + background: none; border: none; cursor: pointer; + color: var(--text-secondary); padding: 4px; + border-radius: 4px; flex-shrink: 0; + transition: color 200ms ease, background 200ms ease; +} +.bookslm-btn-toggle:hover { color: var(--accent); background: var(--surface2); } .bookslm-header { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 14px; font-weight: 600; }