frontend/js/ structure: state.js (55 lines) — Shared mutable state, constants utils.js (510 lines) — EXT_ICONS, getFileIcon, escapeHtml, safeCreateIcons auth.js (547 lines) — api(), AuthManager, initLoginForm, AdminPanel search.js (1106 lines)— SearchHistory, QueryParser, Autocomplete, performSearch sidebar.js (1091 lines)— Vault tree, sidebar filter, TagFilterService, loadTags viewer.js (1554 lines)— openFile, Outline, ScrollSpy, Frontmatter, Editor ui.js (2250 lines)— Theme, Toast, Sidebar, Dropdowns, Tabs, ContextMenu dashboard.js (461 lines) — Dashboard widgets (Recent, Stats, Bookmarks) config.js (999 lines) — Config panel, Hidden files, About, Sidebar tabs sync.js (436 lines) — SSE/IndexUpdateManager, PWA registration graph.js (401 lines) — GraphViewManager (force-directed canvas graph) legacy.js (550 lines) — Remaining bridge functions (goHome, showWelcome, initSearch) app.js (80 lines) — Thin orchestrator: imports all modules, calls init() index.html: switched from <script src="app.js"> to <script type="module" src="js/app.js"> Original app.js preserved for backward compatibility. All 14 modules pass node --check syntax validation.
561 lines
21 KiB
JavaScript
561 lines
21 KiB
JavaScript
/* ObsiGate — Legacy module: remaining functions for the orchestrator
|
|
Extracted from the monolithic frontend/app.js IIFE.
|
|
Functions already in other modules are re-exported. */
|
|
|
|
// --- State imports ---
|
|
import {
|
|
currentVault,
|
|
currentPath,
|
|
showingSource,
|
|
cachedRawSource,
|
|
searchTimeout,
|
|
searchCaseSensitive,
|
|
searchWholeWord,
|
|
searchRegex,
|
|
searchFilterVisible,
|
|
advancedSearchOffset,
|
|
selectedTags,
|
|
selectedContextVault,
|
|
vaultSettings,
|
|
allVaults,
|
|
MIN_SEARCH_LENGTH,
|
|
} from './state.js';
|
|
|
|
// --- Search imports ---
|
|
import {
|
|
AutocompleteDropdown,
|
|
SearchChips,
|
|
SearchHistory,
|
|
performAdvancedSearch,
|
|
} from './search.js';
|
|
|
|
// --- Dashboard imports ---
|
|
import {
|
|
DashboardRecentWidget,
|
|
DashboardStatsWidget,
|
|
DashboardBookmarkWidget,
|
|
DashboardSharedWidget,
|
|
} from './dashboard.js';
|
|
|
|
// --- Re-exports from modules that already have these functions ---
|
|
export { initSidebarTabs, initConfigModal, initHelpModal, initRecentTab } from './config.js';
|
|
export { initSyncStatus } from './sync.js';
|
|
export { DashboardRecentWidget } from './dashboard.js';
|
|
|
|
// =========================================================================
|
|
// Progress bar helpers (used by showWelcome)
|
|
// =========================================================================
|
|
|
|
function showProgressBar() {
|
|
const bar = document.getElementById("search-progress-bar");
|
|
if (bar) bar.classList.add("active");
|
|
}
|
|
|
|
function hideProgressBar() {
|
|
const bar = document.getElementById("search-progress-bar");
|
|
if (bar) bar.classList.remove("active");
|
|
}
|
|
|
|
// =========================================================================
|
|
// loadVaultSettings
|
|
// =========================================================================
|
|
|
|
async function loadVaultSettings() {
|
|
try {
|
|
const settings = await api("/api/vaults/settings/all");
|
|
vaultSettings = settings;
|
|
} catch (err) {
|
|
console.error("Failed to load vault settings:", err);
|
|
vaultSettings = {};
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Config helpers (needed by initSearch)
|
|
// =========================================================================
|
|
|
|
const _FRONTEND_CONFIG_KEY = "obsigate-perf-config";
|
|
|
|
function _getFrontendConfig() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(_FRONTEND_CONFIG_KEY) || "{}");
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function _getEffective(key, fallback) {
|
|
const cfg = _getFrontendConfig();
|
|
return cfg[key] !== undefined ? cfg[key] : fallback;
|
|
}
|
|
|
|
/** Check if user is focused on an input/textarea/contenteditable */
|
|
function _isInputFocused() {
|
|
const tag = document.activeElement?.tagName;
|
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
return document.activeElement?.isContentEditable === true;
|
|
}
|
|
|
|
// =========================================================================
|
|
// initSearch
|
|
// =========================================================================
|
|
|
|
function initSearch() {
|
|
const input = document.getElementById("search-input");
|
|
if (!input) return;
|
|
const caseBtn = document.getElementById("search-case-btn");
|
|
const wordBtn = document.getElementById("search-word-btn");
|
|
const regexBtn = document.getElementById("search-regex-btn");
|
|
const filterBtn = document.getElementById("search-filter-btn");
|
|
const clearBtn = document.getElementById("search-clear-btn");
|
|
const filterRow = document.getElementById("search-filter-row");
|
|
const prevBtn = document.getElementById("search-prev-btn");
|
|
const nextBtn = document.getElementById("search-next-btn");
|
|
const countEl = document.getElementById("search-match-count");
|
|
|
|
function _updateToggleUI() {
|
|
caseBtn.classList.toggle("active", searchCaseSensitive);
|
|
wordBtn.classList.toggle("active", searchWholeWord);
|
|
regexBtn.classList.toggle("active", searchRegex);
|
|
filterBtn.classList.toggle("active", searchFilterVisible);
|
|
}
|
|
|
|
// Toggle buttons
|
|
caseBtn.addEventListener("click", () => { searchCaseSensitive = !searchCaseSensitive; _updateToggleUI(); _research(); });
|
|
if (wordBtn) wordBtn.addEventListener("click", () => { searchWholeWord = !searchWholeWord; _updateToggleUI(); _research(); });
|
|
if (regexBtn) regexBtn.addEventListener("click", () => { searchRegex = !searchRegex; _updateToggleUI(); _research(); });
|
|
if (filterBtn) filterBtn.addEventListener("click", () => { searchFilterVisible = !searchFilterVisible; if (filterRow) filterRow.style.display = searchFilterVisible ? "flex" : "none"; _updateToggleUI(); });
|
|
|
|
// Result navigation (up/down arrows + Enter)
|
|
let _searchResultIdx = -1;
|
|
let _searchResultItems = [];
|
|
|
|
function _updateResultHighlight() {
|
|
_searchResultItems.forEach((el, i) => {
|
|
el.classList.toggle("search-result-active", i === _searchResultIdx);
|
|
});
|
|
if (_searchResultIdx >= 0 && _searchResultIdx < _searchResultItems.length) {
|
|
_searchResultItems[_searchResultIdx].scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
}
|
|
const countEl = document.getElementById("search-match-count");
|
|
if (countEl) countEl.textContent = _searchResultIdx >= 0 ? `${_searchResultIdx + 1}/${_searchResultItems.length}` : `0/${_searchResultItems.length}`;
|
|
}
|
|
|
|
function _refreshResultItems() {
|
|
_searchResultItems = Array.from(document.querySelectorAll(".search-result-item"));
|
|
_searchResultIdx = _searchResultItems.length > 0 ? 0 : -1;
|
|
_updateResultHighlight();
|
|
}
|
|
|
|
window.navigateSearchResults = function(delta) {
|
|
_searchResultItems = Array.from(document.querySelectorAll(".search-result-item"));
|
|
if (_searchResultItems.length === 0) return;
|
|
_searchResultIdx = Math.max(0, Math.min(_searchResultItems.length - 1, _searchResultIdx + delta));
|
|
_updateResultHighlight();
|
|
};
|
|
|
|
if (prevBtn) prevBtn.addEventListener("click", () => navigateSearchResults(-1));
|
|
if (nextBtn) nextBtn.addEventListener("click", () => navigateSearchResults(1));
|
|
|
|
function _research() {
|
|
const q = input.value.trim();
|
|
if (q.length >= _getEffective("min_query_length", 2)) {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
const vault = document.getElementById("vault-filter").value;
|
|
const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null;
|
|
advancedSearchOffset = 0;
|
|
performAdvancedSearch(q, vault, tagFilter);
|
|
}, _getEffective("debounce_ms", 300));
|
|
}
|
|
}
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.altKey && !e.ctrlKey && !e.metaKey) {
|
|
if (e.key === "c" || e.key === "C") { e.preventDefault(); caseBtn.click(); }
|
|
else if (e.key === "w" || e.key === "W") { e.preventDefault(); if (wordBtn) wordBtn.click(); }
|
|
else if (e.key === "r" || e.key === "R") { e.preventDefault(); if (regexBtn) regexBtn.click(); }
|
|
else if (e.key === "f" || e.key === "F") { e.preventDefault(); if (filterBtn) filterBtn.click(); input.focus(); }
|
|
}
|
|
});
|
|
|
|
// Initialize sub-controllers
|
|
AutocompleteDropdown.init();
|
|
SearchChips.init();
|
|
|
|
// Initially hide clear button
|
|
if (clearBtn) clearBtn.style.display = "none";
|
|
|
|
// Input handler: debounced search + autocomplete dropdown
|
|
input.addEventListener("input", () => {
|
|
const hasText = input.value.length > 0;
|
|
clearBtn.style.display = hasText ? "flex" : "none";
|
|
|
|
// Show autocomplete dropdown while typing
|
|
AutocompleteDropdown.populate(input.value, input.selectionStart);
|
|
|
|
// Debounced search execution
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(
|
|
() => {
|
|
const q = input.value.trim();
|
|
const vault = document.getElementById("vault-filter").value;
|
|
const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null;
|
|
advancedSearchOffset = 0;
|
|
if (q.length >= _getEffective("min_query_length", MIN_SEARCH_LENGTH) || tagFilter) {
|
|
performAdvancedSearch(q, vault, tagFilter);
|
|
} else if (q.length === 0) {
|
|
SearchChips.clear();
|
|
showWelcome();
|
|
}
|
|
},
|
|
_getEffective("debounce_ms", 300),
|
|
);
|
|
});
|
|
|
|
// Focus handler: show history dropdown
|
|
input.addEventListener("focus", () => {
|
|
if (input.value.length === 0) {
|
|
const historyItems = SearchHistory.filter("").slice(0, 5);
|
|
if (historyItems.length > 0) {
|
|
AutocompleteDropdown.populate("", 0);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Keyboard navigation in dropdown
|
|
input.addEventListener("keydown", (e) => {
|
|
if (AutocompleteDropdown.isVisible()) {
|
|
if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
AutocompleteDropdown.navigateDown();
|
|
} else if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
AutocompleteDropdown.navigateUp();
|
|
} else if (e.key === "Enter") {
|
|
// First: check dropdown suggestions (higher priority than search results)
|
|
if (AutocompleteDropdown.isVisible() && AutocompleteDropdown.selectActive()) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
// Second: navigate search results if visible
|
|
const results = document.querySelectorAll(".search-result-item");
|
|
if (results.length > 0 && _searchResultIdx >= 0) {
|
|
const el = results[_searchResultIdx];
|
|
if (el) {
|
|
const vault = el.dataset.vault;
|
|
const path = el.dataset.path;
|
|
if (vault && path) { TabManager.openPreview(vault, path); e.preventDefault(); return; }
|
|
}
|
|
}
|
|
// Third: execute search
|
|
AutocompleteDropdown.hide();
|
|
const q = input.value.trim();
|
|
if (q) {
|
|
SearchHistory.add(q);
|
|
clearTimeout(searchTimeout);
|
|
advancedSearchOffset = 0;
|
|
const vault = document.getElementById("vault-filter").value;
|
|
const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null;
|
|
performAdvancedSearch(q, vault, tagFilter);
|
|
}
|
|
e.preventDefault();
|
|
} else if (e.key === "ArrowDown" && !AutocompleteDropdown.isVisible()) {
|
|
// Navigate search results when dropdown is closed
|
|
if (window.navigateSearchResults) { window.navigateSearchResults(1); e.preventDefault(); }
|
|
} else if (e.key === "ArrowUp" && !AutocompleteDropdown.isVisible()) {
|
|
if (window.navigateSearchResults) { window.navigateSearchResults(-1); e.preventDefault(); }
|
|
} else if (e.key === "Escape") {
|
|
AutocompleteDropdown.hide();
|
|
e.stopPropagation();
|
|
}
|
|
} else if (e.key === "Enter") {
|
|
if (AutocompleteDropdown.isVisible() && AutocompleteDropdown.selectActive()) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
const q = input.value.trim();
|
|
if (q) {
|
|
SearchHistory.add(q);
|
|
clearTimeout(searchTimeout);
|
|
advancedSearchOffset = 0;
|
|
const vault = document.getElementById("vault-filter").value;
|
|
const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null;
|
|
performAdvancedSearch(q, vault, tagFilter);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
clearBtn.addEventListener("click", () => {
|
|
input.value = "";
|
|
if (clearBtn) clearBtn.style.display = "none";
|
|
searchCaseSensitive = false;
|
|
searchWholeWord = false;
|
|
searchRegex = false;
|
|
_updateToggleUI();
|
|
SearchChips.clear();
|
|
AutocompleteDropdown.hide();
|
|
showWelcome();
|
|
});
|
|
|
|
// Global keyboard shortcuts
|
|
document.addEventListener("keydown", (e) => {
|
|
// Ctrl+K or Cmd+K: focus search
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
|
|
e.preventDefault();
|
|
input.focus();
|
|
input.select();
|
|
}
|
|
// "/" key: focus search (when not in an input/textarea)
|
|
if (e.key === "/" && !_isInputFocused()) {
|
|
e.preventDefault();
|
|
input.focus();
|
|
}
|
|
// Escape: blur search input and close dropdown
|
|
if (e.key === "Escape" && document.activeElement === input) {
|
|
AutocompleteDropdown.hide();
|
|
input.blur();
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// showWelcome
|
|
// =========================================================================
|
|
|
|
function showWelcome() {
|
|
hideProgressBar();
|
|
|
|
// Restore or rebuild the dashboard with tabbed sections
|
|
const area = document.getElementById("content-area");
|
|
const home = document.getElementById("dashboard-home");
|
|
|
|
if (area && !home) {
|
|
area.innerHTML = `
|
|
<div id="dashboard-home" class="dashboard-home" role="region" aria-label="Tableau de bord">
|
|
<!-- 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>
|
|
<button class="dashboard-tab" data-tab="shared">
|
|
<i data-lucide="share-2" style="width:14px;height:14px"></i> Partagés
|
|
</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 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>
|
|
<span>Aucun bookmark</span>
|
|
<p>Épinglez des fichiers pour les retrouver ici.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Panel -->
|
|
<div id="dashboard-panel-recent" class="dashboard-panel">
|
|
<div class="dashboard-header">
|
|
<div class="dashboard-title-row">
|
|
<span id="dashboard-count" class="dashboard-badge"></span>
|
|
</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>
|
|
<div id="dashboard-recent-empty" class="dashboard-recent-empty hidden">
|
|
<i data-lucide="inbox"></i>
|
|
<span>Aucun fichier récent</span>
|
|
<p>Ouvrez un fichier pour le voir apparaître ici</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shared Panel -->
|
|
<div id="dashboard-panel-shared" class="dashboard-panel">
|
|
<div id="dashboard-shared-grid" class="dashboard-recent-grid"></div>
|
|
<div id="dashboard-shared-empty" class="dashboard-recent-empty">
|
|
<i data-lucide="share-2"></i>
|
|
<span>Aucun document partagé</span>
|
|
<p>Partagez un document pour le voir apparaître ici</p>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
// Re-initialize widgets and dashboard tabs
|
|
DashboardRecentWidget.init();
|
|
initDashboardTabs();
|
|
safeCreateIcons();
|
|
} else if (home) {
|
|
// Dashboard already exists, show it with default tab
|
|
home.style.display = "";
|
|
// 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"]');
|
|
const defaultPanel = document.getElementById("dashboard-panel-stats");
|
|
if (defaultTab) defaultTab.classList.add("active");
|
|
if (defaultPanel) defaultPanel.classList.add("active");
|
|
}
|
|
|
|
// Load all widgets (they handle missing elements gracefully)
|
|
if (typeof DashboardStatsWidget !== "undefined") {
|
|
DashboardStatsWidget.load();
|
|
}
|
|
if (typeof DashboardConflictsWidget !== "undefined") {
|
|
DashboardConflictsWidget.load();
|
|
}
|
|
DashboardRecentWidget.load(selectedContextVault);
|
|
if (typeof DashboardBookmarkWidget !== "undefined") {
|
|
DashboardBookmarkWidget.load(selectedContextVault);
|
|
}
|
|
if (typeof DashboardSharedWidget !== "undefined") {
|
|
DashboardSharedWidget.load();
|
|
}
|
|
|
|
// Load saved searches sidebar
|
|
loadSavedSearches();
|
|
}
|
|
|
|
// =========================================================================
|
|
// goHome
|
|
// =========================================================================
|
|
|
|
function goHome() {
|
|
const searchInput = document.getElementById("search-input");
|
|
if (searchInput) searchInput.value = "";
|
|
|
|
document.querySelectorAll(".tree-item.active").forEach((el) => el.classList.remove("active"));
|
|
|
|
currentVault = null;
|
|
currentPath = null;
|
|
showingSource = false;
|
|
cachedRawSource = null;
|
|
|
|
closeMobileSidebar();
|
|
showWelcome();
|
|
}
|
|
|
|
// =========================================================================
|
|
// loadSavedSearches (needed by showWelcome)
|
|
// =========================================================================
|
|
|
|
async function loadSavedSearches() {
|
|
const list = document.getElementById("saved-searches-list");
|
|
const empty = document.getElementById("saved-searches-empty");
|
|
if (!list) return;
|
|
try {
|
|
const searches = await api("/api/saved-searches");
|
|
if (!searches.length) {
|
|
list.innerHTML = "";
|
|
if (empty) empty.style.display = "";
|
|
return;
|
|
}
|
|
if (empty) empty.style.display = "none";
|
|
list.innerHTML = searches.map(s => {
|
|
const badges = [];
|
|
if (s.case_sensitive) badges.push('<span class="search-filter-badge">Aa</span>');
|
|
if (s.whole_word) badges.push('<span class="search-filter-badge">wd</span>');
|
|
if (s.regex) badges.push('<span class="search-filter-badge">.*</span>');
|
|
const pathFilters = [];
|
|
if (s.include_paths) pathFilters.push(`<span class="saved-search-path" title="Inclure: ${escapeHtml(s.include_paths)}">\u{1F4E5} ${escapeHtml(s.include_paths)}</span>`);
|
|
if (s.exclude_paths) pathFilters.push(`<span class="saved-search-path" title="Exclure: ${escapeHtml(s.exclude_paths)}">\u{1F4E4} ${escapeHtml(s.exclude_paths)}</span>`);
|
|
const vaultStr = s.vault && s.vault !== "all" ? `<span class="saved-search-vault">\u{1F4C1} ${escapeHtml(s.vault)}</span>` : "";
|
|
return `
|
|
<div class="saved-search-item">
|
|
<div class="saved-search-query">${escapeHtml(s.query)}</div>
|
|
<div class="saved-search-meta">
|
|
${badges.join("")}
|
|
${vaultStr}
|
|
</div>
|
|
${pathFilters.length ? '<div class="saved-search-filters">' + pathFilters.join(" ") + '</div>' : ""}
|
|
<button class="saved-search-delete" data-id="${s.id}" title="Supprimer"><E2><9C><95></button>
|
|
</div>
|
|
`}).join("");
|
|
list.querySelectorAll(".saved-search-item").forEach(item => {
|
|
item.addEventListener("click", (e) => {
|
|
if (e.target.classList.contains("saved-search-delete")) return;
|
|
const idx = Array.from(list.children).indexOf(item);
|
|
const s = searches[idx];
|
|
if (!s) return;
|
|
// Apply the saved search
|
|
const input = document.getElementById("search-input");
|
|
if (input) input.value = s.query;
|
|
searchCaseSensitive = s.case_sensitive || false;
|
|
searchWholeWord = s.whole_word || false;
|
|
searchRegex = s.regex || false;
|
|
if (typeof _updateToggleUI === "function") _updateToggleUI();
|
|
if (s.include_paths) {
|
|
const incl = document.getElementById("search-include-input");
|
|
if (incl) incl.value = s.include_paths;
|
|
}
|
|
if (s.exclude_paths) {
|
|
const excl = document.getElementById("search-exclude-input");
|
|
if (excl) excl.value = s.exclude_paths;
|
|
}
|
|
// Execute the search
|
|
AutocompleteDropdown.hide();
|
|
AutocompleteDropdown._suppressNext = true;
|
|
const vault = s.vault || "all";
|
|
if (input) { input.dispatchEvent(new Event("input")); }
|
|
clearTimeout(searchTimeout);
|
|
advancedSearchOffset = 0;
|
|
performAdvancedSearch(s.query, vault, null);
|
|
});
|
|
});
|
|
list.querySelectorAll(".saved-search-delete").forEach(b => b.addEventListener("click", async (e) => {
|
|
e.stopPropagation();
|
|
await api(`/api/saved-searches/${b.dataset.id}`, { method: "DELETE" });
|
|
loadSavedSearches();
|
|
}));
|
|
safeCreateIcons();
|
|
} catch (err) { /* silently ignore */ }
|
|
}
|
|
|
|
// =========================================================================
|
|
// initDashboardTabs (needed by showWelcome)
|
|
// =========================================================================
|
|
|
|
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");
|
|
});
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// Exports
|
|
// =========================================================================
|
|
|
|
export {
|
|
loadVaultSettings,
|
|
initSearch,
|
|
showWelcome,
|
|
goHome,
|
|
};
|