';
panel.appendChild(section);
// Trigger icon refresh via global
if (window._obsigateRefreshIcons) window._obsigateRefreshIcons();
}
/** Update the AI section in the open sync panel. */
function _updateAIStatusInPanel() {
const section = document.getElementById('sync-panel-ai');
if (!section) return;
section.innerHTML = '';
const panel = document.getElementById('sync-panel');
if (panel) {
// Re-render just the AI section
const oldSection = panel.querySelector('.sync-panel__ai-section');
if (oldSection) oldSection.remove();
_renderAIStatusSection(panel);
}
}
/** Flash the sync badge white during AI processing.
* NOTE: now handled via CSS class on #sync-badge.
* The visual flash in the editor itself is handled by ai.js
* which adds a processing indicator inline. */
function _setBadgeAIProcessing(active) {
const badge = document.getElementById('sync-badge');
if (!badge) return;
if (active) {
badge.classList.add('sync-badge--ai-processing');
} else {
badge.classList.remove('sync-badge--ai-processing');
}
}
/** Initialize AI status tracking. */
function _initAIStatus() {
// Check AI status immediately at startup
_checkAIStatus();
// Listen for AI processing events from ai.js
document.addEventListener('ai-processing-start', () => {
_aiProcessing = true;
_setBadgeAIProcessing(true);
_updateAIStatusInPanel();
});
document.addEventListener('ai-processing-end', () => {
_aiProcessing = false;
_setBadgeAIProcessing(false);
_updateAIStatusInPanel();
});
// Re-check AI status when sync panel opens (in case config changed)
const badge = document.getElementById('sync-badge');
if (badge) {
badge.addEventListener('click', () => {
_checkAIStatus();
}, { once: false });
}
}
// ---------------------------------------------------------------------------
// PWA Service Worker Registration
// ---------------------------------------------------------------------------
function registerServiceWorker() {
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("[PWA] Service Worker registered successfully:", registration.scope);
// Check for updates periodically
setInterval(() => {
registration.update();
}, 60000); // Check every minute
// Handle service worker updates
registration.addEventListener("updatefound", () => {
const newWorker = registration.installing;
newWorker.addEventListener("statechange", () => {
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
// New service worker available
showUpdateNotification();
}
});
});
})
.catch((error) => {
console.log("[PWA] Service Worker registration failed:", error);
});
});
}
}
function showUpdateNotification() {
const message = document.createElement("div");
message.className = "pwa-update-notification";
message.innerHTML = `
Une nouvelle version d'ObsiGate est disponible !
`;
document.body.appendChild(message);
// Auto-dismiss after 30 seconds
setTimeout(() => {
if (message.parentElement) {
message.remove();
}
}, 30000);
}
// Handle install prompt
let deferredPrompt;
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
deferredPrompt = e;
// Show install button if desired
const installBtn = document.getElementById("pwa-install-btn");
if (installBtn) {
installBtn.style.display = "block";
installBtn.addEventListener("click", async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`[PWA] User response to install prompt: ${outcome}`);
deferredPrompt = null;
installBtn.style.display = "none";
}
});
}
});
// Log when app is installed
window.addEventListener("appinstalled", () => {
console.log("[PWA] ObsiGate has been installed");
showToast(t("sync.install_success"), "success");
});
// ── 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");
});
});
}
// ---------------------------------------------------------------------------
// Init — called by app.js orchestrator
// ---------------------------------------------------------------------------
export { initSyncStatus, initDashboardTabs };
export function init() {
registerServiceWorker();
initOffline();
initThemes();
initAboutModal();
// Sync file index after a short delay (let vaults load first).
// Gate on auth at execution time (not scheduling time) so that
// initAuth() has a chance to complete and set _authEnabled / token.
// The login handler triggers syncFileIndexFromServer explicitly on
// fresh login, so we only do it here for already-authenticated or
// auth-disabled sessions.
setTimeout(() => {
if (!AuthManager._authEnabled || AuthManager.getToken()) {
syncFileIndexFromServer();
}
}, 3000);
initDashboardTabs();
// Listen for Forge editor rename events
window.addEventListener('message', function(e) {
if (!e.data || !e.data.type) return;
if (e.data.type === 'forge-renamed') {
var vault = e.data.vault;
var oldPath = e.data.oldPath;
var newPath = e.data.newPath;
console.log('[Forge] File renamed:', vault, oldPath, '->', newPath);
var titleEl = document.getElementById('editor-title');
if (titleEl && titleEl.textContent.indexOf('Forge:') === 0) {
titleEl.textContent = 'Forge: ' + newPath.split('/').pop();
}
refreshSidebarTreePreservingState(vault);
if (state.currentVault === vault && state.currentPath === oldPath) {
import('./viewer.js').then(function(m) { m.openFile(vault, newPath); });
}
}
if (e.data.type === 'forge-close') {
var modal = document.getElementById('editor-modal');
var forgeFrame = document.getElementById('forge-iframe');
if (forgeFrame) forgeFrame.remove();
if (modal) modal.classList.remove('active');
// Restore brand text
var brand = modal.querySelector('.editor-brand');
if (brand) brand.textContent = 'ObsiGate';
// Restore header/footer
var headerEl = modal.querySelector('.editor-header');
var footerEl = modal.querySelector('.editor-footer');
if (headerEl) headerEl.style.display = '';
if (footerEl) footerEl.style.display = '';
// Refresh current file content
if (state.currentVault && state.currentPath) {
import('./viewer.js').then(function(m) { m.openFile(state.currentVault, state.currentPath); });
}
}
});
}