ObsiGate/frontend/js/legacy.js
Bruno Charest 643a73e0f5
All checks were successful
CI / lint (push) Successful in 13s
CI / security (push) Successful in 8s
CI / test (push) Successful in 16s
CI / build (push) Successful in 3s
fix: strip read_file line numbers accidentally injected into JS files
2026-05-28 16:40:14 -04:00

561 lines
22 KiB
JavaScript

1|/* 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 {
state.currentVault,
state.currentPath,
state.showingSource,
state.cachedRawSource,
state.searchTimeout,
state.searchCaseSensitive,
state.searchWholeWord,
state.searchRegex,
state.searchFilterVisible,
state.advancedSearchOffset,
state.selectedTags,
state.selectedContextVault,
state.vaultSettings,
state.allVaults,
state.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");
state.vaultSettings = settings;
} catch (err) {
console.error("Failed to load vault settings:", err);
state.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", state.searchCaseSensitive);
wordBtn.classList.toggle("active", state.searchWholeWord);
regexBtn.classList.toggle("active", state.searchRegex);
filterBtn.classList.toggle("active", state.searchFilterVisible);
}
// Toggle buttons
caseBtn.addEventListener("click", () => { state.searchCaseSensitive = !state.searchCaseSensitive; _updateToggleUI(); _research(); });
if (wordBtn) wordBtn.addEventListener("click", () => { state.searchWholeWord = !state.searchWholeWord; _updateToggleUI(); _research(); });
if (regexBtn) regexBtn.addEventListener("click", () => { state.searchRegex = !state.searchRegex; _updateToggleUI(); _research(); });
if (filterBtn) filterBtn.addEventListener("click", () => { state.searchFilterVisible = !state.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(state.searchTimeout);
state.searchTimeout = setTimeout(() => {
const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
state.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(state.searchTimeout);
state.searchTimeout = setTimeout(
() => {
const q = input.value.trim();
const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
state.advancedSearchOffset = 0;
if (q.length >= _getEffective("min_query_length", state.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(state.searchTimeout);
state.advancedSearchOffset = 0;
const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.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(state.searchTimeout);
state.advancedSearchOffset = 0;
const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
performAdvancedSearch(q, vault, tagFilter);
}
e.preventDefault();
}
});
clearBtn.addEventListener("click", () => {
input.value = "";
if (clearBtn) clearBtn.style.display = "none";
state.searchCaseSensitive = false;
state.searchWholeWord = false;
state.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(state.selectedContextVault);
if (typeof DashboardBookmarkWidget !== "undefined") {
DashboardBookmarkWidget.load(state.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"));
state.currentVault = null;
state.currentPath = null;
state.showingSource = false;
state.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;
state.searchCaseSensitive = s.case_sensitive || false;
state.searchWholeWord = s.whole_word || false;
state.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(state.searchTimeout);
state.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,
};