Refactor file opening to use TabManager and add dashboard tab system

This commit is contained in:
Bruno Charest 2026-05-26 12:14:47 -04:00
parent b38f3f16e4
commit 872a3e56dd
2 changed files with 80 additions and 90 deletions

View File

@ -542,7 +542,7 @@
li.appendChild(metaEl);
li.addEventListener("click", () => {
this.hide();
openFile(item.vault, item.path);
TabManager.openPreview(item.vault, item.path);
});
this._titlesList.appendChild(li);
});
@ -2642,10 +2642,15 @@
attachTreeItemLongPress(fileItem, () => ({ vault: vaultName, path: item.path, type: "file", isReadonly: false }));
fileItem.addEventListener("click", () => {
scrollTreeItemIntoView(fileItem, false);
openFile(vaultName, item.path);
TabManager.openPreview(vaultName, item.path);
closeMobileSidebar();
});
fileItem.addEventListener("dblclick", (e) => {
e.preventDefault();
TabManager.openPersistent(vaultName, item.path);
});
fileItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
const isReadonly = false;
@ -2797,7 +2802,7 @@
if (entry.type === "directory") {
await focusPathInSidebar(entry.vault, entry.path, { alignToTop: true, expandTarget: true });
} else {
await openFile(entry.vault, entry.path);
await TabManager.openPreview(entry.vault, entry.path);
await focusPathInSidebar(entry.vault, getParentDirectoryPath(entry.path), { alignToTop: true, expandTarget: true });
syncActiveFileTreeItem(entry.vault, entry.path);
}
@ -5657,26 +5662,16 @@
</div>
</div>`;
// Wire dashboard tab switching
document.querySelectorAll(".dashboard-tab").forEach(tab => {
tab.addEventListener("click", function() {
document.querySelectorAll(".dashboard-tab").forEach(t => t.classList.remove("active"));
document.querySelectorAll(".dashboard-panel").forEach(p => p.classList.remove("active"));
this.classList.add("active");
const panel = document.getElementById("dashboard-panel-" + this.dataset.tab);
if (panel) panel.classList.add("active");
});
});
// Re-initialize widgets
// Re-initialize widgets and dashboard tabs
if (typeof DashboardRecentWidget !== "undefined") {
DashboardRecentWidget.init();
}
initDashboardTabs();
safeCreateIcons();
} else if (home) {
// Dashboard already exists, just show it
// Dashboard already exists, show it with default tab
home.style.display = "";
// Activate default tab
// Reset tabs to default
document.querySelectorAll(".dashboard-tab").forEach(t => t.classList.remove("active"));
document.querySelectorAll(".dashboard-panel").forEach(p => p.classList.remove("active"));
const defaultTab = document.querySelector('.dashboard-tab[data-tab="stats"]');
@ -7506,8 +7501,25 @@
document.addEventListener("DOMContentLoaded", () => {
init();
registerServiceWorker();
initDashboardTabs();
});
// ── Dashboard tab switching (runs on page load and after rebuild) ──
function initDashboardTabs() {
document.querySelectorAll(".dashboard-tab").forEach(tab => {
// Remove existing listeners by cloning
const newTab = tab.cloneNode(true);
tab.parentNode.replaceChild(newTab, tab);
newTab.addEventListener("click", function() {
document.querySelectorAll(".dashboard-tab").forEach(t => t.classList.remove("active"));
document.querySelectorAll(".dashboard-panel").forEach(p => p.classList.remove("active"));
this.classList.add("active");
const panel = document.getElementById("dashboard-panel-" + this.dataset.tab);
if (panel) panel.classList.add("active");
});
});
}
// ---------------------------------------------------------------------------
// Tab Manager — Multi-file tab support
// ---------------------------------------------------------------------------

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ObsiGate</title>
<!-- PWA Meta Tags -->
<meta name="description" content="Porte d'entrée web pour vos vaults Obsidian - Accédez, naviguez et recherchez dans toutes vos notes">
<meta name="theme-color" content="#2563eb">
@ -12,18 +12,18 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="ObsiGate">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" sizes="152x152" href="/static/icons/icon-152x152.svg">
<link rel="apple-touch-icon" sizes="192x192" href="/static/icons/icon-192x192.svg">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/static/icons/icon-72x72.svg">
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192x192.svg">
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" id="hljs-theme-dark">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" id="hljs-theme-light" disabled>
@ -36,7 +36,7 @@
}
}
</script>
<!-- CodeMirror 6 -->
<script type="module">
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine } from "https://esm.sh/@codemirror/view@6.9.0?external=@codemirror/state";
@ -58,7 +58,7 @@
import { java } from "https://esm.sh/@codemirror/lang-java@6.0.1?external=@codemirror/state";
import { rust } from "https://esm.sh/@codemirror/lang-rust@6.0.1?external=@codemirror/state";
import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark@6.1.0?external=@codemirror/state";
const basicSetup = [
lineNumbers(),
highlightActiveLineGutter(),
@ -86,7 +86,7 @@
...completionKeymap,
])
];
window.CodeMirror = { EditorView, EditorState, basicSetup, markdown, python, javascript, html, css, json, xml, sql, php, cpp, java, rust, oneDark, keymap };
</script>
</head>
@ -358,27 +358,29 @@
<!-- Content -->
<main class="content-area" id="content-area" aria-label="Contenu principal">
<div id="dashboard-home" class="dashboard-home" role="region" aria-label="Tableau de bord">
<!-- Stats Section -->
<div id="dashboard-stats-section" class="dashboard-section">
<div class="dashboard-header">
<div class="dashboard-title-row">
<i data-lucide="bar-chart-3" class="dashboard-icon" style="color:var(--accent)"></i>
<h2>Statistiques</h2>
</div>
</div>
<!-- Dashboard Tabs -->
<div class="dashboard-tabs">
<button class="dashboard-tab active" data-tab="stats">
<i data-lucide="bar-chart-3" style="width:14px;height:14px"></i> Statistiques
</button>
<button class="dashboard-tab" data-tab="bookmarks">
<i data-lucide="bookmark" style="width:14px;height:14px"></i> Bookmarks
</button>
<button class="dashboard-tab" data-tab="recent">
<i data-lucide="clock" style="width:14px;height:14px"></i> Récents
</button>
</div>
<!-- Stats Panel -->
<div id="dashboard-panel-stats" class="dashboard-panel active">
<div id="dashboard-stats-grid" class="dashboard-stats-grid">
<div class="dashboard-stats-loading">Chargement...</div>
</div>
<div id="dashboard-conflicts-container" style="margin-top:16px"></div>
</div>
<!-- Bookmarks Section -->
<div id="dashboard-bookmarks-section" class="dashboard-section">
<div class="dashboard-header">
<div class="dashboard-title-row">
<i data-lucide="bookmark" class="dashboard-icon" style="color:var(--accent-green)"></i>
<h2>Bookmarks</h2>
</div>
</div>
<!-- Bookmarks Panel -->
<div id="dashboard-panel-bookmarks" class="dashboard-panel">
<div id="dashboard-bookmarks-grid" class="dashboard-recent-grid"></div>
<div id="dashboard-bookmarks-empty" class="dashboard-recent-empty">
<i data-lucide="pin"></i>
@ -387,47 +389,23 @@
</div>
</div>
</div>
</div>
<!-- Sync Conflicts Section -->
<div id="dashboard-conflicts-section" class="dashboard-section" style="display:none">
<!-- Recent Panel -->
<div id="dashboard-panel-recent" class="dashboard-panel">
<div class="dashboard-header">
<div class="dashboard-title-row">
<i data-lucide="alert-triangle" class="dashboard-icon" style="color:var(--accent-orange)"></i>
<h2>Conflits de synchronisation</h2>
<span id="dashboard-conflicts-count" class="dashboard-badge" style="background:var(--accent-orange)"></span>
</div>
</div>
<div id="dashboard-conflicts-grid" class="dashboard-conflicts-grid"></div>
</div>
<!-- Recently Opened Section -->
<div id="dashboard-recent-section" class="dashboard-section">
<div class="dashboard-header">
<div class="dashboard-title-row">
<i data-lucide="clock" class="dashboard-icon"></i>
<h2>Derniers fichiers ouverts</h2>
<span id="dashboard-count" class="dashboard-badge"></span>
</div>
<!-- <div class="dashboard-actions hidden">
<div class="dashboard-actions">
<select id="dashboard-vault-filter" class="dashboard-filter" aria-label="Filtrer par vault">
<option value="all">Tous les vaults</option>
</select>
</div> -->
</div>
</div>
<div id="dashboard-recent-grid" class="dashboard-recent-grid"></div>
<div id="dashboard-loading" class="dashboard-loading">
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div>
<div class="skeleton-card"></div><div class="skeleton-card"></div><div class="skeleton-card"></div>
<div class="skeleton-card"></div><div class="skeleton-card"></div><div class="skeleton-card"></div>
</div>
<div id="dashboard-recent-empty" class="dashboard-recent-empty hidden">
<i data-lucide="inbox"></i>
<span>Aucun fichier récent</span>
@ -504,7 +482,7 @@
<div class="editor-body" id="config-body">
<div class="config-content">
<!-- Performance Settings Frontend -->
<!-- Performance Settings - Frontend -->
<section class="config-section">
<h2>Paramètres de recherche</h2>
<p class="config-description">Ces paramètres s'appliquent immédiatement côté client.</p>
@ -512,22 +490,22 @@
<div class="config-row">
<label class="config-label" for="cfg-debounce">Délai debounce (ms)</label>
<input type="number" id="cfg-debounce" class="config-input config-input--num" min="100" max="2000" step="50" value="300">
<span class="config-hint">Délai avant exécution de la recherche (1002000)</span>
<span class="config-hint">Délai avant exécution de la recherche (100-2000)</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-results-per-page">Résultats par page</label>
<input type="number" id="cfg-results-per-page" class="config-input config-input--num" min="10" max="200" step="10" value="50">
<span class="config-hint">Nombre de résultats affichés par page (10200)</span>
<span class="config-hint">Nombre de résultats affichés par page (10-200)</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-min-query">Longueur min. requête</label>
<input type="number" id="cfg-min-query" class="config-input config-input--num" min="1" max="5" step="1" value="2">
<span class="config-hint">Nombre minimum de caractères avant recherche (15)</span>
<span class="config-hint">Nombre minimum de caractères avant recherche (1-5)</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-timeout">Timeout recherche (ms)</label>
<input type="number" id="cfg-timeout" class="config-input config-input--num" min="5000" max="120000" step="5000" value="30000">
<span class="config-hint">Annuler la recherche après ce délai (5000120000)</span>
<span class="config-hint">Annuler la recherche après ce délai (5000-120000)</span>
</div>
</section>
@ -541,7 +519,7 @@
</div>
</section>
<!-- Performance Settings Backend -->
<!-- Performance Settings - Backend -->
<section class="config-section">
<h2>Paramètres backend <span class="config-badge-restart">Redémarrage requis</span></h2>
<p class="config-description">Ces paramètres sont sauvegardés sur le serveur. Certains nécessitent un redémarrage ou une réindexation.</p>
@ -549,12 +527,12 @@
<div class="config-row">
<label class="config-label" for="cfg-workers">Workers de recherche</label>
<input type="number" id="cfg-workers" class="config-input config-input--num" min="1" max="8" step="1" value="2">
<span class="config-hint">Threads dédiés à la recherche (18)</span>
<span class="config-hint">Threads dédiés à la recherche (1-8)</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-max-content">Taille max contenu (octets)</label>
<input type="number" id="cfg-max-content" class="config-input config-input--num" min="10000" max="1000000" step="10000" value="100000">
<span class="config-hint">Contenu indexé par fichier (10K1M). Réindexation requise.</span>
<span class="config-hint">Contenu indexé par fichier (10K-1M). Réindexation requise.</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-title-boost">Boost titre</label>
@ -569,7 +547,7 @@
<div class="config-row">
<label class="config-label" for="cfg-prefix-exp">Expansions préfixe max</label>
<input type="number" id="cfg-prefix-exp" class="config-input config-input--num" min="10" max="200" step="10" value="50">
<span class="config-hint">Nombre max de tokens élargis par préfixe (10200)</span>
<span class="config-hint">Nombre max de tokens élargis par préfixe (10-200)</span>
</div>
<div class="config-actions-row">
@ -583,14 +561,14 @@
<section class="config-section">
<h2>Filtrage de tags</h2>
<p class="config-description">Définissez les patterns de tags à masquer dans la sidebar. Vous pouvez utiliser des wildcards pour cibler les tags de template.</p>
<div class="config-filters-list" id="config-filters-list"></div>
<div class="config-add-pattern">
<input type="text" id="config-pattern-input" placeholder="Ex: #&lt;% ... %&gt; ou #{{ ... }}" class="config-input">
<button id="config-add-btn" class="config-btn-add">Ajouter</button>
</div>
<div class="config-regex-preview" id="config-regex-preview" style="display:none;">
<small>Regex : <code id="config-regex-code"></code></small>
</div>
@ -620,12 +598,12 @@
<div class="config-row">
<label class="config-label" for="cfg-watcher-interval">Intervalle polling (s)</label>
<input type="number" id="cfg-watcher-interval" class="config-input config-input--num" min="1" max="30" step="1" value="5">
<span class="config-hint">Intervalle de scrutation en mode polling (130 secondes)</span>
<span class="config-hint">Intervalle de scrutation en mode polling (1-30 secondes)</span>
</div>
<div class="config-row">
<label class="config-label" for="cfg-watcher-debounce">Debounce (s)</label>
<input type="number" id="cfg-watcher-debounce" class="config-input config-input--num" min="0.5" max="10" step="0.5" value="2">
<span class="config-hint">Délai avant traitement des changements groupés (0.510 secondes)</span>
<span class="config-hint">Délai avant traitement des changements groupés (0.5-10 secondes)</span>
</div>
</section>
@ -634,13 +612,13 @@
<h2>🗂️ Fichiers cachés</h2>
<p class="config-description">Contrôlez l'affichage des fichiers/dossiers cachés (commençant par <code>.</code>) par vault.</p>
<p class="config-hint" style="margin-bottom: 12px; padding: 8px; background: var(--background-secondary); border-radius: 4px;">
<strong>Note :</strong> Tous les fichiers sont toujours indexés et cherchables. Ce paramètre contrôle uniquement leur visibilité dans l'interface.
i <strong>Note :</strong> Tous les fichiers sont toujours indexés et cherchables. Ce paramètre contrôle uniquement leur visibilité dans l'interface.
</p>
<div id="hidden-files-vault-list">
<!-- Vault-specific settings will be injected here -->
</div>
<div class="config-actions-row" style="margin-top: 16px;">
<button class="config-btn-save" id="cfg-save-hidden-files">💾 Sauvegarder</button>
</div>
@ -780,7 +758,7 @@
<section class="help-section" id="help-interface">
<h2>🧭 Interface utilisateur</h2>
<h3>En-tête</h3>
<p>La barre supérieure contient les éléments essentiels :</p>
<ul>
@ -1084,7 +1062,7 @@
</section>
<div class="help-footer">
<p><strong>ObsiGate v1.2.0</strong> Porte d'entrée web pour vos vaults Obsidian</p>
<p><strong>ObsiGate v1.2.0</strong> - Porte d'entrée web pour vos vaults Obsidian</p>
<p>Pour plus d'informations, consultez le <a href="https://git.dracodev.net/Projets/ObsiGate" target="_blank">dépôt du projet</a>.</p>
</div>
</div>