feat(bookslm): bouton toggle cacher/afficher la sidebar dans le header
CI / lint (push) Successful in 37s
CI / security (push) Successful in 24s
CI / test (push) Successful in 49s
CI / build (push) Successful in 22s
CI / e2e (push) Successful in 6m16s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

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
This commit is contained in:
2026-09-07 13:59:00 -04:00
parent 0d29dd00fd
commit 5350738c22
4 changed files with 61 additions and 0 deletions
+46
View File
@@ -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 = `
<div class="bookslm-header">
<button class="bookslm-btn-toggle" title="${t('bookslm.toggle_sidebar')}" aria-label="${t('bookslm.toggle_sidebar')}">
<i data-lucide="panel-right-close" style="width:16px;height:16px"></i>
</button>
<span>📚</span>
<span class="bookslm-title"></span>
<span class="bookslm-picker-host" style="margin-left:auto"></span>
@@ -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);
+1
View File
@@ -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",
+1
View File
@@ -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",
+13
View File
@@ -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; }