fix: strip read_file line numbers accidentally injected into JS files
This commit is contained in:
+546
-547
File diff suppressed because it is too large
Load Diff
+1011
-1012
File diff suppressed because it is too large
Load Diff
+460
-461
@@ -1,462 +1,461 @@
|
|||||||
1|// dashboard.js — extracted from app.js (3414-3806) + DashboardBookmarkWidget (3810-3870)
|
1|// dashboard.js — extracted from app.js (3414-3806) + DashboardBookmarkWidget (3810-3870)
|
||||||
import { state } from './state.js';
|
import { state } from './state.js';
|
||||||
3|
|
|
||||||
4|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
5|// Recent files
|
// Recent files
|
||||||
6|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
7|let _recentRefreshTimer = null;
|
let _recentRefreshTimer = null;
|
||||||
8|let _recentTimestampTimer = null;
|
let _recentTimestampTimer = null;
|
||||||
9|let _recentFilesCache = [];
|
let _recentFilesCache = [];
|
||||||
10|
|
|
||||||
11|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
12|// Dashboard Recent Files Widget
|
// Dashboard Recent Files Widget
|
||||||
13|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
14|// ── Dashboard Stats Widget ──
|
// ── Dashboard Stats Widget ──
|
||||||
15|const DashboardStatsWidget = {
|
const DashboardStatsWidget = {
|
||||||
16| async load() {
|
async load() {
|
||||||
17| const grid = document.getElementById("dashboard-stats-grid");
|
const grid = document.getElementById("dashboard-stats-grid");
|
||||||
18| if (!grid) return;
|
if (!grid) return;
|
||||||
19| grid.innerHTML = '<div class="dashboard-stats-loading">Chargement...</div>';
|
grid.innerHTML = '<div class="dashboard-stats-loading">Chargement...</div>';
|
||||||
20| try {
|
try {
|
||||||
21| const data = await api("/api/dashboard");
|
const data = await api("/api/dashboard");
|
||||||
22| this.render(data);
|
this.render(data);
|
||||||
23| } catch (err) {
|
} catch (err) {
|
||||||
24| grid.innerHTML = `<div class="dashboard-recent-empty">Erreur: ${escapeHtml(err.message)}</div>`;
|
grid.innerHTML = `<div class="dashboard-recent-empty">Erreur: ${escapeHtml(err.message)}</div>`;
|
||||||
25| }
|
}
|
||||||
26| },
|
},
|
||||||
27| render(data) {
|
render(data) {
|
||||||
28| const grid = document.getElementById("dashboard-stats-grid");
|
const grid = document.getElementById("dashboard-stats-grid");
|
||||||
29| if (!grid) return;
|
if (!grid) return;
|
||||||
30| const fmtSize = (bytes) => bytes < 1024 ? `${bytes} o` : bytes < 1048576 ? `${(bytes/1024).toFixed(1)} Ko` : bytes < 1073741824 ? `${(bytes/1048576).toFixed(1)} Mo` : `${(bytes/1073741824).toFixed(1)} Go`;
|
const fmtSize = (bytes) => bytes < 1024 ? `${bytes} o` : bytes < 1048576 ? `${(bytes/1024).toFixed(1)} Ko` : bytes < 1073741824 ? `${(bytes/1048576).toFixed(1)} Mo` : `${(bytes/1073741824).toFixed(1)} Go`;
|
||||||
31| const items = [
|
const items = [
|
||||||
32| { icon: "files", label: "Fichiers", value: data.total_files.toLocaleString() },
|
{ icon: "files", label: "Fichiers", value: data.total_files.toLocaleString() },
|
||||||
33| { icon: "tags", label: "Tags uniques", value: data.total_tags.toLocaleString() },
|
{ icon: "tags", label: "Tags uniques", value: data.total_tags.toLocaleString() },
|
||||||
34| { icon: "hard-drive", label: "Taille totale", value: fmtSize(data.total_size_bytes) },
|
{ icon: "hard-drive", label: "Taille totale", value: fmtSize(data.total_size_bytes) },
|
||||||
35| { icon: "folder-open", label: "Vaults", value: data.vaults.length.toString() },
|
{ icon: "folder-open", label: "Vaults", value: data.vaults.length.toString() },
|
||||||
36| ];
|
];
|
||||||
37| grid.innerHTML = items.map(i => `
|
grid.innerHTML = items.map(i => `
|
||||||
38| <div class="stat-card">
|
<div class="stat-card">
|
||||||
39| <i data-lucide="${i.icon}" class="stat-icon"></i>
|
<i data-lucide="${i.icon}" class="stat-icon"></i>
|
||||||
40| <span class="stat-value">${i.value}</span>
|
<span class="stat-value">${i.value}</span>
|
||||||
41| <span class="stat-label">${i.label}</span>
|
<span class="stat-label">${i.label}</span>
|
||||||
42| </div>
|
</div>
|
||||||
43| `).join("");
|
`).join("");
|
||||||
44| safeCreateIcons();
|
safeCreateIcons();
|
||||||
45| }
|
}
|
||||||
46|};
|
};
|
||||||
47|
|
|
||||||
48|// ── Dashboard Shared Widget ──
|
// ── Dashboard Shared Widget ──
|
||||||
49|const DashboardSharedWidget = {
|
const DashboardSharedWidget = {
|
||||||
50| async load() {
|
async load() {
|
||||||
51| const grid = document.getElementById("dashboard-shared-grid");
|
const grid = document.getElementById("dashboard-shared-grid");
|
||||||
52| const empty = document.getElementById("dashboard-shared-empty");
|
const empty = document.getElementById("dashboard-shared-empty");
|
||||||
53| if (!grid) return;
|
if (!grid) return;
|
||||||
54| try {
|
try {
|
||||||
55| const shares = await api("/api/shares");
|
const shares = await api("/api/shares");
|
||||||
56| if (!shares.length) { if (empty) empty.style.display = ""; grid.innerHTML = ""; return; }
|
if (!shares.length) { if (empty) empty.style.display = ""; grid.innerHTML = ""; return; }
|
||||||
57| if (empty) empty.style.display = "none";
|
if (empty) empty.style.display = "none";
|
||||||
58| grid.innerHTML = shares.map(s => `
|
grid.innerHTML = shares.map(s => `
|
||||||
59| <div class="shared-card" data-vault="${escapeHtml(s.vault)}" data-path="${escapeHtml(s.path)}">
|
<div class="shared-card" data-vault="${escapeHtml(s.vault)}" data-path="${escapeHtml(s.path)}">
|
||||||
60| <div class="shared-card-header">
|
<div class="shared-card-header">
|
||||||
61| <i data-lucide="file-text" style="width:14px;height:14px"></i>
|
<i data-lucide="file-text" style="width:14px;height:14px"></i>
|
||||||
62| <span class="shared-card-title">${escapeHtml(s.path.split("/").pop().replace(/\.md$/i, ""))}</span>
|
<span class="shared-card-title">${escapeHtml(s.path.split("/").pop().replace(/\.md$/i, ""))}</span>
|
||||||
63| <span class="shared-card-vault">${escapeHtml(s.vault)}</span>
|
<span class="shared-card-vault">${escapeHtml(s.vault)}</span>
|
||||||
64| </div>
|
</div>
|
||||||
65| <div class="shared-card-meta">
|
<div class="shared-card-meta">
|
||||||
66| <span>${s.access_count || 0} vue(s)</span>
|
<span>${s.access_count || 0} vue(s)</span>
|
||||||
67| ${s.expires_at ? `<span>Expire le ${new Date(s.expires_at).toLocaleDateString("fr-FR")}</span>` : ""}
|
${s.expires_at ? `<span>Expire le ${new Date(s.expires_at).toLocaleDateString("fr-FR")}</span>` : ""}
|
||||||
68| </div>
|
</div>
|
||||||
69| <div class="shared-card-actions">
|
<div class="shared-card-actions">
|
||||||
70| <button class="shared-copy-btn" data-url="${window.location.origin}/s/${s.token}">📋 Copier</button>
|
<button class="shared-copy-btn" data-url="${window.location.origin}/s/${s.token}">📋 Copier</button>
|
||||||
71| <button class="shared-open-btn">📂 Ouvrir</button>
|
<button class="shared-open-btn">📂 Ouvrir</button>
|
||||||
72| <button class="shared-revoke-btn" data-id="${s.id}">🗑</button>
|
<button class="shared-revoke-btn" data-id="${s.id}">🗑</button>
|
||||||
73| </div>
|
</div>
|
||||||
74| </div>
|
</div>
|
||||||
75| `).join("");
|
`).join("");
|
||||||
76| lucide.createIcons();
|
lucide.createIcons();
|
||||||
77| grid.querySelectorAll(".shared-copy-btn").forEach(b => b.addEventListener("click", async (e) => {
|
grid.querySelectorAll(".shared-copy-btn").forEach(b => b.addEventListener("click", async (e) => {
|
||||||
78| e.stopPropagation();
|
e.stopPropagation();
|
||||||
79| const url = b.dataset.url;
|
const url = b.dataset.url;
|
||||||
80| try { await navigator.clipboard.writeText(url); } catch { const ta = document.createElement("textarea"); ta.value=url; ta.style.position="fixed"; ta.style.left="-9999px"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); }
|
try { await navigator.clipboard.writeText(url); } catch { const ta = document.createElement("textarea"); ta.value=url; ta.style.position="fixed"; ta.style.left="-9999px"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); }
|
||||||
81| showToast("Lien copié !", "success");
|
showToast("Lien copié !", "success");
|
||||||
82| }));
|
}));
|
||||||
83| grid.querySelectorAll(".shared-open-btn").forEach(b => b.addEventListener("click", (e) => {
|
grid.querySelectorAll(".shared-open-btn").forEach(b => b.addEventListener("click", (e) => {
|
||||||
84| e.stopPropagation();
|
e.stopPropagation();
|
||||||
85| const card = b.closest(".shared-card");
|
const card = b.closest(".shared-card");
|
||||||
86| if (card) TabManager.openPreview(card.dataset.vault, card.dataset.path);
|
if (card) TabManager.openPreview(card.dataset.vault, card.dataset.path);
|
||||||
87| }));
|
}));
|
||||||
88| grid.querySelectorAll(".shared-revoke-btn").forEach(b => b.addEventListener("click", async (e) => {
|
grid.querySelectorAll(".shared-revoke-btn").forEach(b => b.addEventListener("click", async (e) => {
|
||||||
89| e.stopPropagation();
|
e.stopPropagation();
|
||||||
90| await api(`/api/share/${b.dataset.id}`, { method: "DELETE" });
|
await api(`/api/share/${b.dataset.id}`, { method: "DELETE" });
|
||||||
91| showToast("Partage révoqué", "success");
|
showToast("Partage révoqué", "success");
|
||||||
92| this.load();
|
this.load();
|
||||||
93| }));
|
}));
|
||||||
94| grid.querySelectorAll(".shared-card").forEach(card => card.addEventListener("click", () => {
|
grid.querySelectorAll(".shared-card").forEach(card => card.addEventListener("click", () => {
|
||||||
95| TabManager.openPreview(card.dataset.vault, card.dataset.path);
|
TabManager.openPreview(card.dataset.vault, card.dataset.path);
|
||||||
96| }));
|
}));
|
||||||
97| } catch (err) { if (empty) empty.style.display = ""; }
|
} catch (err) { if (empty) empty.style.display = ""; }
|
||||||
98| }
|
}
|
||||||
99|};
|
};
|
||||||
100|
|
|
||||||
101|// ── Dashboard Conflicts Widget ──
|
// ── Dashboard Conflicts Widget ──
|
||||||
102|const DashboardConflictsWidget = {
|
const DashboardConflictsWidget = {
|
||||||
103| async load() {
|
async load() {
|
||||||
104| const container = document.getElementById("dashboard-conflicts-container");
|
const container = document.getElementById("dashboard-conflicts-container");
|
||||||
105| if (!container) return;
|
if (!container) return;
|
||||||
106| try {
|
try {
|
||||||
107| const data = await api("/api/conflicts");
|
const data = await api("/api/conflicts");
|
||||||
108| if (data.total === 0) { container.innerHTML = ""; return; }
|
if (data.total === 0) { container.innerHTML = ""; return; }
|
||||||
109| this.render(data.conflicts, container);
|
this.render(data.conflicts, container);
|
||||||
110| } catch (err) { container.innerHTML = ""; }
|
} catch (err) { container.innerHTML = ""; }
|
||||||
111| },
|
},
|
||||||
112| render(conflicts, container) {
|
render(conflicts, container) {
|
||||||
113| container.innerHTML = `
|
container.innerHTML = `
|
||||||
114| <div class="dashboard-section">
|
<div class="dashboard-section">
|
||||||
115| <div class="dashboard-header">
|
<div class="dashboard-header">
|
||||||
116| <div class="dashboard-title-row">
|
<div class="dashboard-title-row">
|
||||||
117| <i data-lucide="alert-triangle" class="dashboard-icon" style="color:var(--accent-orange)"></i>
|
<i data-lucide="alert-triangle" class="dashboard-icon" style="color:var(--accent-orange)"></i>
|
||||||
118| <h2>Conflits de synchronisation</h2>
|
<h2>Conflits de synchronisation</h2>
|
||||||
119| <span class="dashboard-badge" style="background:var(--accent-orange)">${conflicts.length}</span>
|
<span class="dashboard-badge" style="background:var(--accent-orange)">${conflicts.length}</span>
|
||||||
120| </div>
|
</div>
|
||||||
121| </div>
|
</div>
|
||||||
122| <div class="dashboard-conflicts-grid">
|
<div class="dashboard-conflicts-grid">
|
||||||
123| ${conflicts.map(c => `
|
${conflicts.map(c => `
|
||||||
124| <div class="conflict-card">
|
<div class="conflict-card">
|
||||||
125| <div class="conflict-info">
|
<div class="conflict-info">
|
||||||
126| <span class="conflict-vault">${escapeHtml(c.vault)}</span>
|
<span class="conflict-vault">${escapeHtml(c.vault)}</span>
|
||||||
127| <span class="conflict-name">${escapeHtml(c.conflict_path.split("/").pop())}</span>
|
<span class="conflict-name">${escapeHtml(c.conflict_path.split("/").pop())}</span>
|
||||||
128| <span class="conflict-date">Conflit du ${c.conflict_date.replace(/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})/, "$3/$2/$1 $4:$5")}</span>
|
<span class="conflict-date">Conflit du ${c.conflict_date.replace(/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})/, "$3/$2/$1 $4:$5")}</span>
|
||||||
129| </div>
|
</div>
|
||||||
130| <div class="conflict-actions">
|
<div class="conflict-actions">
|
||||||
131| <button class="conflict-btn keep-local" data-vault="${escapeHtml(c.vault)}" data-conflict="${escapeHtml(c.conflict_path)}" data-original="${escapeHtml(c.original_path)}">Garder l'original</button>
|
<button class="conflict-btn keep-local" data-vault="${escapeHtml(c.vault)}" data-conflict="${escapeHtml(c.conflict_path)}" data-original="${escapeHtml(c.original_path)}">Garder l'original</button>
|
||||||
132| <button class="conflict-btn keep-conflict" data-vault="${escapeHtml(c.vault)}" data-conflict="${escapeHtml(c.conflict_path)}" data-original="${escapeHtml(c.original_path)}">Garder le conflit</button>
|
<button class="conflict-btn keep-conflict" data-vault="${escapeHtml(c.vault)}" data-conflict="${escapeHtml(c.conflict_path)}" data-original="${escapeHtml(c.original_path)}">Garder le conflit</button>
|
||||||
133| </div>
|
</div>
|
||||||
134| </div>
|
</div>
|
||||||
135| `).join("")}
|
`).join("")}
|
||||||
136| </div>
|
</div>
|
||||||
137| </div>`;
|
</div>`;
|
||||||
138| lucide.createIcons();
|
lucide.createIcons();
|
||||||
139| container.querySelectorAll(".keep-local").forEach(btn => btn.addEventListener("click", () => this._resolve(btn.dataset, "keep_local")));
|
container.querySelectorAll(".keep-local").forEach(btn => btn.addEventListener("click", () => this._resolve(btn.dataset, "keep_local")));
|
||||||
140| container.querySelectorAll(".keep-conflict").forEach(btn => btn.addEventListener("click", () => this._resolve(btn.dataset, "keep_conflict")));
|
container.querySelectorAll(".keep-conflict").forEach(btn => btn.addEventListener("click", () => this._resolve(btn.dataset, "keep_conflict")));
|
||||||
141| },
|
},
|
||||||
142| async _resolve(d, action) {
|
async _resolve(d, action) {
|
||||||
143| try {
|
try {
|
||||||
144| await api("/api/conflicts/resolve", { method: "POST", body: JSON.stringify({ vault: d.vault, conflict_path: d.conflict, original_path: d.original, action }) });
|
await api("/api/conflicts/resolve", { method: "POST", body: JSON.stringify({ vault: d.vault, conflict_path: d.conflict, original_path: d.original, action }) });
|
||||||
145| showToast("Conflit résolu", "success");
|
showToast("Conflit résolu", "success");
|
||||||
146| this.load();
|
this.load();
|
||||||
147| } catch (err) { showToast("Erreur: " + err.message, "error"); }
|
} catch (err) { showToast("Erreur: " + err.message, "error"); }
|
||||||
148| }
|
}
|
||||||
149|};
|
};
|
||||||
150|
|
|
||||||
151|const DashboardRecentWidget = {
|
const DashboardRecentWidget = {
|
||||||
152| _cache: [],
|
_cache: [],
|
||||||
153| _currentFilter: "",
|
_currentFilter: "",
|
||||||
154|
|
|
||||||
155| async load(vaultFilter = "") {
|
async load(vaultFilter = "") {
|
||||||
156| const v = vaultFilter || selectedContextVault || "all";
|
const v = vaultFilter || selectedContextVault || "all";
|
||||||
157| this._currentFilter = v;
|
this._currentFilter = v;
|
||||||
158| this.showLoading();
|
this.showLoading();
|
||||||
159|
|
|
||||||
160| let url = "/api/recent?mode=opened";
|
let url = "/api/recent?mode=opened";
|
||||||
161| if (v !== "all") url += `&vault=${encodeURIComponent(v)}`;
|
if (v !== "all") url += `&vault=${encodeURIComponent(v)}`;
|
||||||
162|
|
|
||||||
163| try {
|
try {
|
||||||
164| const data = await api(url);
|
const data = await api(url);
|
||||||
165| this._cache = data.files || [];
|
this._cache = data.files || [];
|
||||||
166| this.render();
|
this.render();
|
||||||
167| } catch (err) {
|
} catch (err) {
|
||||||
168| console.error("Dashboard: Failed to load recent files:", err);
|
console.error("Dashboard: Failed to load recent files:", err);
|
||||||
169| this.showError();
|
this.showError();
|
||||||
170| }
|
}
|
||||||
171| },
|
},
|
||||||
172|
|
|
||||||
173| async toggleBookmark(vault, path, title, card) {
|
async toggleBookmark(vault, path, title, card) {
|
||||||
174| try {
|
try {
|
||||||
175| const data = await api("/api/bookmarks/toggle", {
|
const data = await api("/api/bookmarks/toggle", {
|
||||||
176| method: "POST",
|
method: "POST",
|
||||||
177| headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
178| body: JSON.stringify({ vault, path, title }),
|
body: JSON.stringify({ vault, path, title }),
|
||||||
179| });
|
});
|
||||||
180|
|
|
||||||
181| // Refresh both widgets to keep sync
|
// Refresh both widgets to keep sync
|
||||||
182| DashboardBookmarkWidget.load();
|
DashboardBookmarkWidget.load();
|
||||||
183|
|
|
||||||
184| // Update current card icon if it exists
|
// Update current card icon if it exists
|
||||||
185| if (card) {
|
if (card) {
|
||||||
186| const btn = card.querySelector(".dashboard-card-bookmark-btn");
|
const btn = card.querySelector(".dashboard-card-bookmark-btn");
|
||||||
187| if (btn) {
|
if (btn) {
|
||||||
188| btn.classList.toggle("active", data.bookmarked);
|
btn.classList.toggle("active", data.bookmarked);
|
||||||
189| const icon = btn.querySelector("i");
|
const icon = btn.querySelector("i");
|
||||||
190| if (icon) icon.setAttribute("data-lucide", data.bookmarked ? "bookmark" : "bookmark-plus");
|
if (icon) icon.setAttribute("data-lucide", data.bookmarked ? "bookmark" : "bookmark-plus");
|
||||||
191| safeCreateIcons();
|
safeCreateIcons();
|
||||||
192| }
|
}
|
||||||
193| }
|
}
|
||||||
194|
|
|
||||||
195| // Check if we need to refresh the current list to reflect bookmark status across all cards
|
// Check if we need to refresh the current list to reflect bookmark status across all cards
|
||||||
196| // To avoid flickering, just update the cache and re-render if needed or do a silent refresh
|
// To avoid flickering, just update the cache and re-render if needed or do a silent refresh
|
||||||
197| this._cache.forEach(f => {
|
this._cache.forEach(f => {
|
||||||
198| if (f.vault === vault && f.path === path) f.bookmarked = data.bookmarked;
|
if (f.vault === vault && f.path === path) f.bookmarked = data.bookmarked;
|
||||||
199| });
|
});
|
||||||
200| } catch (err) {
|
} catch (err) {
|
||||||
201| console.error("Failed to toggle bookmark:", err);
|
console.error("Failed to toggle bookmark:", err);
|
||||||
202| showToast("Erreur lors de l'épinglage", "error");
|
showToast("Erreur lors de l'épinglage", "error");
|
||||||
203| }
|
}
|
||||||
204| },
|
},
|
||||||
205|
|
|
||||||
206| showLoading() {
|
showLoading() {
|
||||||
207| const grid = document.getElementById("dashboard-recent-grid");
|
const grid = document.getElementById("dashboard-recent-grid");
|
||||||
208| const loading = document.getElementById("dashboard-loading");
|
const loading = document.getElementById("dashboard-loading");
|
||||||
209| const empty = document.getElementById("dashboard-recent-empty");
|
const empty = document.getElementById("dashboard-recent-empty");
|
||||||
210| const count = document.getElementById("dashboard-count");
|
const count = document.getElementById("dashboard-count");
|
||||||
211|
|
|
||||||
212| if (grid) grid.innerHTML = "";
|
if (grid) grid.innerHTML = "";
|
||||||
213| if (loading) loading.classList.add("active");
|
if (loading) loading.classList.add("active");
|
||||||
214| if (empty) empty.classList.add("hidden");
|
if (empty) empty.classList.add("hidden");
|
||||||
215| if (count) count.textContent = "";
|
if (count) count.textContent = "";
|
||||||
216| },
|
},
|
||||||
217|
|
|
||||||
218| render() {
|
render() {
|
||||||
219| const grid = document.getElementById("dashboard-recent-grid");
|
const grid = document.getElementById("dashboard-recent-grid");
|
||||||
220| const loading = document.getElementById("dashboard-loading");
|
const loading = document.getElementById("dashboard-loading");
|
||||||
221| const empty = document.getElementById("dashboard-recent-empty");
|
const empty = document.getElementById("dashboard-recent-empty");
|
||||||
222| const count = document.getElementById("dashboard-count");
|
const count = document.getElementById("dashboard-count");
|
||||||
223|
|
|
||||||
224| if (loading) loading.classList.remove("active");
|
if (loading) loading.classList.remove("active");
|
||||||
225|
|
|
||||||
226| if (!this._cache || this._cache.length === 0) {
|
if (!this._cache || this._cache.length === 0) {
|
||||||
227| this.showEmpty();
|
this.showEmpty();
|
||||||
228| return;
|
return;
|
||||||
229| }
|
}
|
||||||
230|
|
|
||||||
231| if (empty) empty.classList.add("hidden");
|
if (empty) empty.classList.add("hidden");
|
||||||
232| if (count) count.textContent = `${this._cache.length} fichier${this._cache.length > 1 ? "s" : ""}`;
|
if (count) count.textContent = `${this._cache.length} fichier${this._cache.length > 1 ? "s" : ""}`;
|
||||||
233|
|
|
||||||
234| if (!grid) return;
|
if (!grid) return;
|
||||||
235| grid.innerHTML = "";
|
grid.innerHTML = "";
|
||||||
236|
|
|
||||||
237| this._cache.forEach((f, index) => {
|
this._cache.forEach((f, index) => {
|
||||||
238| const card = this._createCard(f, index);
|
const card = this._createCard(f, index);
|
||||||
239| grid.appendChild(card);
|
grid.appendChild(card);
|
||||||
240| });
|
});
|
||||||
241|
|
|
||||||
242| safeCreateIcons();
|
safeCreateIcons();
|
||||||
243| },
|
},
|
||||||
244|
|
|
||||||
245| _createCard(file, index) {
|
_createCard(file, index) {
|
||||||
246| const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
247| card.className = "dashboard-card";
|
card.className = "dashboard-card";
|
||||||
248| card.setAttribute("data-vault", file.vault);
|
card.setAttribute("data-vault", file.vault);
|
||||||
249| card.setAttribute("data-path", file.path);
|
card.setAttribute("data-path", file.path);
|
||||||
250| card.style.animationDelay = `${Math.min(index * 50, 400)}ms`;
|
card.style.animationDelay = `${Math.min(index * 50, 400)}ms`;
|
||||||
251|
|
|
||||||
252| // Header with icon and vault badge
|
// Header with icon and vault badge
|
||||||
253| const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
254| header.className = "dashboard-card-header";
|
header.className = "dashboard-card-header";
|
||||||
255|
|
|
||||||
256| const iconContainer = document.createElement("div");
|
const iconContainer = document.createElement("div");
|
||||||
257| iconContainer.className = "dashboard-card-icon";
|
iconContainer.className = "dashboard-card-icon";
|
||||||
258| const fileIconName = getFileIcon(file.path);
|
const fileIconName = getFileIcon(file.path);
|
||||||
259| try {
|
try {
|
||||||
260| iconContainer.appendChild(icon(fileIconName, 24));
|
iconContainer.appendChild(icon(fileIconName, 24));
|
||||||
261| } catch (e) {
|
} catch (e) {
|
||||||
262| console.error("Error creating icon:", fileIconName, e);
|
console.error("Error creating icon:", fileIconName, e);
|
||||||
263| // Fallback to default file icon
|
// Fallback to default file icon
|
||||||
264| iconContainer.appendChild(icon("file", 24));
|
iconContainer.appendChild(icon("file", 24));
|
||||||
265| }
|
}
|
||||||
266|
|
|
||||||
267| const badge = document.createElement("span");
|
const badge = document.createElement("span");
|
||||||
268| badge.className = "dashboard-vault-badge";
|
badge.className = "dashboard-vault-badge";
|
||||||
269| badge.textContent = file.vault;
|
badge.textContent = file.vault;
|
||||||
270|
|
|
||||||
271| const bookmarkBtn = document.createElement("button");
|
const bookmarkBtn = document.createElement("button");
|
||||||
272| bookmarkBtn.className = `dashboard-card-bookmark-btn ${file.bookmarked ? "active" : ""}`;
|
bookmarkBtn.className = `dashboard-card-bookmark-btn ${file.bookmarked ? "active" : ""}`;
|
||||||
273| bookmarkBtn.title = file.bookmarked ? "Retirer des bookmarks" : "Ajouter aux bookmarks";
|
bookmarkBtn.title = file.bookmarked ? "Retirer des bookmarks" : "Ajouter aux bookmarks";
|
||||||
274| bookmarkBtn.innerHTML = `<i data-lucide="${file.bookmarked ? "bookmark" : "bookmark-plus"}" style="width:14px;height:14px"></i>`;
|
bookmarkBtn.innerHTML = `<i data-lucide="${file.bookmarked ? "bookmark" : "bookmark-plus"}" style="width:14px;height:14px"></i>`;
|
||||||
275|
|
|
||||||
276| bookmarkBtn.addEventListener("click", (e) => {
|
bookmarkBtn.addEventListener("click", (e) => {
|
||||||
277| e.stopPropagation();
|
e.stopPropagation();
|
||||||
278| this.toggleBookmark(file.vault, file.path, file.title, card);
|
this.toggleBookmark(file.vault, file.path, file.title, card);
|
||||||
279| });
|
});
|
||||||
280|
|
|
||||||
281| header.appendChild(iconContainer);
|
header.appendChild(iconContainer);
|
||||||
282| header.appendChild(badge);
|
header.appendChild(badge);
|
||||||
283| header.appendChild(bookmarkBtn);
|
header.appendChild(bookmarkBtn);
|
||||||
284| card.appendChild(header);
|
card.appendChild(header);
|
||||||
285|
|
|
||||||
286| // Title
|
// Title
|
||||||
287| const title = document.createElement("h3");
|
const title = document.createElement("h3");
|
||||||
288| title.className = "dashboard-card-title";
|
title.className = "dashboard-card-title";
|
||||||
289| title.textContent = file.title || file.path.split("/").pop();
|
title.textContent = file.title || file.path.split("/").pop();
|
||||||
290| title.title = file.title || file.path;
|
title.title = file.title || file.path;
|
||||||
291| card.appendChild(title);
|
card.appendChild(title);
|
||||||
292|
|
|
||||||
293| // Path (compact)
|
// Path (compact)
|
||||||
294| const pathParts = file.path.split("/");
|
const pathParts = file.path.split("/");
|
||||||
295| if (pathParts.length > 1) {
|
if (pathParts.length > 1) {
|
||||||
296| const path = document.createElement("div");
|
const path = document.createElement("div");
|
||||||
297| path.className = "dashboard-card-path";
|
path.className = "dashboard-card-path";
|
||||||
298| path.textContent = pathParts.slice(0, -1).join(" / ");
|
path.textContent = pathParts.slice(0, -1).join(" / ");
|
||||||
299| path.title = file.path;
|
path.title = file.path;
|
||||||
300| card.appendChild(path);
|
card.appendChild(path);
|
||||||
301| }
|
}
|
||||||
302|
|
|
||||||
303| // Footer with time and tags
|
// Footer with time and tags
|
||||||
304| const footer = document.createElement("div");
|
const footer = document.createElement("div");
|
||||||
305| footer.className = "dashboard-card-footer";
|
footer.className = "dashboard-card-footer";
|
||||||
306|
|
|
||||||
307| const time = document.createElement("span");
|
const time = document.createElement("span");
|
||||||
308| time.className = "dashboard-card-time";
|
time.className = "dashboard-card-time";
|
||||||
309| time.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> ${file.mtime_human || this._humanizeDelta(file.mtime)}`;
|
time.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> ${file.mtime_human || this._humanizeDelta(file.mtime)}`;
|
||||||
310|
|
|
||||||
311| footer.appendChild(time);
|
footer.appendChild(time);
|
||||||
312|
|
|
||||||
313| // Tags
|
// Tags
|
||||||
314| if (file.tags && file.tags.length > 0) {
|
if (file.tags && file.tags.length > 0) {
|
||||||
315| const tags = document.createElement("div");
|
const tags = document.createElement("div");
|
||||||
316| tags.className = "dashboard-card-tags";
|
tags.className = "dashboard-card-tags";
|
||||||
317| file.tags.slice(0, 3).forEach((tag) => {
|
file.tags.slice(0, 3).forEach((tag) => {
|
||||||
318| const tagEl = document.createElement("span");
|
const tagEl = document.createElement("span");
|
||||||
319| tagEl.className = "tag-pill";
|
tagEl.className = "tag-pill";
|
||||||
320| tagEl.textContent = tag;
|
tagEl.textContent = tag;
|
||||||
321| tags.appendChild(tagEl);
|
tags.appendChild(tagEl);
|
||||||
322| });
|
});
|
||||||
323| footer.appendChild(tags);
|
footer.appendChild(tags);
|
||||||
324| }
|
}
|
||||||
325|
|
|
||||||
326| card.appendChild(footer);
|
card.appendChild(footer);
|
||||||
327|
|
|
||||||
328| // Click handler
|
// Click handler
|
||||||
329| card.addEventListener("click", () => {
|
card.addEventListener("click", () => {
|
||||||
330| openFile(file.vault, file.path);
|
openFile(file.vault, file.path);
|
||||||
331| });
|
});
|
||||||
332|
|
|
||||||
333| return card;
|
return card;
|
||||||
334| },
|
},
|
||||||
335|
|
|
||||||
336| showEmpty() {
|
showEmpty() {
|
||||||
337| const grid = document.getElementById("dashboard-recent-grid");
|
const grid = document.getElementById("dashboard-recent-grid");
|
||||||
338| const loading = document.getElementById("dashboard-loading");
|
const loading = document.getElementById("dashboard-loading");
|
||||||
339| const empty = document.getElementById("dashboard-recent-empty");
|
const empty = document.getElementById("dashboard-recent-empty");
|
||||||
340| const count = document.getElementById("dashboard-count");
|
const count = document.getElementById("dashboard-count");
|
||||||
341|
|
|
||||||
342| if (grid) grid.innerHTML = "";
|
if (grid) grid.innerHTML = "";
|
||||||
343| if (loading) loading.classList.remove("active");
|
if (loading) loading.classList.remove("active");
|
||||||
344| if (empty) empty.classList.remove("hidden");
|
if (empty) empty.classList.remove("hidden");
|
||||||
345| if (count) count.textContent = "0 fichiers";
|
if (count) count.textContent = "0 fichiers";
|
||||||
346| safeCreateIcons();
|
safeCreateIcons();
|
||||||
347| },
|
},
|
||||||
348|
|
|
||||||
349| showError() {
|
showError() {
|
||||||
350| this.showEmpty();
|
this.showEmpty();
|
||||||
351| const empty = document.getElementById("dashboard-recent-empty");
|
const empty = document.getElementById("dashboard-recent-empty");
|
||||||
352| if (empty) {
|
if (empty) {
|
||||||
353| const msg = empty.querySelector("span");
|
const msg = empty.querySelector("span");
|
||||||
354| if (msg) msg.textContent = "Erreur de chargement";
|
if (msg) msg.textContent = "Erreur de chargement";
|
||||||
355| }
|
}
|
||||||
356| },
|
},
|
||||||
357|
|
|
||||||
358| _humanizeDelta(mtime) {
|
_humanizeDelta(mtime) {
|
||||||
359| const delta = Date.now() / 1000 - mtime;
|
const delta = Date.now() / 1000 - mtime;
|
||||||
360| if (delta < 60) return "à l'instant";
|
if (delta < 60) return "à l'instant";
|
||||||
361| if (delta < 3600) return `il y a ${Math.floor(delta / 60)} min`;
|
if (delta < 3600) return `il y a ${Math.floor(delta / 60)} min`;
|
||||||
362| if (delta < 86400) return `il y a ${Math.floor(delta / 3600)} h`;
|
if (delta < 86400) return `il y a ${Math.floor(delta / 3600)} h`;
|
||||||
363| if (delta < 604800) return `il y a ${Math.floor(delta / 86400)} j`;
|
if (delta < 604800) return `il y a ${Math.floor(delta / 86400)} j`;
|
||||||
364| return new Date(mtime * 1000).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" });
|
return new Date(mtime * 1000).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" });
|
||||||
365| },
|
},
|
||||||
366|
|
|
||||||
367| populateVaultFilter() {
|
populateVaultFilter() {
|
||||||
368| const select = document.getElementById("dashboard-vault-filter");
|
const select = document.getElementById("dashboard-vault-filter");
|
||||||
369| if (!select) return;
|
if (!select) return;
|
||||||
370|
|
|
||||||
371| // Keep first option "Tous les vaults"
|
// Keep first option "Tous les vaults"
|
||||||
372| while (select.options.length > 1) select.remove(1);
|
while (select.options.length > 1) select.remove(1);
|
||||||
373|
|
|
||||||
374| if (typeof allVaults !== "undefined" && Array.isArray(state.allVaults)) {
|
if (typeof allVaults !== "undefined" && Array.isArray(state.allVaults)) {
|
||||||
375| state.allVaults.forEach((v) => {
|
state.allVaults.forEach((v) => {
|
||||||
376| const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
377| opt.value = v.name;
|
opt.value = v.name;
|
||||||
378| opt.textContent = v.name;
|
opt.textContent = v.name;
|
||||||
379| select.appendChild(opt);
|
select.appendChild(opt);
|
||||||
380| });
|
});
|
||||||
381| }
|
}
|
||||||
382| syncVaultSelectors();
|
syncVaultSelectors();
|
||||||
383| },
|
},
|
||||||
384|
|
|
||||||
385| init() {
|
init() {
|
||||||
386| const select = document.getElementById("dashboard-vault-filter");
|
const select = document.getElementById("dashboard-vault-filter");
|
||||||
387| if (select) {
|
if (select) {
|
||||||
388| select.addEventListener("change", async () => {
|
select.addEventListener("change", async () => {
|
||||||
389| await setSelectedVaultContext(select.value, { focusVault: select.value !== "all" });
|
await setSelectedVaultContext(select.value, { focusVault: select.value !== "all" });
|
||||||
390| });
|
});
|
||||||
391| }
|
}
|
||||||
392|
|
|
||||||
393| this.populateVaultFilter();
|
this.populateVaultFilter();
|
||||||
394| },
|
},
|
||||||
395|};
|
};
|
||||||
396|
|
|
||||||
397|// ── Dashboard Bookmarks Widget ──
|
// ── Dashboard Bookmarks Widget ──
|
||||||
398|// (moved from app.js 3810-3870; logically belongs with dashboard widgets)
|
// (moved from app.js 3810-3870; logically belongs with dashboard widgets)
|
||||||
399|const DashboardBookmarkWidget = {
|
const DashboardBookmarkWidget = {
|
||||||
400| _cache: [],
|
_cache: [],
|
||||||
401| _currentFilter: "",
|
_currentFilter: "",
|
||||||
402|
|
|
||||||
403| async load(vaultFilter = "") {
|
async load(vaultFilter = "") {
|
||||||
404| const v = vaultFilter || selectedContextVault || "all";
|
const v = vaultFilter || selectedContextVault || "all";
|
||||||
405| this._currentFilter = v;
|
this._currentFilter = v;
|
||||||
406| this.showLoading();
|
this.showLoading();
|
||||||
407|
|
|
||||||
408| let url = "/api/bookmarks";
|
let url = "/api/bookmarks";
|
||||||
409| if (v !== "all") url += `?vault=${encodeURIComponent(v)}`;
|
if (v !== "all") url += `?vault=${encodeURIComponent(v)}`;
|
||||||
410|
|
|
||||||
411| try {
|
try {
|
||||||
412| const data = await api(url);
|
const data = await api(url);
|
||||||
413| this._cache = data.files || [];
|
this._cache = data.files || [];
|
||||||
414| this.render();
|
this.render();
|
||||||
415| } catch (err) {
|
} catch (err) {
|
||||||
416| console.error("Dashboard: Failed to load bookmarks:", err);
|
console.error("Dashboard: Failed to load bookmarks:", err);
|
||||||
417| this.showEmpty();
|
this.showEmpty();
|
||||||
418| }
|
}
|
||||||
419| },
|
},
|
||||||
420|
|
|
||||||
421| showLoading() {
|
showLoading() {
|
||||||
422| const grid = document.getElementById("dashboard-bookmarks-grid");
|
const grid = document.getElementById("dashboard-bookmarks-grid");
|
||||||
423| const empty = document.getElementById("dashboard-bookmarks-empty");
|
const empty = document.getElementById("dashboard-bookmarks-empty");
|
||||||
424| const section = document.getElementById("dashboard-bookmarks-section");
|
const section = document.getElementById("dashboard-bookmarks-section");
|
||||||
425|
|
|
||||||
426| if (grid) grid.innerHTML = "";
|
if (grid) grid.innerHTML = "";
|
||||||
427| if (empty) empty.classList.add("hidden");
|
if (empty) empty.classList.add("hidden");
|
||||||
428| },
|
},
|
||||||
429|
|
|
||||||
430| render() {
|
render() {
|
||||||
431| const grid = document.getElementById("dashboard-bookmarks-grid");
|
const grid = document.getElementById("dashboard-bookmarks-grid");
|
||||||
432| const empty = document.getElementById("dashboard-bookmarks-empty");
|
const empty = document.getElementById("dashboard-bookmarks-empty");
|
||||||
433| const section = document.getElementById("dashboard-bookmarks-section");
|
const section = document.getElementById("dashboard-bookmarks-section");
|
||||||
434|
|
|
||||||
435| if (!this._cache || this._cache.length === 0) {
|
if (!this._cache || this._cache.length === 0) {
|
||||||
436| if (grid) grid.innerHTML = "";
|
if (grid) grid.innerHTML = "";
|
||||||
437| if (empty) empty.classList.remove("hidden");
|
if (empty) empty.classList.remove("hidden");
|
||||||
438| return;
|
return;
|
||||||
439| }
|
}
|
||||||
440|
|
|
||||||
441| if (empty) empty.classList.add("hidden");
|
if (empty) empty.classList.add("hidden");
|
||||||
442| if (!grid) return;
|
if (!grid) return;
|
||||||
443| grid.innerHTML = "";
|
grid.innerHTML = "";
|
||||||
444|
|
|
||||||
445| this._cache.forEach((f, idx) => {
|
this._cache.forEach((f, idx) => {
|
||||||
446| const card = DashboardRecentWidget._createCard(f, idx);
|
const card = DashboardRecentWidget._createCard(f, idx);
|
||||||
447| grid.appendChild(card);
|
grid.appendChild(card);
|
||||||
448| });
|
});
|
||||||
449|
|
|
||||||
450| safeCreateIcons();
|
safeCreateIcons();
|
||||||
451| },
|
},
|
||||||
452|
|
|
||||||
453| showEmpty() {
|
showEmpty() {
|
||||||
454| const grid = document.getElementById("dashboard-bookmarks-grid");
|
const grid = document.getElementById("dashboard-bookmarks-grid");
|
||||||
455| const empty = document.getElementById("dashboard-bookmarks-empty");
|
const empty = document.getElementById("dashboard-bookmarks-empty");
|
||||||
456| if (grid) grid.innerHTML = "";
|
if (grid) grid.innerHTML = "";
|
||||||
457| if (empty) empty.classList.remove("hidden");
|
if (empty) empty.classList.remove("hidden");
|
||||||
458| }
|
}
|
||||||
459|};
|
};
|
||||||
460|
|
|
||||||
461|export { DashboardRecentWidget, DashboardStatsWidget, DashboardBookmarkWidget, DashboardSharedWidget };
|
export { DashboardRecentWidget, DashboardStatsWidget, DashboardBookmarkWidget, DashboardSharedWidget };
|
||||||
462|
|
|
||||||
|
|||||||
+735
-736
File diff suppressed because it is too large
Load Diff
+560
-561
File diff suppressed because it is too large
Load Diff
+1048
-1106
File diff suppressed because it is too large
Load Diff
+1090
-1091
File diff suppressed because it is too large
Load Diff
@@ -56,4 +56,4 @@ export const state = {
|
|||||||
activeSidebarTab: "vaults",
|
activeSidebarTab: "vaults",
|
||||||
filterDebounce: null,
|
filterDebounce: null,
|
||||||
vaultSettings: {},
|
vaultSettings: {},
|
||||||
};
|
};
|
||||||
+437
-438
@@ -1,438 +1,437 @@
|
|||||||
1|/* ObsiGate — Sync: SSE client + PWA registration */
|
1|/* ObsiGate — Sync: SSE client + PWA registration */
|
||||||
2|import {
|
import {
|
||||||
3| state.currentVault,
|
state.currentVault,
|
||||||
4| state.currentPath,
|
state.currentPath,
|
||||||
5| state.activeSidebarTab,
|
state.activeSidebarTab,
|
||||||
6| selectedContextVault
|
selectedContextVault
|
||||||
7|} from './state.js';
|
} from './state.js';
|
||||||
8|import { showToast } from './ui.js';
|
import { showToast } from './ui.js';
|
||||||
9|import { loadVaults, loadTags, refreshTagsForContext } from './sidebar.js';
|
import { loadVaults, loadTags, refreshTagsForContext } from './sidebar.js';
|
||||||
10|
|
|
||||||
11|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
12|// SSE Client — IndexUpdateManager
|
// SSE Client — IndexUpdateManager
|
||||||
13|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
14|export const IndexUpdateManager = (() => {
|
export const IndexUpdateManager = (() => {
|
||||||
15| let eventSource = null;
|
let eventSource = null;
|
||||||
16| let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
17| let reconnectDelay = 1000;
|
let reconnectDelay = 1000;
|
||||||
18| const MAX_RECONNECT_DELAY = 30000;
|
const MAX_RECONNECT_DELAY = 30000;
|
||||||
19| let recentEvents = [];
|
let recentEvents = [];
|
||||||
20| const MAX_RECENT_EVENTS = 20;
|
const MAX_RECENT_EVENTS = 20;
|
||||||
21| let connectionState = "disconnected"; // disconnected | connecting | connected
|
let connectionState = "disconnected"; // disconnected | connecting | connected
|
||||||
22|
|
|
||||||
23| function connect() {
|
function connect() {
|
||||||
24| if (eventSource) {
|
if (eventSource) {
|
||||||
25| eventSource.close();
|
eventSource.close();
|
||||||
26| }
|
}
|
||||||
27| connectionState = "connecting";
|
connectionState = "connecting";
|
||||||
28| _updateBadge();
|
_updateBadge();
|
||||||
29|
|
|
||||||
30| eventSource = new EventSource("/api/events");
|
eventSource = new EventSource("/api/events");
|
||||||
31|
|
|
||||||
32| eventSource.addEventListener("connected", (e) => {
|
eventSource.addEventListener("connected", (e) => {
|
||||||
33| connectionState = "connected";
|
connectionState = "connected";
|
||||||
34| reconnectDelay = 1000;
|
reconnectDelay = 1000;
|
||||||
35| _updateBadge();
|
_updateBadge();
|
||||||
36| });
|
});
|
||||||
37|
|
|
||||||
38| eventSource.addEventListener("index_updated", (e) => {
|
eventSource.addEventListener("index_updated", (e) => {
|
||||||
39| try {
|
try {
|
||||||
40| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
41| _addEvent("index_updated", data);
|
_addEvent("index_updated", data);
|
||||||
42| _onIndexUpdated(data);
|
_onIndexUpdated(data);
|
||||||
43| } catch (err) {
|
} catch (err) {
|
||||||
44| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
45| }
|
}
|
||||||
46| });
|
});
|
||||||
47|
|
|
||||||
48| eventSource.addEventListener("index_reloaded", (e) => {
|
eventSource.addEventListener("index_reloaded", (e) => {
|
||||||
49| try {
|
try {
|
||||||
50| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
51| _addEvent("index_reloaded", data);
|
_addEvent("index_reloaded", data);
|
||||||
52| _onIndexReloaded(data);
|
_onIndexReloaded(data);
|
||||||
53| } catch (err) {
|
} catch (err) {
|
||||||
54| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
55| }
|
}
|
||||||
56| });
|
});
|
||||||
57|
|
|
||||||
58| eventSource.addEventListener("vault_added", (e) => {
|
eventSource.addEventListener("vault_added", (e) => {
|
||||||
59| try {
|
try {
|
||||||
60| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
61| _addEvent("vault_added", data);
|
_addEvent("vault_added", data);
|
||||||
62| showToast(`Vault "${data.vault}" ajouté (${data.stats.file_count} fichiers)`, "info");
|
showToast(`Vault "${data.vault}" ajouté (${data.stats.file_count} fichiers)`, "info");
|
||||||
63| loadVaults();
|
loadVaults();
|
||||||
64| loadTags();
|
loadTags();
|
||||||
65| } catch (err) {
|
} catch (err) {
|
||||||
66| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
67| }
|
}
|
||||||
68| });
|
});
|
||||||
69|
|
|
||||||
70| eventSource.addEventListener("vault_removed", (e) => {
|
eventSource.addEventListener("vault_removed", (e) => {
|
||||||
71| try {
|
try {
|
||||||
72| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
73| _addEvent("vault_removed", data);
|
_addEvent("vault_removed", data);
|
||||||
74| showToast(`Vault "${data.vault}" supprimé`, "info");
|
showToast(`Vault "${data.vault}" supprimé`, "info");
|
||||||
75| loadVaults();
|
loadVaults();
|
||||||
76| loadTags();
|
loadTags();
|
||||||
77| } catch (err) {
|
} catch (err) {
|
||||||
78| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
79| }
|
}
|
||||||
80| });
|
});
|
||||||
81|
|
|
||||||
82| eventSource.addEventListener("index_start", (e) => {
|
eventSource.addEventListener("index_start", (e) => {
|
||||||
83| try {
|
try {
|
||||||
84| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
85| _addEvent("index_start", data);
|
_addEvent("index_start", data);
|
||||||
86| connectionState = "syncing";
|
connectionState = "syncing";
|
||||||
87| _updateBadge();
|
_updateBadge();
|
||||||
88| showToast(`Indexation démarrée (${data.total_vaults} vaults)`, "info");
|
showToast(`Indexation démarrée (${data.total_vaults} vaults)`, "info");
|
||||||
89| } catch (err) {
|
} catch (err) {
|
||||||
90| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
91| }
|
}
|
||||||
92| });
|
});
|
||||||
93|
|
|
||||||
94| eventSource.addEventListener("index_progress", (e) => {
|
eventSource.addEventListener("index_progress", (e) => {
|
||||||
95| try {
|
try {
|
||||||
96| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
97| _addEvent("index_progress", data);
|
_addEvent("index_progress", data);
|
||||||
98| connectionState = "syncing";
|
connectionState = "syncing";
|
||||||
99| _updateBadge();
|
_updateBadge();
|
||||||
100| loadVaults();
|
loadVaults();
|
||||||
101| loadTags();
|
loadTags();
|
||||||
102| } catch (err) {
|
} catch (err) {
|
||||||
103| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
104| }
|
}
|
||||||
105| });
|
});
|
||||||
106|
|
|
||||||
107| eventSource.addEventListener("index_complete", (e) => {
|
eventSource.addEventListener("index_complete", (e) => {
|
||||||
108| try {
|
try {
|
||||||
109| const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
110| _addEvent("index_complete", data);
|
_addEvent("index_complete", data);
|
||||||
111| connectionState = "connected";
|
connectionState = "connected";
|
||||||
112| _updateBadge();
|
_updateBadge();
|
||||||
113| showToast(`Indexation terminée (${data.total_files} fichiers)`, "success");
|
showToast(`Indexation terminée (${data.total_files} fichiers)`, "success");
|
||||||
114| loadVaults();
|
loadVaults();
|
||||||
115| loadTags();
|
loadTags();
|
||||||
116| } catch (err) {
|
} catch (err) {
|
||||||
117| console.error("SSE parse error:", err);
|
console.error("SSE parse error:", err);
|
||||||
118| }
|
}
|
||||||
119| });
|
});
|
||||||
120|
|
|
||||||
121| eventSource.onerror = () => {
|
eventSource.onerror = () => {
|
||||||
122| connectionState = "disconnected";
|
connectionState = "disconnected";
|
||||||
123| _updateBadge();
|
_updateBadge();
|
||||||
124| eventSource.close();
|
eventSource.close();
|
||||||
125| eventSource = null;
|
eventSource = null;
|
||||||
126| _scheduleReconnect();
|
_scheduleReconnect();
|
||||||
127| };
|
};
|
||||||
128| }
|
}
|
||||||
129|
|
|
||||||
130| function _scheduleReconnect() {
|
function _scheduleReconnect() {
|
||||||
131| if (reconnectTimer) clearTimeout(reconnectTimer);
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||||
132| reconnectTimer = setTimeout(() => {
|
reconnectTimer = setTimeout(() => {
|
||||||
133| reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
||||||
134| connect();
|
connect();
|
||||||
135| }, reconnectDelay);
|
}, reconnectDelay);
|
||||||
136| }
|
}
|
||||||
137|
|
|
||||||
138| function _addEvent(type, data) {
|
function _addEvent(type, data) {
|
||||||
139| recentEvents.unshift({
|
recentEvents.unshift({
|
||||||
140| type,
|
type,
|
||||||
141| data,
|
data,
|
||||||
142| timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
143| });
|
});
|
||||||
144| if (recentEvents.length > MAX_RECENT_EVENTS) {
|
if (recentEvents.length > MAX_RECENT_EVENTS) {
|
||||||
145| recentEvents = recentEvents.slice(0, MAX_RECENT_EVENTS);
|
recentEvents = recentEvents.slice(0, MAX_RECENT_EVENTS);
|
||||||
146| }
|
}
|
||||||
147| }
|
}
|
||||||
148|
|
|
||||||
149| async function _onIndexUpdated(data) {
|
async function _onIndexUpdated(data) {
|
||||||
150| // Brief syncing state
|
// Brief syncing state
|
||||||
151| connectionState = "syncing";
|
connectionState = "syncing";
|
||||||
152| _updateBadge();
|
_updateBadge();
|
||||||
153|
|
|
||||||
154| const n = data.total_changes || 0;
|
const n = data.total_changes || 0;
|
||||||
155| const vaults = (data.vaults || []).join(", ");
|
const vaults = (data.vaults || []).join(", ");
|
||||||
156| // Toast removed: silent auto-indexing — no notification needed
|
// Toast removed: silent auto-indexing — no notification needed
|
||||||
157|
|
|
||||||
158| // Refresh sidebar and tags if affected vault matches current context
|
// Refresh sidebar and tags if affected vault matches current context
|
||||||
159| const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(state.selectedContextVault);
|
const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(state.selectedContextVault);
|
||||||
160| if (affectsCurrentVault) {
|
if (affectsCurrentVault) {
|
||||||
161| try {
|
try {
|
||||||
162| await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]);
|
await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]);
|
||||||
163| // Refresh current file if it was updated
|
// Refresh current file if it was updated
|
||||||
164| if (currentVault && state.currentPath) {
|
if (currentVault && state.currentPath) {
|
||||||
165| const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath);
|
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath);
|
||||||
166| if (changed) {
|
if (changed) {
|
||||||
167| openFile(state.currentVault, state.currentPath);
|
openFile(state.currentVault, state.currentPath);
|
||||||
168| }
|
}
|
||||||
169| }
|
}
|
||||||
170| } catch (err) {
|
} catch (err) {
|
||||||
171| console.error("Error refreshing after index update:", err);
|
console.error("Error refreshing after index update:", err);
|
||||||
172| }
|
}
|
||||||
173| }
|
}
|
||||||
174|
|
|
||||||
175| // Refresh recent tab if it is active
|
// Refresh recent tab if it is active
|
||||||
176| if (state.activeSidebarTab === "recent") {
|
if (state.activeSidebarTab === "recent") {
|
||||||
177| const vaultFilter = document.getElementById("recent-vault-filter");
|
const vaultFilter = document.getElementById("recent-vault-filter");
|
||||||
178| loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
|
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
|
||||||
179| }
|
}
|
||||||
180|
|
|
||||||
181| setTimeout(() => {
|
setTimeout(() => {
|
||||||
182| connectionState = "connected";
|
connectionState = "connected";
|
||||||
183| _updateBadge();
|
_updateBadge();
|
||||||
184| }, 1500);
|
}, 1500);
|
||||||
185| }
|
}
|
||||||
186|
|
|
||||||
187| async function _onIndexReloaded(data) {
|
async function _onIndexReloaded(data) {
|
||||||
188| connectionState = "syncing";
|
connectionState = "syncing";
|
||||||
189| _updateBadge();
|
_updateBadge();
|
||||||
190| showToast("Index complet rechargé", "info");
|
showToast("Index complet rechargé", "info");
|
||||||
191| try {
|
try {
|
||||||
192| await Promise.all([loadVaults(), loadTags()]);
|
await Promise.all([loadVaults(), loadTags()]);
|
||||||
193| } catch (err) {
|
} catch (err) {
|
||||||
194| console.error("Error refreshing after full reload:", err);
|
console.error("Error refreshing after full reload:", err);
|
||||||
195| }
|
}
|
||||||
196| setTimeout(() => {
|
setTimeout(() => {
|
||||||
197| connectionState = "connected";
|
connectionState = "connected";
|
||||||
198| _updateBadge();
|
_updateBadge();
|
||||||
199| }, 1500);
|
}, 1500);
|
||||||
200| }
|
}
|
||||||
201|
|
|
||||||
202| function _updateBadge() {
|
function _updateBadge() {
|
||||||
203| const badge = document.getElementById("sync-badge");
|
const badge = document.getElementById("sync-badge");
|
||||||
204| if (!badge) return;
|
if (!badge) return;
|
||||||
205| badge.className = "sync-badge sync-badge--" + connectionState;
|
badge.className = "sync-badge sync-badge--" + connectionState;
|
||||||
206| const labels = {
|
const labels = {
|
||||||
207| disconnected: "Déconnecté",
|
disconnected: "Déconnecté",
|
||||||
208| connecting: "Connexion...",
|
connecting: "Connexion...",
|
||||||
209| connected: "Synchronisé",
|
connected: "Synchronisé",
|
||||||
210| syncing: "Mise à jour...",
|
syncing: "Mise à jour...",
|
||||||
211| };
|
};
|
||||||
212| badge.title = labels[connectionState] || connectionState;
|
badge.title = labels[connectionState] || connectionState;
|
||||||
213| }
|
}
|
||||||
214|
|
|
||||||
215| function disconnect() {
|
function disconnect() {
|
||||||
216| if (eventSource) {
|
if (eventSource) {
|
||||||
217| eventSource.close();
|
eventSource.close();
|
||||||
218| eventSource = null;
|
eventSource = null;
|
||||||
219| }
|
}
|
||||||
220| if (reconnectTimer) {
|
if (reconnectTimer) {
|
||||||
221| clearTimeout(reconnectTimer);
|
clearTimeout(reconnectTimer);
|
||||||
222| reconnectTimer = null;
|
reconnectTimer = null;
|
||||||
223| }
|
}
|
||||||
224| connectionState = "disconnected";
|
connectionState = "disconnected";
|
||||||
225| _updateBadge();
|
_updateBadge();
|
||||||
226| }
|
}
|
||||||
227|
|
|
||||||
228| function getState() {
|
function getState() {
|
||||||
229| return connectionState;
|
return connectionState;
|
||||||
230| }
|
}
|
||||||
231|
|
|
||||||
232| function getRecentEvents() {
|
function getRecentEvents() {
|
||||||
233| return recentEvents;
|
return recentEvents;
|
||||||
234| }
|
}
|
||||||
235|
|
|
||||||
236| return { connect, disconnect, getState, getRecentEvents };
|
return { connect, disconnect, getState, getRecentEvents };
|
||||||
237|})();
|
})();
|
||||||
238|
|
|
||||||
239|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
240|// Sync status badge and panel
|
// Sync status badge and panel
|
||||||
241|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
242|
|
|
||||||
243|function initSyncStatus() {
|
function initSyncStatus() {
|
||||||
244| const badge = document.getElementById("sync-badge");
|
const badge = document.getElementById("sync-badge");
|
||||||
245| if (!badge) return;
|
if (!badge) return;
|
||||||
246|
|
|
||||||
247| badge.addEventListener("click", (e) => {
|
badge.addEventListener("click", (e) => {
|
||||||
248| e.stopPropagation();
|
e.stopPropagation();
|
||||||
249| toggleSyncPanel();
|
toggleSyncPanel();
|
||||||
250| });
|
});
|
||||||
251|
|
|
||||||
252| IndexUpdateManager.connect();
|
IndexUpdateManager.connect();
|
||||||
253|}
|
}
|
||||||
254|
|
|
||||||
255|function toggleSyncPanel() {
|
function toggleSyncPanel() {
|
||||||
256| let panel = document.getElementById("sync-panel");
|
let panel = document.getElementById("sync-panel");
|
||||||
257| if (panel) {
|
if (panel) {
|
||||||
258| panel.remove();
|
panel.remove();
|
||||||
259| return;
|
return;
|
||||||
260| }
|
}
|
||||||
261| // Auto reconnect if disconnected when user opens the panel
|
// Auto reconnect if disconnected when user opens the panel
|
||||||
262| if (IndexUpdateManager.getState() === "disconnected") {
|
if (IndexUpdateManager.getState() === "disconnected") {
|
||||||
263| IndexUpdateManager.connect();
|
IndexUpdateManager.connect();
|
||||||
264| }
|
}
|
||||||
265| panel = document.createElement("div");
|
panel = document.createElement("div");
|
||||||
266| panel.id = "sync-panel";
|
panel.id = "sync-panel";
|
||||||
267| panel.className = "sync-panel";
|
panel.className = "sync-panel";
|
||||||
268| _renderSyncPanel(panel);
|
_renderSyncPanel(panel);
|
||||||
269| document.body.appendChild(panel);
|
document.body.appendChild(panel);
|
||||||
270|
|
|
||||||
271| // Close on outside click
|
// Close on outside click
|
||||||
272| setTimeout(() => {
|
setTimeout(() => {
|
||||||
273| document.addEventListener("click", _closeSyncPanelOutside, { once: true });
|
document.addEventListener("click", _closeSyncPanelOutside, { once: true });
|
||||||
274| }, 0);
|
}, 0);
|
||||||
275|}
|
}
|
||||||
276|
|
|
||||||
277|function _closeSyncPanelOutside(e) {
|
function _closeSyncPanelOutside(e) {
|
||||||
278| const panel = document.getElementById("sync-panel");
|
const panel = document.getElementById("sync-panel");
|
||||||
279| if (panel && !panel.contains(e.target) && e.target.id !== "sync-badge") {
|
if (panel && !panel.contains(e.target) && e.target.id !== "sync-badge") {
|
||||||
280| panel.remove();
|
panel.remove();
|
||||||
281| }
|
}
|
||||||
282|}
|
}
|
||||||
283|
|
|
||||||
284|function _renderSyncPanel(panel) {
|
function _renderSyncPanel(panel) {
|
||||||
285| const state = IndexUpdateManager.getState();
|
const state = IndexUpdateManager.getState();
|
||||||
286| const events = IndexUpdateManager.getRecentEvents();
|
const events = IndexUpdateManager.getRecentEvents();
|
||||||
287|
|
|
||||||
288| const stateLabels = {
|
const stateLabels = {
|
||||||
289| disconnected: "Déconnecté",
|
disconnected: "Déconnecté",
|
||||||
290| connecting: "Connexion...",
|
connecting: "Connexion...",
|
||||||
291| connected: "Connecté",
|
connected: "Connecté",
|
||||||
292| syncing: "Synchronisation...",
|
syncing: "Synchronisation...",
|
||||||
293| };
|
};
|
||||||
294|
|
|
||||||
295| let html = `<div class="sync-panel__header">
|
let html = `<div class="sync-panel__header">
|
||||||
296| <span class="sync-panel__title">Synchronisation</span>
|
<span class="sync-panel__title">Synchronisation</span>
|
||||||
297| <span class="sync-panel__state sync-panel__state--${state}">${stateLabels[state] || state}</span>
|
<span class="sync-panel__state sync-panel__state--${state}">${stateLabels[state] || state}</span>
|
||||||
298| </div>`;
|
</div>`;
|
||||||
299|
|
|
||||||
300| if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
301| html += `<div class="sync-panel__empty">Aucun événement récent</div>`;
|
html += `<div class="sync-panel__empty">Aucun événement récent</div>`;
|
||||||
302| } else {
|
} else {
|
||||||
303| html += `<div class="sync-panel__events">`;
|
html += `<div class="sync-panel__events">`;
|
||||||
304| events.slice(0, 10).forEach((ev) => {
|
events.slice(0, 10).forEach((ev) => {
|
||||||
305| const time = new Date(ev.timestamp).toLocaleTimeString();
|
const time = new Date(ev.timestamp).toLocaleTimeString();
|
||||||
306| const typeLabels = {
|
const typeLabels = {
|
||||||
307| index_updated: "Mise à jour",
|
index_updated: "Mise à jour",
|
||||||
308| index_reloaded: "Rechargement",
|
index_reloaded: "Rechargement",
|
||||||
309| vault_added: "Vault ajouté",
|
vault_added: "Vault ajouté",
|
||||||
310| vault_removed: "Vault supprimé",
|
vault_removed: "Vault supprimé",
|
||||||
311| index_start: "Démarrage index.",
|
index_start: "Démarrage index.",
|
||||||
312| index_progress: "Vault indexé",
|
index_progress: "Vault indexé",
|
||||||
313| index_complete: "Indexation tech.",
|
index_complete: "Indexation tech.",
|
||||||
314| };
|
};
|
||||||
315| const label = typeLabels[ev.type] || ev.type;
|
const label = typeLabels[ev.type] || ev.type;
|
||||||
316| let detail = ev.data.vaults ? ev.data.vaults.join(", ") : ev.data.vault || "";
|
let detail = ev.data.vaults ? ev.data.vaults.join(", ") : ev.data.vault || "";
|
||||||
317| if (ev.type === "index_start") detail = `${ev.data.total_vaults} vaults à traiter`;
|
if (ev.type === "index_start") detail = `${ev.data.total_vaults} vaults à traiter`;
|
||||||
318| if (ev.type === "index_progress") detail = `${ev.data.vault} (${ev.data.files} fichiers)`;
|
if (ev.type === "index_progress") detail = `${ev.data.vault} (${ev.data.files} fichiers)`;
|
||||||
319| if (ev.type === "index_complete" && ev.data.total_files !== undefined) detail = `${ev.data.total_files} fichiers total`;
|
if (ev.type === "index_complete" && ev.data.total_files !== undefined) detail = `${ev.data.total_files} fichiers total`;
|
||||||
320| html += `<div class="sync-panel__event">
|
html += `<div class="sync-panel__event">
|
||||||
321| <span class="sync-panel__event-type">${label}</span>
|
<span class="sync-panel__event-type">${label}</span>
|
||||||
322| <span class="sync-panel__event-detail">${detail}</span>
|
<span class="sync-panel__event-detail">${detail}</span>
|
||||||
323| <span class="sync-panel__event-time">${time}</span>
|
<span class="sync-panel__event-time">${time}</span>
|
||||||
324| </div>`;
|
</div>`;
|
||||||
325| });
|
});
|
||||||
326| html += `</div>`;
|
html += `</div>`;
|
||||||
327| }
|
}
|
||||||
328|
|
|
||||||
329| panel.innerHTML = html;
|
panel.innerHTML = html;
|
||||||
330|}
|
}
|
||||||
331|
|
|
||||||
332|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
333|// PWA Service Worker Registration
|
// PWA Service Worker Registration
|
||||||
334|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
335|function registerServiceWorker() {
|
function registerServiceWorker() {
|
||||||
336| if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
337| window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
338| navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
339| .register("/sw.js")
|
.register("/sw.js")
|
||||||
340| .then((registration) => {
|
.then((registration) => {
|
||||||
341| console.log("[PWA] Service Worker registered successfully:", registration.scope);
|
console.log("[PWA] Service Worker registered successfully:", registration.scope);
|
||||||
342|
|
|
||||||
343| // Check for updates periodically
|
// Check for updates periodically
|
||||||
344| setInterval(() => {
|
setInterval(() => {
|
||||||
345| registration.update();
|
registration.update();
|
||||||
346| }, 60000); // Check every minute
|
}, 60000); // Check every minute
|
||||||
347|
|
|
||||||
348| // Handle service worker updates
|
// Handle service worker updates
|
||||||
349| registration.addEventListener("updatefound", () => {
|
registration.addEventListener("updatefound", () => {
|
||||||
350| const newWorker = registration.installing;
|
const newWorker = registration.installing;
|
||||||
351| newWorker.addEventListener("statechange", () => {
|
newWorker.addEventListener("statechange", () => {
|
||||||
352| if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
|
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
|
||||||
353| // New service worker available
|
// New service worker available
|
||||||
354| showUpdateNotification();
|
showUpdateNotification();
|
||||||
355| }
|
}
|
||||||
356| });
|
});
|
||||||
357| });
|
});
|
||||||
358| })
|
})
|
||||||
359| .catch((error) => {
|
.catch((error) => {
|
||||||
360| console.log("[PWA] Service Worker registration failed:", error);
|
console.log("[PWA] Service Worker registration failed:", error);
|
||||||
361| });
|
});
|
||||||
362| });
|
});
|
||||||
363| }
|
}
|
||||||
364|}
|
}
|
||||||
365|
|
|
||||||
366|function showUpdateNotification() {
|
function showUpdateNotification() {
|
||||||
367| const message = document.createElement("div");
|
const message = document.createElement("div");
|
||||||
368| message.className = "pwa-update-notification";
|
message.className = "pwa-update-notification";
|
||||||
369| message.innerHTML = `
|
message.innerHTML = `
|
||||||
370| <div class="pwa-update-content">
|
<div class="pwa-update-content">
|
||||||
371| <span>Une nouvelle version d'ObsiGate est disponible !</span>
|
<span>Une nouvelle version d'ObsiGate est disponible !</span>
|
||||||
372| <button class="pwa-update-btn" onclick="window.location.reload()">Mettre à jour</button>
|
<button class="pwa-update-btn" onclick="window.location.reload()">Mettre à jour</button>
|
||||||
373| <button class="pwa-update-dismiss" onclick="this.parentElement.parentElement.remove()">×</button>
|
<button class="pwa-update-dismiss" onclick="this.parentElement.parentElement.remove()">×</button>
|
||||||
374| </div>
|
</div>
|
||||||
375| `;
|
`;
|
||||||
376| document.body.appendChild(message);
|
document.body.appendChild(message);
|
||||||
377|
|
|
||||||
378| // Auto-dismiss after 30 seconds
|
// Auto-dismiss after 30 seconds
|
||||||
379| setTimeout(() => {
|
setTimeout(() => {
|
||||||
380| if (message.parentElement) {
|
if (message.parentElement) {
|
||||||
381| message.remove();
|
message.remove();
|
||||||
382| }
|
}
|
||||||
383| }, 30000);
|
}, 30000);
|
||||||
384|}
|
}
|
||||||
385|
|
|
||||||
386|// Handle install prompt
|
// Handle install prompt
|
||||||
387|let deferredPrompt;
|
let deferredPrompt;
|
||||||
388|window.addEventListener("beforeinstallprompt", (e) => {
|
window.addEventListener("beforeinstallprompt", (e) => {
|
||||||
389| e.preventDefault();
|
e.preventDefault();
|
||||||
390| deferredPrompt = e;
|
deferredPrompt = e;
|
||||||
391|
|
|
||||||
392| // Show install button if desired
|
// Show install button if desired
|
||||||
393| const installBtn = document.getElementById("pwa-install-btn");
|
const installBtn = document.getElementById("pwa-install-btn");
|
||||||
394| if (installBtn) {
|
if (installBtn) {
|
||||||
395| installBtn.style.display = "block";
|
installBtn.style.display = "block";
|
||||||
396| installBtn.addEventListener("click", async () => {
|
installBtn.addEventListener("click", async () => {
|
||||||
397| if (deferredPrompt) {
|
if (deferredPrompt) {
|
||||||
398| deferredPrompt.prompt();
|
deferredPrompt.prompt();
|
||||||
399| const { outcome } = await deferredPrompt.userChoice;
|
const { outcome } = await deferredPrompt.userChoice;
|
||||||
400| console.log(`[PWA] User response to install prompt: ${outcome}`);
|
console.log(`[PWA] User response to install prompt: ${outcome}`);
|
||||||
401| deferredPrompt = null;
|
deferredPrompt = null;
|
||||||
402| installBtn.style.display = "none";
|
installBtn.style.display = "none";
|
||||||
403| }
|
}
|
||||||
404| });
|
});
|
||||||
405| }
|
}
|
||||||
406|});
|
});
|
||||||
407|
|
|
||||||
408|// Log when app is installed
|
// Log when app is installed
|
||||||
409|window.addEventListener("appinstalled", () => {
|
window.addEventListener("appinstalled", () => {
|
||||||
410| console.log("[PWA] ObsiGate has been installed");
|
console.log("[PWA] ObsiGate has been installed");
|
||||||
411| showToast("ObsiGate installé avec succès !", "success");
|
showToast("ObsiGate installé avec succès !", "success");
|
||||||
412|});
|
});
|
||||||
413|
|
|
||||||
414|// ── Dashboard tab switching (runs on page load and after rebuild) ──
|
// ── Dashboard tab switching (runs on page load and after rebuild) ──
|
||||||
415|function initDashboardTabs() {
|
function initDashboardTabs() {
|
||||||
416| document.querySelectorAll(".dashboard-tab").forEach(tab => {
|
document.querySelectorAll(".dashboard-tab").forEach(tab => {
|
||||||
417| // Remove existing listeners by cloning
|
// Remove existing listeners by cloning
|
||||||
418| const newTab = tab.cloneNode(true);
|
const newTab = tab.cloneNode(true);
|
||||||
419| tab.parentNode.replaceChild(newTab, tab);
|
tab.parentNode.replaceChild(newTab, tab);
|
||||||
420| newTab.addEventListener("click", function() {
|
newTab.addEventListener("click", function() {
|
||||||
421| document.querySelectorAll(".dashboard-tab").forEach(t => t.classList.remove("active"));
|
document.querySelectorAll(".dashboard-tab").forEach(t => t.classList.remove("active"));
|
||||||
422| document.querySelectorAll(".dashboard-panel").forEach(p => p.classList.remove("active"));
|
document.querySelectorAll(".dashboard-panel").forEach(p => p.classList.remove("active"));
|
||||||
423| this.classList.add("active");
|
this.classList.add("active");
|
||||||
424| const panel = document.getElementById("dashboard-panel-" + this.dataset.tab);
|
const panel = document.getElementById("dashboard-panel-" + this.dataset.tab);
|
||||||
425| if (panel) panel.classList.add("active");
|
if (panel) panel.classList.add("active");
|
||||||
426| });
|
});
|
||||||
427| });
|
});
|
||||||
428|}
|
}
|
||||||
429|
|
|
||||||
430|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
431|// Init — called by app.js orchestrator
|
// Init — called by app.js orchestrator
|
||||||
432|// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
433|export { initSyncStatus };
|
export { initSyncStatus };
|
||||||
434|export function init() {
|
export function init() {
|
||||||
435| registerServiceWorker();
|
registerServiceWorker();
|
||||||
436| initDashboardTabs();
|
initDashboardTabs();
|
||||||
437|}
|
}
|
||||||
438|
|
|
||||||
|
|||||||
+1296
-2000
File diff suppressed because it is too large
Load Diff
+509
-510
File diff suppressed because it is too large
Load Diff
+1254
-1554
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user