diff --git a/frontend/js/search.js b/frontend/js/search.js index a202c8d..0605a8f 100644 --- a/frontend/js/search.js +++ b/frontend/js/search.js @@ -1,12 +1,12 @@ /* ObsiGate — Auto-extracted module */ -import { state } from './state.js'; +import { state } from './state.js';; // --------------------------------------------------------------------------- // Search History Service (localStorage, LIFO, max 50, dedup) // --------------------------------------------------------------------------- const SearchHistory = { _load() { try { - const raw = localStorage.getItem(SEARCH_HISTORY_KEY); + const raw = localStorage.getItem(state.SEARCH_HISTORY_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; @@ -14,7 +14,7 @@ const SearchHistory = { }, _save(entries) { try { - localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(entries)); + localStorage.setItem(state.SEARCH_HISTORY_KEY, JSON.stringify(entries)); } catch {} }, getAll() { @@ -25,7 +25,7 @@ const SearchHistory = { const q = query.trim(); let entries = this._load().filter((e) => e !== q); entries.unshift(q); - if (entries.length > MAX_HISTORY_ENTRIES) entries = entries.slice(0, MAX_HISTORY_ENTRIES); + if (entries.length > state.MAX_HISTORY_ENTRIES) entries = entries.slice(0, state.MAX_HISTORY_ENTRIES); this._save(entries); }, remove(query) { @@ -167,8 +167,8 @@ const AutocompleteDropdown = { hide() { if (this._dropdown) this._dropdown.hidden = true; - dropdownActiveIndex = -1; - dropdownItems = []; + state.dropdownActiveIndex = -1; + state.dropdownItems = []; }, isVisible() { @@ -179,9 +179,9 @@ const AutocompleteDropdown = { async populate(inputValue, cursorPos) { if (this._suppressNext) { this._suppressNext = false; return; } // Cancel previous suggestion request - if (suggestAbortController) { - suggestAbortController.abort(); - suggestAbortController = null; + if (state.suggestAbortController) { + state.suggestAbortController.abort(); + state.suggestAbortController = null; } const ctx = QueryParser.getContext(inputValue, cursorPos); @@ -228,10 +228,10 @@ const AutocompleteDropdown = { }, async _fetchSuggestions(prefix, vault) { - suggestAbortController = new AbortController(); + state.suggestAbortController = new AbortController(); // Fetch titles try { - const titlesRes = await api(`/api/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: suggestAbortController.signal }); + const titlesRes = await api(`/api/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: state.suggestAbortController.signal }); this._renderTitles(titlesRes.suggestions || [], prefix); this._titlesSection.hidden = !(titlesRes.suggestions || []).length; if (titlesRes.suggestions?.length) this.show(); @@ -241,7 +241,7 @@ const AutocompleteDropdown = { } // Fetch tags — keep section always visible to confirm it works try { - const tagsRes = await api(`/api/tags/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: suggestAbortController.signal }); + const tagsRes = await api(`/api/tags/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: state.suggestAbortController.signal }); const items = tagsRes.suggestions || []; if (items.length > 0) { this._renderTags(items, prefix); @@ -363,30 +363,30 @@ const AutocompleteDropdown = { }, _collectItems() { - dropdownItems = Array.from(this._dropdown.querySelectorAll(".search-dropdown__item")); - dropdownActiveIndex = -1; - dropdownItems.forEach((item) => item.classList.remove("active")); + state.dropdownItems = Array.from(this._dropdown.querySelectorAll(".search-dropdown__item")); + state.dropdownActiveIndex = -1; + state.dropdownItems.forEach((item) => item.classList.remove("active")); }, navigateDown() { - if (!this.isVisible() || dropdownItems.length === 0) return; - if (dropdownActiveIndex >= 0) dropdownItems[dropdownActiveIndex].classList.remove("active"); - dropdownActiveIndex = (dropdownActiveIndex + 1) % dropdownItems.length; - dropdownItems[dropdownActiveIndex].classList.add("active"); - dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" }); + if (!this.isVisible() || state.dropdownItems.length === 0) return; + if (dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); + state.dropdownActiveIndex = (dropdownActiveIndex + 1) % state.dropdownItems.length; + state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); + state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); }, navigateUp() { - if (!this.isVisible() || dropdownItems.length === 0) return; - if (dropdownActiveIndex >= 0) dropdownItems[dropdownActiveIndex].classList.remove("active"); - dropdownActiveIndex = dropdownActiveIndex <= 0 ? dropdownItems.length - 1 : dropdownActiveIndex - 1; - dropdownItems[dropdownActiveIndex].classList.add("active"); - dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" }); + if (!this.isVisible() || state.dropdownItems.length === 0) return; + if (dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); + state.dropdownActiveIndex = dropdownActiveIndex <= 0 ? state.dropdownItems.length - 1 : dropdownActiveIndex - 1; + state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); + state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); }, selectActive() { - if (dropdownActiveIndex >= 0 && dropdownActiveIndex < dropdownItems.length) { - dropdownItems[dropdownActiveIndex].click(); + if (dropdownActiveIndex >= 0 && dropdownActiveIndex < state.dropdownItems.length) { + state.dropdownItems[state.dropdownActiveIndex].click(); return true; } return false; @@ -460,8 +460,8 @@ const SearchChips = { function _triggerAdvancedSearch(rawQuery) { const q = (rawQuery || "").trim(); const vault = document.getElementById("vault-filter").value; - const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; - advancedSearchOffset = 0; + const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; + state.advancedSearchOffset = 0; if (q.length > 0 || tagFilter) { SearchHistory.add(q); performAdvancedSearch(q, vault, tagFilter); @@ -490,17 +490,17 @@ function initSearch() { 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); + 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", () => { 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(); }); + 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; @@ -536,11 +536,11 @@ function initSearch() { function _research() { const q = input.value.trim(); if (q.length >= _getEffective("min_query_length", 2)) { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(() => { + clearTimeout(state.searchTimeout); + state.searchTimeout = setTimeout(() => { const vault = document.getElementById("vault-filter").value; - const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; - advancedSearchOffset = 0; + const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; + state.advancedSearchOffset = 0; performAdvancedSearch(q, vault, tagFilter); }, _getEffective("debounce_ms", 300)); } @@ -572,14 +572,14 @@ function initSearch() { AutocompleteDropdown.populate(input.value, input.selectionStart); // Debounced search execution - clearTimeout(searchTimeout); - searchTimeout = setTimeout( + clearTimeout(state.searchTimeout); + state.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) { + 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(); @@ -630,10 +630,10 @@ function initSearch() { const q = input.value.trim(); if (q) { SearchHistory.add(q); - clearTimeout(searchTimeout); - advancedSearchOffset = 0; + clearTimeout(state.searchTimeout); + state.advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; - const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; + const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; performAdvancedSearch(q, vault, tagFilter); } e.preventDefault(); @@ -654,10 +654,10 @@ function initSearch() { const q = input.value.trim(); if (q) { SearchHistory.add(q); - clearTimeout(searchTimeout); - advancedSearchOffset = 0; + clearTimeout(state.searchTimeout); + state.advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; - const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; + const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; performAdvancedSearch(q, vault, tagFilter); } e.preventDefault(); @@ -667,9 +667,9 @@ function initSearch() { clearBtn.addEventListener("click", () => { input.value = ""; if (clearBtn) clearBtn.style.display = "none"; - searchCaseSensitive = false; - searchWholeWord = false; - searchRegex = false; + state.searchCaseSensitive = false; + state.searchWholeWord = false; + state.searchRegex = false; _updateToggleUI(); SearchChips.clear(); AutocompleteDropdown.hide(); @@ -706,47 +706,47 @@ function _isInputFocused() { // --- Backward-compatible search (existing /api/search endpoint) --- async function performSearch(query, vaultFilter, tagFilter) { - if (searchAbortController) searchAbortController.abort(); - searchAbortController = new AbortController(); - const searchId = ++currentSearchId; + if (state.searchAbortController) state.searchAbortController.abort(); + state.searchAbortController = new AbortController(); + const searchId = ++state.currentSearchId; showLoading(); let url = `/api/search?q=${encodeURIComponent(query)}&vault=${encodeURIComponent(vaultFilter)}`; if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`; try { - const data = await api(url, { signal: searchAbortController.signal }); - if (searchId !== currentSearchId) return; + const data = await api(url, { signal: state.searchAbortController.signal }); + if (searchId !== state.currentSearchId) return; renderSearchResults(data, query, tagFilter); } catch (err) { if (err.name === "AbortError") return; - if (searchId !== currentSearchId) return; + if (searchId !== state.currentSearchId) return; showWelcome(); } finally { hideProgressBar(); - if (searchId === currentSearchId) searchAbortController = null; + if (searchId === state.currentSearchId) state.searchAbortController = null; } } // --- Advanced search with TF-IDF, facets, pagination --- async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) { - if (searchAbortController) searchAbortController.abort(); - searchAbortController = new AbortController(); - const searchId = ++currentSearchId; + if (state.searchAbortController) state.searchAbortController.abort(); + state.searchAbortController = new AbortController(); + const searchId = ++state.currentSearchId; showLoading(); - const ofs = offset !== undefined ? offset : advancedSearchOffset; - const sortBy = sort || advancedSearchSort; - advancedSearchLastQuery = query; + const ofs = offset !== undefined ? offset : state.advancedSearchOffset; + const sortBy = sort || state.advancedSearchSort; + state.advancedSearchLastQuery = query; // Update chips from parsed query const parsed = QueryParser.parse(query); SearchChips.update(parsed); - const effectiveLimit = _getEffective("results_per_page", ADVANCED_SEARCH_LIMIT); + const effectiveLimit = _getEffective("results_per_page", state.ADVANCED_SEARCH_LIMIT); let url = `/api/search/advanced?q=${encodeURIComponent(query)}&vault=${encodeURIComponent(vaultFilter)}&limit=${effectiveLimit}&offset=${ofs}&sort=${sortBy}`; if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`; - if (searchCaseSensitive) url += "&case_sensitive=true"; - if (searchWholeWord) url += "&whole_word=true"; - if (searchRegex) url += "®ex=true"; + if (state.searchCaseSensitive) url += "&case_sensitive=true"; + if (state.searchWholeWord) url += "&whole_word=true"; + if (state.searchRegex) url += "®ex=true"; const includeEl = document.getElementById("search-include-input"); const excludeEl = document.getElementById("search-exclude-input"); if (includeEl?.value.trim()) url += `&include_paths=${encodeURIComponent(includeEl.value.trim())}`; @@ -755,26 +755,26 @@ async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort // Search timeout — abort if server takes too long const timeoutId = setTimeout( () => { - if (searchAbortController) searchAbortController.abort(); + if (state.searchAbortController) state.searchAbortController.abort(); }, - _getEffective("search_timeout_ms", SEARCH_TIMEOUT_MS), + _getEffective("search_timeout_ms", state.SEARCH_TIMEOUT_MS), ); try { - const data = await api(url, { signal: searchAbortController.signal }); + const data = await api(url, { signal: state.searchAbortController.signal }); clearTimeout(timeoutId); - if (searchId !== currentSearchId) return; - advancedSearchTotal = data.total; - advancedSearchOffset = ofs; + if (searchId !== state.currentSearchId) return; + state.advancedSearchTotal = data.total; + state.advancedSearchOffset = ofs; renderAdvancedSearchResults(data, query, tagFilter); } catch (err) { clearTimeout(timeoutId); if (err.name === "AbortError") return; - if (searchId !== currentSearchId) return; + if (searchId !== state.currentSearchId) return; showWelcome(); } finally { hideProgressBar(); - if (searchId === currentSearchId) searchAbortController = null; + if (searchId === state.currentSearchId) state.searchAbortController = null; } } @@ -797,7 +797,7 @@ function renderSearchResults(data, query, tagFilter) { const titleDiv = el("div", { class: "search-result-title" }); if (query && query.trim()) { - highlightSearchText(titleDiv, r.title, query, searchCaseSensitive); + highlightSearchText(titleDiv, r.title, query, state.searchCaseSensitive); } else { titleDiv.textContent = r.title; } @@ -805,7 +805,7 @@ function renderSearchResults(data, query, tagFilter) { if (r.snippet && r.snippet.includes("")) { snippetDiv.innerHTML = r.snippet; } else if (query && query.trim() && r.snippet) { - highlightSearchText(snippetDiv, r.snippet, query, searchCaseSensitive); + highlightSearchText(snippetDiv, r.snippet, query, state.searchCaseSensitive); } else { snippetDiv.textContent = r.snippet || ""; } @@ -869,9 +869,9 @@ function renderAdvancedSearchResults(data, query, tagFilter) { // Active filter badges const filtersRow = el("div", { class: "search-filters-row" }); - if (searchCaseSensitive) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode("Aa")])); - if (searchWholeWord) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode("wd")])); - if (searchRegex) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode(".*")])); + if (state.searchCaseSensitive) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode("Aa")])); + if (state.searchWholeWord) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode("wd")])); + if (state.searchRegex) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode(".*")])); const inclEl = document.getElementById("search-include-input"); const exclEl = document.getElementById("search-exclude-input"); if (inclEl?.value.trim()) filtersRow.appendChild(el("span", { class: "search-filter-badge path" }, [document.createTextNode("incl: " + inclEl.value.trim())])); @@ -880,19 +880,19 @@ function renderAdvancedSearchResults(data, query, tagFilter) { // Sort controls const sortDiv = el("div", { class: "search-sort" }); - const btnRelevance = el("button", { class: "search-sort__btn" + (advancedSearchSort === "relevance" ? " active" : ""), type: "button" }); + const btnRelevance = el("button", { class: "search-sort__btn" + (state.advancedSearchSort === "relevance" ? " active" : ""), type: "button" }); btnRelevance.textContent = "Pertinence"; btnRelevance.addEventListener("click", () => { - advancedSearchSort = "relevance"; - advancedSearchOffset = 0; + state.advancedSearchSort = "relevance"; + state.advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; performAdvancedSearch(query, vault, tagFilter, 0, "relevance"); }); - const btnDate = el("button", { class: "search-sort__btn" + (advancedSearchSort === "modified" ? " active" : ""), type: "button" }); + const btnDate = el("button", { class: "search-sort__btn" + (state.advancedSearchSort === "modified" ? " active" : ""), type: "button" }); btnDate.textContent = "Date"; btnDate.addEventListener("click", () => { - advancedSearchSort = "modified"; - advancedSearchOffset = 0; + state.advancedSearchSort = "modified"; + state.advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; performAdvancedSearch(query, vault, tagFilter, 0, "modified"); }); @@ -913,9 +913,9 @@ function renderAdvancedSearchResults(data, query, tagFilter) { body: JSON.stringify({ query: query, vault: document.getElementById("vault-filter")?.value || "all", - case_sensitive: searchCaseSensitive, - whole_word: searchWholeWord, - regex: searchRegex, + case_sensitive: state.searchCaseSensitive, + whole_word: state.searchWholeWord, + regex: state.searchRegex, include_paths: inclEl?.value || "", exclude_paths: exclEl?.value || "", }), @@ -927,9 +927,9 @@ function renderAdvancedSearchResults(data, query, tagFilter) { area.appendChild(header); // Active sidebar tag chips - if (selectedTags.length > 0) { + if (state.selectedTags.length > 0) { const activeTags = el("div", { class: "search-results-active-tags" }); - selectedTags.forEach((tag) => { + state.selectedTags.forEach((tag) => { const removeBtn = el( "button", { @@ -1012,7 +1012,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) { const titleDiv = el("div", { class: "search-result-title" }); if (freeText) { - highlightSearchText(titleDiv, r.title, freeText, searchCaseSensitive); + highlightSearchText(titleDiv, r.title, freeText, state.searchCaseSensitive); } else { titleDiv.textContent = r.title; } @@ -1022,7 +1022,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) { if (r.snippet && r.snippet.includes("")) { snippetDiv.innerHTML = r.snippet; } else if (freeText && r.snippet) { - highlightSearchText(snippetDiv, r.snippet, freeText, searchCaseSensitive); + highlightSearchText(snippetDiv, r.snippet, freeText, state.searchCaseSensitive); } else { snippetDiv.textContent = r.snippet || ""; } @@ -1060,30 +1060,30 @@ function renderAdvancedSearchResults(data, query, tagFilter) { area.appendChild(container); // Pagination - if (data.total > ADVANCED_SEARCH_LIMIT) { + if (data.total > state.ADVANCED_SEARCH_LIMIT) { const paginationDiv = el("div", { class: "search-pagination" }); const prevBtn = el("button", { class: "search-pagination__btn", type: "button" }); prevBtn.textContent = "← Précédent"; - prevBtn.disabled = advancedSearchOffset === 0; + prevBtn.disabled = state.advancedSearchOffset === 0; prevBtn.addEventListener("click", () => { - advancedSearchOffset = Math.max(0, advancedSearchOffset - ADVANCED_SEARCH_LIMIT); + state.advancedSearchOffset = Math.max(0, advancedSearchOffset - state.ADVANCED_SEARCH_LIMIT); const vault = document.getElementById("vault-filter").value; - performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset); + performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); document.getElementById("content-area").scrollTop = 0; }); const info = el("span", { class: "search-pagination__info" }); const from = advancedSearchOffset + 1; - const to = Math.min(advancedSearchOffset + ADVANCED_SEARCH_LIMIT, data.total); + const to = Math.min(advancedSearchOffset + state.ADVANCED_SEARCH_LIMIT, data.total); info.textContent = `${from}–${to} sur ${data.total}`; const nextBtn = el("button", { class: "search-pagination__btn", type: "button" }); nextBtn.textContent = "Suivant →"; nextBtn.disabled = advancedSearchOffset + ADVANCED_SEARCH_LIMIT >= data.total; nextBtn.addEventListener("click", () => { - advancedSearchOffset += ADVANCED_SEARCH_LIMIT; + advancedSearchOffset += state.ADVANCED_SEARCH_LIMIT; const vault = document.getElementById("vault-filter").value; - performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset); + performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); document.getElementById("content-area").scrollTop = 0; }); diff --git a/frontend/js/ui.js b/frontend/js/ui.js index 3bb2183..ea16a20 100644 --- a/frontend/js/ui.js +++ b/frontend/js/ui.js @@ -1,10 +1,10 @@ /* ObsiGate — Auto-extracted module */ -import { state } from './state.js'; +import { state } from './state.js';; // --------------------------------------------------------------------------- // Right Sidebar Manager // --------------------------------------------------------------------------- -const RightSidebarManager = { +export const RightSidebarManager = { init() { this.loadState(); this.initToggle(); @@ -16,11 +16,11 @@ const RightSidebarManager = { const savedWidth = localStorage.getItem("obsigate-right-sidebar-width"); if (savedVisible !== null) { - rightSidebarVisible = savedVisible === "true"; + state.rightSidebarVisible = savedVisible === "true"; } if (savedWidth) { - rightSidebarWidth = parseInt(savedWidth) || 280; + state.rightSidebarWidth = parseInt(savedWidth) || 280; } this.applyState(); @@ -34,9 +34,9 @@ const RightSidebarManager = { if (!sidebar) return; - if (rightSidebarVisible) { + if (state.rightSidebarVisible) { sidebar.classList.remove("hidden"); - sidebar.style.width = `${rightSidebarWidth}px`; + sidebar.style.width = `${state.rightSidebarWidth}px`; if (handle) handle.classList.remove("hidden"); if (tocBtn) { tocBtn.classList.add("active"); @@ -64,8 +64,8 @@ const RightSidebarManager = { }, toggle() { - rightSidebarVisible = !rightSidebarVisible; - localStorage.setItem("obsigate-right-sidebar-visible", rightSidebarVisible); + state.rightSidebarVisible = !state.rightSidebarVisible; + localStorage.setItem("obsigate-right-sidebar-visible", state.rightSidebarVisible); this.applyState(); }, @@ -105,7 +105,7 @@ const RightSidebarManager = { newWidth = Math.max(200, Math.min(400, newWidth)); sidebar.style.width = `${newWidth}px`; - rightSidebarWidth = newWidth; + state.rightSidebarWidth = newWidth; }; const onMouseUp = () => { @@ -116,7 +116,7 @@ const RightSidebarManager = { document.body.style.cursor = ""; document.body.style.userSelect = ""; - localStorage.setItem("obsigate-right-sidebar-width", rightSidebarWidth); + localStorage.setItem("obsigate-right-sidebar-width", state.rightSidebarWidth); }; handle.addEventListener("mousedown", onMouseDown); @@ -741,12 +741,12 @@ function buildFrontmatterCard(frontmatter) { const nextPath = result.new_path; await refreshSidebarTreePreservingState(); - if (type === 'file' && currentVault === vault && currentPath === path) { + if (type === 'file' && state.currentVault === vault && state.currentPath === path) { await openFile(vault, nextPath); - } else if (type === 'directory' && currentVault === vault && currentPath && (currentPath === path || currentPath.startsWith(`${path}/`))) { - const suffix = currentPath === path ? '' : currentPath.slice(path.length); - currentPath = `${nextPath}${suffix}`; - await focusPathInSidebar(vault, currentPath, { alignToTop: false }); + } else if (type === 'directory' && state.currentVault === vault && currentPath && (state.currentPath === path || state.currentPath.startsWith(`${path}/`))) { + const suffix = state.currentPath === path ? '' : state.currentPath.slice(path.length); + state.currentPath = `${nextPath}${suffix}`; + await focusPathInSidebar(vault, state.currentPath, { alignToTop: false }); } showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success'); @@ -830,7 +830,7 @@ function buildFrontmatterCard(frontmatter) { this._closeModal(overlay); await refreshSidebarTreePreservingState(); - if (currentVault === vault && currentPath && currentPath.startsWith(path)) { + if (state.currentVault === vault && currentPath && state.currentPath.startsWith(path)) { showWelcome(); } } catch (err) { @@ -892,7 +892,7 @@ function buildFrontmatterCard(frontmatter) { this._closeModal(overlay); await refreshSidebarTreePreservingState(); - if (currentVault === vault && currentPath === path) { + if (state.currentVault === vault && state.currentPath === path) { showWelcome(); } } catch (err) { diff --git a/frontend/js/viewer.js b/frontend/js/viewer.js index b300ad5..8e19461 100644 --- a/frontend/js/viewer.js +++ b/frontend/js/viewer.js @@ -1,5 +1,5 @@ /* ObsiGate — Auto-extracted module */ -import { state } from './state.js'; +import { state } from './state.js';; // --------------------------------------------------------------------------- // Outline/TOC Manager // --------------------------------------------------------------------------- @@ -114,7 +114,7 @@ const OutlineManager = { outlineList.appendChild(item); }); - headingsCache = headings; + state.headingsCache = headings; }, /** @@ -144,9 +144,9 @@ const OutlineManager = { * Set active heading in outline */ setActiveHeading(headingId) { - if (activeHeadingId === headingId) return; + if (state.activeHeadingId === headingId) return; - activeHeadingId = headingId; + state.activeHeadingId = headingId; const items = document.querySelectorAll(".outline-item"); items.forEach((item) => { @@ -178,8 +178,8 @@ const OutlineManager = { destroy() { ScrollSpyManager.destroy(); ReadingProgressManager.destroy(); - headingsCache = []; - activeHeadingId = null; + state.headingsCache = []; + state.activeHeadingId = null; }, }; @@ -309,10 +309,10 @@ const ReadingProgressManager = { // File viewer // --------------------------------------------------------------------------- async function openFile(vaultName, filePath) { - currentVault = vaultName; - currentPath = filePath; - showingSource = false; - cachedRawSource = null; + state.currentVault = vaultName; + state.currentPath = filePath; + state.showingSource = false; + state.cachedRawSource = null; // Highlight active syncActiveFileTreeItem(vaultName, filePath); @@ -435,12 +435,12 @@ function renderFile(data) { copyBtn.addEventListener("click", async () => { try { // Fetch raw content if not already cached - if (!cachedRawSource) { + if (!state.cachedRawSource) { const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`; const rawData = await api(rawUrl); - cachedRawSource = rawData.raw; + state.cachedRawSource = rawData.raw; } - await navigator.clipboard.writeText(cachedRawSource); + await navigator.clipboard.writeText(state.cachedRawSource); copyBtn.lastChild.textContent = "Copié !"; setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500); } catch (err) { @@ -541,15 +541,15 @@ function renderFile(data) { const raw = document.getElementById("file-raw-content"); if (!rendered || !raw) return; - showingSource = !showingSource; - if (showingSource) { + state.showingSource = !state.showingSource; + if (state.showingSource) { sourceBtn.classList.add("active"); - if (!cachedRawSource) { + if (!state.cachedRawSource) { const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`; const rawData = await api(rawUrl); - cachedRawSource = rawData.raw; + state.cachedRawSource = rawData.raw; } - raw.textContent = cachedRawSource; + raw.textContent = state.cachedRawSource; rendered.style.display = "none"; raw.style.display = "block"; } else { @@ -725,7 +725,7 @@ function attachTreeItemLongPress(itemEl, getMenuData) { } function getVaultIcon(vaultName, size = 16) { - const v = allVaults.find((val) => val.name === vaultName); + const v = state.allVaults.find((val) => val.name === vaultName); const type = v ? v.type : "VAULT"; if (type === "DIR") { @@ -948,10 +948,10 @@ function showWelcome() { DashboardConflictsWidget.load(); } if (typeof DashboardRecentWidget !== "undefined") { - DashboardRecentWidget.load(selectedContextVault); + DashboardRecentWidget.load(state.selectedContextVault); } if (typeof DashboardBookmarkWidget !== "undefined") { - DashboardBookmarkWidget.load(selectedContextVault); + DashboardBookmarkWidget.load(state.selectedContextVault); } if (typeof DashboardSharedWidget !== "undefined") { DashboardSharedWidget.load(); @@ -1002,9 +1002,9 @@ async function loadSavedSearches() { // 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; + 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"); @@ -1019,8 +1019,8 @@ async function loadSavedSearches() { AutocompleteDropdown._suppressNext = true; const vault = s.vault || "all"; if (input) { input.dispatchEvent(new Event("input")); } - clearTimeout(searchTimeout); - advancedSearchOffset = 0; + clearTimeout(state.searchTimeout); + state.advancedSearchOffset = 0; performAdvancedSearch(s.query, vault, null); }); }); @@ -1059,10 +1059,10 @@ function goHome() { document.querySelectorAll(".tree-item.active").forEach((el) => el.classList.remove("active")); - currentVault = null; - currentPath = null; - showingSource = false; - cachedRawSource = null; + state.currentVault = null; + state.currentPath = null; + state.showingSource = false; + state.cachedRawSource = null; closeMobileSidebar(); showWelcome(); @@ -1216,15 +1216,15 @@ const IndexUpdateManager = (() => { // Toast removed: silent auto-indexing — no notification needed // Refresh sidebar and tags if affected vault matches current context - const affectsCurrentVault = selectedContextVault === "all" || (data.vaults || []).includes(selectedContextVault); + const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(state.selectedContextVault); if (affectsCurrentVault) { try { await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]); // Refresh current file if it was updated - if (currentVault && currentPath) { - const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === currentPath); + if (currentVault && state.currentPath) { + const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath); if (changed) { - openFile(currentVault, currentPath); + openFile(state.currentVault, state.currentPath); } } } catch (err) { @@ -1233,7 +1233,7 @@ const IndexUpdateManager = (() => { } // Refresh recent tab if it is active - if (activeSidebarTab === "recent") { + if (state.activeSidebarTab === "recent") { const vaultFilter = document.getElementById("recent-vault-filter"); loadRecentFiles(vaultFilter ? vaultFilter.value || null : null); }