feat: split view multi-panneaux (#75) — PaneManager intégré avec split right/down, menu contextuel, raccourcis clavier, CSS responsive et tests
CI / lint (push) Failing after 35s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 15s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled

- PaneManager: splitRight/splitDown avec 1-4 panneaux, persistance localStorage, resize
- Viewer/TabManager pane-aware: fonctions getContentArea/getTabBar/getTabList
- Menu contextuel onglets: actions Diviser à droite / Diviser en bas / Fermer le panneau
- Raccourcis clavier: Ctrl+Alt+\ (split right), Ctrl+Alt+Shift+\ (split down), Ctrl+Alt+W (fermer panneau)
- CSS pane-grid: grille, poignées resize, bordure active pane, responsive (désactivé <768px)
- Tests: 22 nouveaux tests unitaires + régression (307 total, 100% green)
- Déployé sur Docker test (localhost:2021)
This commit is contained in:
2026-07-27 21:06:47 -04:00
parent 0cea78b3b1
commit b27b38d32b
5 changed files with 555 additions and 57 deletions
+69 -7
View File
@@ -1877,9 +1877,64 @@ export const TabManager = {
_tabList: null,
_dirtyTabs: new Set(),
// Expose on window for PaneManager cross-module communication
_expose() {
window.TabManager = this;
},
// Pane-aware helpers
_getContentArea() {
if (window.PaneManager && window.PaneManager.isSplit()) {
return window.PaneManager.getActiveContentArea() || document.getElementById('content-area');
}
return document.getElementById('content-area');
},
_getTabBar() {
if (window.PaneManager && window.PaneManager.isSplit()) {
return window.PaneManager.getActiveTabBar() || document.getElementById('tab-bar');
}
return document.getElementById('tab-bar');
},
_getTabList() {
if (window.PaneManager && window.PaneManager.isSplit()) {
return window.PaneManager.getActiveTabList() || document.getElementById('tab-list');
}
return document.getElementById('tab-list');
},
init() {
this._tabBar = document.getElementById("tab-bar");
this._tabList = document.getElementById("tab-list");
this._tabBar = this._getTabBar();
this._tabList = this._getTabList();
this._expose();
},
/** Called by PaneManager when pane focus changes — updates DOM references */
activatePaneTab(paneId) {
// Update DOM references to the newly active pane
this._tabBar = this._getTabBar();
this._tabList = this._getTabList();
// If this pane has a saved active tab, activate it
const pane = window.PaneManager && window.PaneManager.panes
? window.PaneManager.panes.find(p => p.id === paneId)
: null;
if (pane && pane.activeTab && pane.activeTab !== this._activeTabId) {
this.activate(pane.activeTab);
}
if (!pane || !pane.activeTab) {
// New pane with no tab — show dashboard
this._showDashboard();
}
},
/** Track which pane the active tab belongs to */
_updatePaneActiveTab() {
if (window.PaneManager && window.PaneManager.isSplit() && this._activeTabId) {
const pane = window.PaneManager.panes.find(p => p.id === window.PaneManager.activePaneId);
if (pane) {
pane.activeTab = this._activeTabId;
window.PaneManager._persist();
}
}
},
/** Open a file as a preview tab (single-click).
@@ -1986,7 +2041,7 @@ export const TabManager = {
this._activeTabId = tabId;
this._renderTabs();
this._updatePaneActiveTab();
// Load tab content
const cache = this._tabCache[tabId];
if (!cache) return;
@@ -1996,7 +2051,7 @@ export const TabManager = {
state.currentPath = cache.path;
syncActiveFileTreeItem(cache.vault, cache.path);
const area = document.getElementById("content-area");
const area = this._getContentArea();
if (cache.data) {
// Use cached data
@@ -2108,7 +2163,7 @@ export const TabManager = {
const cache = this._tabCache[this._activeTabId];
if (!cache) return;
const area = document.getElementById("content-area");
const area = this._getContentArea();
const rendered = document.getElementById("file-rendered-content");
cache.scrollTop = area.scrollTop;
@@ -2153,7 +2208,7 @@ export const TabManager = {
},
_showDashboard() {
const area = document.getElementById("content-area");
const area = this._getContentArea();
// Save dashboard DOM before clearing (it may have been removed from DOM by renderFile)
let dashboard = document.getElementById("dashboard-home");
if (!dashboard) {
@@ -2294,18 +2349,25 @@ export const TabManager = {
menu.style.left = x + "px";
menu.style.top = y + "px";
menu.innerHTML = `
<div class="context-menu-item" data-action="splitRight"><i data-lucide="layout-panel-right" class="icon"></i><span>Diviser à droite</span></div>
<div class="context-menu-item" data-action="splitDown"><i data-lucide="layout-panel-bottom" class="icon"></i><span>Diviser en bas</span></div>
<div class="context-menu-separator"></div>
<div class="context-menu-item" data-action="close"><i data-lucide="x" class="icon"></i><span>Fermer</span></div>
<div class="context-menu-item" data-action="closeOthers"><i data-lucide="x-circle" class="icon"></i><span>Fermer les autres</span></div>
<div class="context-menu-item" data-action="closeRight"><i data-lucide="arrow-right-circle" class="icon"></i><span>Fermer à droite</span></div>
<div class="context-menu-separator"></div>
<div class="context-menu-item" data-action="closeAll"><i data-lucide="trash-2" class="icon"></i><span>Fermer tout</span></div>
${window.PaneManager && window.PaneManager.isSplit() ? '<div class="context-menu-separator"></div><div class="context-menu-item" data-action="closePane"><i data-lucide="panel-right-close" class="icon"></i><span>Fermer le panneau</span></div>' : ''}
`;
document.body.appendChild(menu);
safeCreateIcons();
menu.addEventListener("click", (e) => {
const action = e.target.closest(".context-menu-item")?.dataset.action;
if (action === "close") this.close(tabId);
if (action === "splitRight") { if (window.PaneManager) { window.PaneManager.splitRight(); menu.remove(); return; } }
else if (action === "splitDown") { if (window.PaneManager) { window.PaneManager.splitDown(); menu.remove(); return; } }
else if (action === "closePane") { if (window.PaneManager) { window.PaneManager.closeActivePane(); menu.remove(); return; } }
else if (action === "close") this.close(tabId);
else if (action === "closeOthers") this.closeOthers(tabId);
else if (action === "closeRight") this.closeRight(tabId);
else if (action === "closeAll") this.closeAll();