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
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:
+69
-7
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user