Refactor frontend into ES modules with modern tooling
CI / lint (push) Successful in 25s
CI / security (push) Successful in 19s
CI / test (push) Successful in 29s
CI / build (push) Successful in 8s
CI / e2e (push) Failing after 21s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Successful in 25s
CI / security (push) Successful in 19s
CI / test (push) Successful in 29s
CI / build (push) Successful in 8s
CI / e2e (push) Failing after 21s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
/* ObsiGate — Legacy module: remaining functions for the orchestrator
|
||||
Extracted from the monolithic frontend/app.js IIFE.
|
||||
Functions already in other modules are re-exported. */
|
||||
|
||||
import { api } from './auth.js';
|
||||
import { state } from './state.js';
|
||||
import { escapeHtml, safeCreateIcons } from './utils.js';
|
||||
import { TabManager, closeMobileSidebar } from './ui.js';
|
||||
import { showProgressBar, hideProgressBar, showWelcome } from './viewer.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, initAboutModal } from './config.js';
|
||||
export { initSyncStatus } from './sync.js';
|
||||
export { DashboardRecentWidget } from './dashboard.js';
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
|
||||
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("rb-case");
|
||||
const wordBtn = document.getElementById("rb-word");
|
||||
const regexBtn = document.getElementById("rb-regex");
|
||||
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("rb-prev");
|
||||
const nextBtn = document.getElementById("rb-next");
|
||||
const countEl = document.getElementById("rb-count");
|
||||
const resultsBar = document.getElementById("results-bar");
|
||||
|
||||
function _showResultsBar() { if (resultsBar) resultsBar.style.display = 'flex'; }
|
||||
function _hideResultsBar() { if (resultsBar) resultsBar.style.display = 'none'; }
|
||||
|
||||
function _updateToggleUI() {
|
||||
if (caseBtn) caseBtn.classList.toggle("active", state.searchCaseSensitive);
|
||||
if (wordBtn) wordBtn.classList.toggle("active", state.searchWholeWord);
|
||||
if (regexBtn) regexBtn.classList.toggle("active", state.searchRegex);
|
||||
if (filterBtn) filterBtn.classList.toggle("active", state.searchFilterVisible);
|
||||
}
|
||||
|
||||
// Toggle buttons
|
||||
if (caseBtn) 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 = state.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("rb-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();
|
||||
if (_searchResultItems.length > 0) _showResultsBar(); else _hideResultsBar();
|
||||
}
|
||||
|
||||
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: autocomplete dropdown only (search triggered on Enter or selection)
|
||||
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);
|
||||
|
||||
// Clear results when input becomes empty
|
||||
if (!hasText) {
|
||||
SearchChips.clear();
|
||||
showWelcome();
|
||||
}
|
||||
});
|
||||
|
||||
// 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.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 === "Escape") {
|
||||
AutocompleteDropdown.hide();
|
||||
e.stopPropagation();
|
||||
}
|
||||
} else {
|
||||
// Dropdown not visible: navigate search results or execute search
|
||||
if (e.key === "ArrowDown") {
|
||||
if (window.navigateSearchResults) { window.navigateSearchResults(1); e.preventDefault(); }
|
||||
} else if (e.key === "ArrowUp") {
|
||||
if (window.navigateSearchResults) { window.navigateSearchResults(-1); e.preventDefault(); }
|
||||
} else if (e.key === "Enter") {
|
||||
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();
|
||||
_hideResultsBar();
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 => {
|
||||
var isDirSearch = (!s.query || s.query.trim() === '') && s.include_paths;
|
||||
var displayQuery = isDirSearch ? (s.include_paths || 'Dossier') : escapeHtml(s.query);
|
||||
var typeIcon = isDirSearch
|
||||
? '<i data-lucide="folder-search" style="width:14px;height:14px;color:var(--accent)" class="saved-search-type-icon"></i>'
|
||||
: '<i data-lucide="search" style="width:14px;height:14px;color:var(--text-muted)" class="saved-search-type-icon"></i>';
|
||||
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${isDirSearch ? ' saved-search-dir' : ''}" data-type="${isDirSearch ? 'directory' : 'search'}">
|
||||
<div class="saved-search-query">${displayQuery}</div>
|
||||
<div class="saved-search-meta">
|
||||
<span class="saved-search-badges">${badges.join("")} ${vaultStr}</span>
|
||||
${typeIcon}
|
||||
</div>
|
||||
${pathFilters.length ? '<div class="saved-search-filters">' + pathFilters.join(" ") + '</div>' : ""}
|
||||
<button class="saved-search-delete" data-id="${s.id}" title="Supprimer">✕</button>
|
||||
</div>
|
||||
`}).join("");
|
||||
// Apply saved filter + init filter bar
|
||||
if (typeof _applySavedFilter === 'function') _applySavedFilter();
|
||||
if (typeof _initSavedFilter === 'function') _initSavedFilter();
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user