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
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:
@@ -1,6 +1,7 @@
|
|||||||
// BooksLM — Directory-scoped AI chat panel (style NotebookLM)
|
// BooksLM — Directory-scoped AI chat panel (style NotebookLM)
|
||||||
import { t } from './i18n.js';
|
import { t } from './i18n.js';
|
||||||
import { buildAIPickerUI } from './ai.js';
|
import { buildAIPickerUI } from './ai.js';
|
||||||
|
import { safeCreateIcons } from './utils.js';
|
||||||
|
|
||||||
class BooksLM {
|
class BooksLM {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -43,6 +44,14 @@ class BooksLM {
|
|||||||
// Open with animation
|
// Open with animation
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this._panel.classList.add('open');
|
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;
|
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() {
|
newConversation() {
|
||||||
this._messages = [];
|
this._messages = [];
|
||||||
this._saveHistory();
|
this._saveHistory();
|
||||||
@@ -117,6 +159,9 @@ class BooksLM {
|
|||||||
panel.className = 'bookslm-panel';
|
panel.className = 'bookslm-panel';
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="bookslm-header">
|
<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>📚</span>
|
||||||
<span class="bookslm-title"></span>
|
<span class="bookslm-title"></span>
|
||||||
<span class="bookslm-picker-host" style="margin-left:auto"></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-close').addEventListener('click', () => this.close());
|
||||||
panel.querySelector('.bookslm-btn-new').addEventListener('click', () => this.newConversation());
|
panel.querySelector('.bookslm-btn-new').addEventListener('click', () => this.newConversation());
|
||||||
panel.querySelector('.bookslm-btn-export').addEventListener('click', () => this.exportConversation());
|
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', () => {
|
panel.querySelector('.bookslm-btn-fullscreen').addEventListener('click', () => {
|
||||||
this._isFullscreen = !this._isFullscreen;
|
this._isFullscreen = !this._isFullscreen;
|
||||||
panel.classList.toggle('fullscreen', this._isFullscreen);
|
panel.classList.toggle('fullscreen', this._isFullscreen);
|
||||||
|
|||||||
@@ -1589,6 +1589,7 @@
|
|||||||
"bookslm.send": "Send",
|
"bookslm.send": "Send",
|
||||||
"bookslm.new_conversation": "New conversation",
|
"bookslm.new_conversation": "New conversation",
|
||||||
"bookslm.export": "Export conversation",
|
"bookslm.export": "Export conversation",
|
||||||
|
"bookslm.toggle_sidebar": "Hide/Show AI sidebar",
|
||||||
"bookslm.copy": "Copy",
|
"bookslm.copy": "Copy",
|
||||||
"bookslm.regenerate": "Regenerate",
|
"bookslm.regenerate": "Regenerate",
|
||||||
"bookslm.suggestion_summary": "Summarize this directory",
|
"bookslm.suggestion_summary": "Summarize this directory",
|
||||||
|
|||||||
@@ -1589,6 +1589,7 @@
|
|||||||
"bookslm.send": "Envoyer",
|
"bookslm.send": "Envoyer",
|
||||||
"bookslm.new_conversation": "Nouvelle conversation",
|
"bookslm.new_conversation": "Nouvelle conversation",
|
||||||
"bookslm.export": "Exporter la conversation",
|
"bookslm.export": "Exporter la conversation",
|
||||||
|
"bookslm.toggle_sidebar": "Masquer/Afficher la sidebar AI",
|
||||||
"bookslm.copy": "Copier",
|
"bookslm.copy": "Copier",
|
||||||
"bookslm.regenerate": "Régénérer",
|
"bookslm.regenerate": "Régénérer",
|
||||||
"bookslm.suggestion_summary": "Résume ce répertoire",
|
"bookslm.suggestion_summary": "Résume ce répertoire",
|
||||||
|
|||||||
@@ -9021,6 +9021,19 @@ body.popup-mode .content-area {
|
|||||||
box-shadow: -4px 0 20px rgba(0,0,0,0.3);
|
box-shadow: -4px 0 20px rgba(0,0,0,0.3);
|
||||||
color: var(--text-primary); }
|
color: var(--text-primary); }
|
||||||
.bookslm-panel.open { transform: translateX(0); }
|
.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;
|
.bookslm-header { display: flex; align-items: center; gap: 8px;
|
||||||
padding: 12px 16px; border-bottom: 1px solid var(--border);
|
padding: 12px 16px; border-bottom: 1px solid var(--border);
|
||||||
font-size: 14px; font-weight: 600; }
|
font-size: 14px; font-weight: 600; }
|
||||||
|
|||||||
Reference in New Issue
Block a user