From 6d36b53b3a37c01a1a34b206d3519a05734bb738 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Thu, 28 May 2026 16:49:29 -0400 Subject: [PATCH] fix: regenerate corrupted ui.js, search.js, viewer.js from app.js --- frontend/js/search.js | 288 ++++++++------- frontend/js/ui.js | 809 +++++++++++++++++++++++++----------------- frontend/js/viewer.js | 303 +++++++++++----- 3 files changed, 870 insertions(+), 530 deletions(-) diff --git a/frontend/js/search.js b/frontend/js/search.js index 18d9c31..a202c8d 100644 --- a/frontend/js/search.js +++ b/frontend/js/search.js @@ -1,19 +1,12 @@ -/* ObsiGate — Search module (extracted from app.js) */ - +/* ObsiGate — Auto-extracted module */ import { state } from './state.js'; -import { safeCreateIcons } from './utils.js'; - -// Re-export constants used internally -const state.SEARCH_HISTORY_KEY = state.SEARCH_HISTORY_KEY; -const state.MAX_HISTORY_ENTRIES = state.MAX_HISTORY_ENTRIES; - // --------------------------------------------------------------------------- // Search History Service (localStorage, LIFO, max 50, dedup) // --------------------------------------------------------------------------- -export const SearchHistory = { +const SearchHistory = { _load() { try { - const raw = localStorage.getItem(state.SEARCH_HISTORY_KEY); + const raw = localStorage.getItem(SEARCH_HISTORY_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; @@ -21,7 +14,7 @@ export const SearchHistory = { }, _save(entries) { try { - localStorage.setItem(state.SEARCH_HISTORY_KEY, JSON.stringify(entries)); + localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(entries)); } catch {} }, getAll() { @@ -32,7 +25,7 @@ export const SearchHistory = { const q = query.trim(); let entries = this._load().filter((e) => e !== q); entries.unshift(q); - if (entries.length > state.MAX_HISTORY_ENTRIES) entries = entries.slice(0, state.MAX_HISTORY_ENTRIES); + if (entries.length > MAX_HISTORY_ENTRIES) entries = entries.slice(0, MAX_HISTORY_ENTRIES); this._save(entries); }, remove(query) { @@ -54,7 +47,7 @@ export const SearchHistory = { // --------------------------------------------------------------------------- // Query Parser — extracts operators (tag:, #, vault:, title:, path:, ext:) // --------------------------------------------------------------------------- -export const QueryParser = { +const QueryParser = { parse(raw) { const result = { tags: [], vault: null, title: null, path: null, ext: null, freeText: "" }; if (!raw) return result; @@ -129,7 +122,7 @@ export const QueryParser = { // --------------------------------------------------------------------------- // Autocomplete Dropdown Controller // --------------------------------------------------------------------------- -export const AutocompleteDropdown = { +const AutocompleteDropdown = { _dropdown: null, _historySection: null, _titlesSection: null, @@ -174,8 +167,8 @@ export const AutocompleteDropdown = { hide() { if (this._dropdown) this._dropdown.hidden = true; - state.dropdownActiveIndex = -1; - state.dropdownItems = []; + dropdownActiveIndex = -1; + dropdownItems = []; }, isVisible() { @@ -186,9 +179,9 @@ export const AutocompleteDropdown = { async populate(inputValue, cursorPos) { if (this._suppressNext) { this._suppressNext = false; return; } // Cancel previous suggestion request - if (state.suggestAbortController) { - state.suggestAbortController.abort(); - state.suggestAbortController = null; + if (suggestAbortController) { + suggestAbortController.abort(); + suggestAbortController = null; } const ctx = QueryParser.getContext(inputValue, cursorPos); @@ -235,10 +228,10 @@ export const AutocompleteDropdown = { }, async _fetchSuggestions(prefix, vault) { - state.suggestAbortController = new AbortController(); + suggestAbortController = new AbortController(); // Fetch titles try { - const titlesRes = await api(`/api/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: state.suggestAbortController.signal }); + const titlesRes = await api(`/api/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: suggestAbortController.signal }); this._renderTitles(titlesRes.suggestions || [], prefix); this._titlesSection.hidden = !(titlesRes.suggestions || []).length; if (titlesRes.suggestions?.length) this.show(); @@ -248,7 +241,7 @@ export 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: state.suggestAbortController.signal }); + const tagsRes = await api(`/api/tags/suggest?q=${encodeURIComponent(prefix)}&vault=${encodeURIComponent(vault)}&limit=5`, { signal: suggestAbortController.signal }); const items = tagsRes.suggestions || []; if (items.length > 0) { this._renderTags(items, prefix); @@ -370,30 +363,30 @@ export const AutocompleteDropdown = { }, _collectItems() { - state.dropdownItems = Array.from(this._dropdown.querySelectorAll(".search-dropdown__item")); - state.dropdownActiveIndex = -1; - state.dropdownItems.forEach((item) => item.classList.remove("active")); + dropdownItems = Array.from(this._dropdown.querySelectorAll(".search-dropdown__item")); + dropdownActiveIndex = -1; + dropdownItems.forEach((item) => item.classList.remove("active")); }, navigateDown() { - if (!this.isVisible() || state.dropdownItems.length === 0) return; - if (state.dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); - state.dropdownActiveIndex = (state.dropdownActiveIndex + 1) % state.dropdownItems.length; - state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); - state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); + 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" }); }, navigateUp() { - if (!this.isVisible() || state.dropdownItems.length === 0) return; - if (state.dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); - state.dropdownActiveIndex = state.dropdownActiveIndex <= 0 ? state.dropdownItems.length - 1 : state.dropdownActiveIndex - 1; - state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); - state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); + 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" }); }, selectActive() { - if (state.dropdownActiveIndex >= 0 && state.dropdownActiveIndex < state.dropdownItems.length) { - state.dropdownItems[state.dropdownActiveIndex].click(); + if (dropdownActiveIndex >= 0 && dropdownActiveIndex < dropdownItems.length) { + dropdownItems[dropdownActiveIndex].click(); return true; } return false; @@ -403,7 +396,7 @@ export const AutocompleteDropdown = { // --------------------------------------------------------------------------- // Search Chips Controller — renders active filter chips from parsed query // --------------------------------------------------------------------------- -export const SearchChips = { +const SearchChips = { _container: null, init() { this._container = document.getElementById("search-chips"); @@ -443,14 +436,71 @@ export const SearchChips = { const chip = el("span", { class: `search-chip search-chip--${type}` }); const label = el("span", { class: "search-chip__label" }); label.textContent = fullOperator; - const removeBtn = el("button", { class: "search-chi + const removeBtn = el("button", { class: "search-chip__remove", title: "Retirer ce filtre", type: "button" }); + removeBtn.innerHTML = ''; + removeBtn.addEventListener("click", () => { + // Remove this operator from the input + const input = document.getElementById("search-input"); + const raw = input.value; + // Remove the operator text from the query + const escaped = fullOperator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + input.value = raw.replace(new RegExp("\\s*" + escaped + "\\s*", "i"), " ").trim(); + _triggerAdvancedSearch(input.value); + }); + chip.appendChild(label); + chip.appendChild(removeBtn); + this._container.appendChild(chip); + safeCreateIcons(); + }, +}; -... [OUTPUT TRUNCATED - 3180 chars omitted out of 53180 total] ... +// --------------------------------------------------------------------------- +// Helper: trigger advanced search from input value +// --------------------------------------------------------------------------- +function _triggerAdvancedSearch(rawQuery) { + const q = (rawQuery || "").trim(); + const vault = document.getElementById("vault-filter").value; + const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; + advancedSearchOffset = 0; + if (q.length > 0 || tagFilter) { + SearchHistory.add(q); + performAdvancedSearch(q, vault, tagFilter); + } else { + SearchChips.clear(); + showWelcome(); + } +} -search(); }); - 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(); }); +// --------------------------------------------------------------------------- +// Search (enhanced with autocomplete, keyboard nav, global shortcuts) +// --------------------------------------------------------------------------- +// ── Search toggle state ── + +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; @@ -486,11 +536,11 @@ search(); }); function _research() { const q = input.value.trim(); if (q.length >= _getEffective("min_query_length", 2)) { - clearTimeout(state.searchTimeout); - state.searchTimeout = setTimeout(() => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { const vault = document.getElementById("vault-filter").value; - const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; - state.advancedSearchOffset = 0; + const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; + advancedSearchOffset = 0; performAdvancedSearch(q, vault, tagFilter); }, _getEffective("debounce_ms", 300)); } @@ -522,14 +572,14 @@ search(); }); AutocompleteDropdown.populate(input.value, input.selectionStart); // Debounced search execution - clearTimeout(state.searchTimeout); - state.searchTimeout = setTimeout( + clearTimeout(searchTimeout); + 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) { + 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(); @@ -580,10 +630,10 @@ search(); }); const q = input.value.trim(); if (q) { SearchHistory.add(q); - clearTimeout(state.searchTimeout); - state.advancedSearchOffset = 0; + clearTimeout(searchTimeout); + advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; - const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; + const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; performAdvancedSearch(q, vault, tagFilter); } e.preventDefault(); @@ -604,10 +654,10 @@ search(); }); const q = input.value.trim(); if (q) { SearchHistory.add(q); - clearTimeout(state.searchTimeout); - state.advancedSearchOffset = 0; + clearTimeout(searchTimeout); + advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; - const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; + const tagFilter = selectedTags.length > 0 ? selectedTags.join(",") : null; performAdvancedSearch(q, vault, tagFilter); } e.preventDefault(); @@ -617,9 +667,9 @@ search(); }); clearBtn.addEventListener("click", () => { input.value = ""; if (clearBtn) clearBtn.style.display = "none"; - state.searchCaseSensitive = false; - state.searchWholeWord = false; - state.searchRegex = false; + searchCaseSensitive = false; + searchWholeWord = false; + searchRegex = false; _updateToggleUI(); SearchChips.clear(); AutocompleteDropdown.hide(); @@ -655,48 +705,48 @@ function _isInputFocused() { } // --- Backward-compatible search (existing /api/search endpoint) --- -export async function performSearch(query, vaultFilter, tagFilter) { - if (state.searchAbortController) state.searchAbortController.abort(); - state.searchAbortController = new AbortController(); - const searchId = ++state.currentSearchId; +async function performSearch(query, vaultFilter, tagFilter) { + if (searchAbortController) searchAbortController.abort(); + searchAbortController = new AbortController(); + const searchId = ++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: state.searchAbortController.signal }); - if (searchId !== state.currentSearchId) return; + const data = await api(url, { signal: searchAbortController.signal }); + if (searchId !== currentSearchId) return; renderSearchResults(data, query, tagFilter); } catch (err) { if (err.name === "AbortError") return; - if (searchId !== state.currentSearchId) return; + if (searchId !== currentSearchId) return; showWelcome(); } finally { hideProgressBar(); - if (searchId === state.currentSearchId) state.searchAbortController = null; + if (searchId === currentSearchId) searchAbortController = null; } } // --- Advanced search with TF-IDF, facets, pagination --- -export async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) { - if (state.searchAbortController) state.searchAbortController.abort(); - state.searchAbortController = new AbortController(); - const searchId = ++state.currentSearchId; +async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) { + if (searchAbortController) searchAbortController.abort(); + searchAbortController = new AbortController(); + const searchId = ++currentSearchId; showLoading(); - const ofs = offset !== undefined ? offset : state.advancedSearchOffset; - const sortBy = sort || state.advancedSearchSort; - state.advancedSearchLastQuery = query; + const ofs = offset !== undefined ? offset : advancedSearchOffset; + const sortBy = sort || advancedSearchSort; + advancedSearchLastQuery = query; // Update chips from parsed query const parsed = QueryParser.parse(query); SearchChips.update(parsed); - const effectiveLimit = _getEffective("results_per_page", state.ADVANCED_SEARCH_LIMIT); + const effectiveLimit = _getEffective("results_per_page", 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 (state.searchCaseSensitive) url += "&case_sensitive=true"; - if (state.searchWholeWord) url += "&whole_word=true"; - if (state.searchRegex) url += "®ex=true"; + if (searchCaseSensitive) url += "&case_sensitive=true"; + if (searchWholeWord) url += "&whole_word=true"; + if (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())}`; @@ -705,31 +755,31 @@ export async function performAdvancedSearch(query, vaultFilter, tagFilter, offse // Search timeout — abort if server takes too long const timeoutId = setTimeout( () => { - if (state.searchAbortController) state.searchAbortController.abort(); + if (searchAbortController) searchAbortController.abort(); }, - _getEffective("search_timeout_ms", state.SEARCH_TIMEOUT_MS), + _getEffective("search_timeout_ms", SEARCH_TIMEOUT_MS), ); try { - const data = await api(url, { signal: state.searchAbortController.signal }); + const data = await api(url, { signal: searchAbortController.signal }); clearTimeout(timeoutId); - if (searchId !== state.currentSearchId) return; - state.advancedSearchTotal = data.total; - state.advancedSearchOffset = ofs; + if (searchId !== currentSearchId) return; + advancedSearchTotal = data.total; + advancedSearchOffset = ofs; renderAdvancedSearchResults(data, query, tagFilter); } catch (err) { clearTimeout(timeoutId); if (err.name === "AbortError") return; - if (searchId !== state.currentSearchId) return; + if (searchId !== currentSearchId) return; showWelcome(); } finally { hideProgressBar(); - if (searchId === state.currentSearchId) state.searchAbortController = null; + if (searchId === currentSearchId) searchAbortController = null; } } // --- Legacy search results renderer (kept for backward compat) --- -export function renderSearchResults(data, query, tagFilter) { +function renderSearchResults(data, query, tagFilter) { const area = document.getElementById("content-area"); area.innerHTML = ""; const header = buildSearchResultsHeader(data, query, tagFilter); @@ -747,7 +797,7 @@ export function renderSearchResults(data, query, tagFilter) { const titleDiv = el("div", { class: "search-result-title" }); if (query && query.trim()) { - highlightSearchText(titleDiv, r.title, query, state.searchCaseSensitive); + highlightSearchText(titleDiv, r.title, query, searchCaseSensitive); } else { titleDiv.textContent = r.title; } @@ -755,7 +805,7 @@ export 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, state.searchCaseSensitive); + highlightSearchText(snippetDiv, r.snippet, query, searchCaseSensitive); } else { snippetDiv.textContent = r.snippet || ""; } @@ -787,7 +837,7 @@ export function renderSearchResults(data, query, tagFilter) { } // --- Advanced search results renderer (facets, highlighted snippets, pagination, sort) --- -export function renderAdvancedSearchResults(data, query, tagFilter) { +function renderAdvancedSearchResults(data, query, tagFilter) { const area = document.getElementById("content-area"); area.innerHTML = ""; @@ -819,9 +869,9 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { // Active filter badges const filtersRow = el("div", { class: "search-filters-row" }); - 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(".*")])); + 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(".*")])); 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())])); @@ -830,19 +880,19 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { // Sort controls const sortDiv = el("div", { class: "search-sort" }); - const btnRelevance = el("button", { class: "search-sort__btn" + (state.advancedSearchSort === "relevance" ? " active" : ""), type: "button" }); + const btnRelevance = el("button", { class: "search-sort__btn" + (advancedSearchSort === "relevance" ? " active" : ""), type: "button" }); btnRelevance.textContent = "Pertinence"; btnRelevance.addEventListener("click", () => { - state.advancedSearchSort = "relevance"; - state.advancedSearchOffset = 0; + advancedSearchSort = "relevance"; + advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; performAdvancedSearch(query, vault, tagFilter, 0, "relevance"); }); - const btnDate = el("button", { class: "search-sort__btn" + (state.advancedSearchSort === "modified" ? " active" : ""), type: "button" }); + const btnDate = el("button", { class: "search-sort__btn" + (advancedSearchSort === "modified" ? " active" : ""), type: "button" }); btnDate.textContent = "Date"; btnDate.addEventListener("click", () => { - state.advancedSearchSort = "modified"; - state.advancedSearchOffset = 0; + advancedSearchSort = "modified"; + advancedSearchOffset = 0; const vault = document.getElementById("vault-filter").value; performAdvancedSearch(query, vault, tagFilter, 0, "modified"); }); @@ -863,9 +913,9 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { body: JSON.stringify({ query: query, vault: document.getElementById("vault-filter")?.value || "all", - case_sensitive: state.searchCaseSensitive, - whole_word: state.searchWholeWord, - regex: state.searchRegex, + case_sensitive: searchCaseSensitive, + whole_word: searchWholeWord, + regex: searchRegex, include_paths: inclEl?.value || "", exclude_paths: exclEl?.value || "", }), @@ -877,9 +927,9 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { area.appendChild(header); // Active sidebar tag chips - if (state.selectedTags.length > 0) { + if (selectedTags.length > 0) { const activeTags = el("div", { class: "search-results-active-tags" }); - state.selectedTags.forEach((tag) => { + selectedTags.forEach((tag) => { const removeBtn = el( "button", { @@ -962,7 +1012,7 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { const titleDiv = el("div", { class: "search-result-title" }); if (freeText) { - highlightSearchText(titleDiv, r.title, freeText, state.searchCaseSensitive); + highlightSearchText(titleDiv, r.title, freeText, searchCaseSensitive); } else { titleDiv.textContent = r.title; } @@ -972,7 +1022,7 @@ export 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, state.searchCaseSensitive); + highlightSearchText(snippetDiv, r.snippet, freeText, searchCaseSensitive); } else { snippetDiv.textContent = r.snippet || ""; } @@ -1010,30 +1060,30 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { area.appendChild(container); // Pagination - if (data.total > state.ADVANCED_SEARCH_LIMIT) { + if (data.total > 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 = state.advancedSearchOffset === 0; + prevBtn.disabled = advancedSearchOffset === 0; prevBtn.addEventListener("click", () => { - state.advancedSearchOffset = Math.max(0, state.advancedSearchOffset - state.ADVANCED_SEARCH_LIMIT); + advancedSearchOffset = Math.max(0, advancedSearchOffset - ADVANCED_SEARCH_LIMIT); const vault = document.getElementById("vault-filter").value; - performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); + performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset); document.getElementById("content-area").scrollTop = 0; }); const info = el("span", { class: "search-pagination__info" }); - const from = state.advancedSearchOffset + 1; - const to = Math.min(state.advancedSearchOffset + state.ADVANCED_SEARCH_LIMIT, data.total); + const from = advancedSearchOffset + 1; + const to = Math.min(advancedSearchOffset + 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 = state.advancedSearchOffset + state.ADVANCED_SEARCH_LIMIT >= data.total; + nextBtn.disabled = advancedSearchOffset + ADVANCED_SEARCH_LIMIT >= data.total; nextBtn.addEventListener("click", () => { - state.advancedSearchOffset += state.ADVANCED_SEARCH_LIMIT; + advancedSearchOffset += ADVANCED_SEARCH_LIMIT; const vault = document.getElementById("vault-filter").value; - performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); + performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset); document.getElementById("content-area").scrollTop = 0; }); @@ -1046,4 +1096,4 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { safeCreateIcons(); // Initialize result navigation (select first result) setTimeout(() => { if (window.navigateSearchResults) window.navigateSearchResults(0); }, 50); -} +} \ No newline at end of file diff --git a/frontend/js/ui.js b/frontend/js/ui.js index a1be93f..3bb2183 100644 --- a/frontend/js/ui.js +++ b/frontend/js/ui.js @@ -1,13 +1,10 @@ -/* ObsiGate — UI: theme, sidebar, context menus, tabs, toast, find-in-page */ +/* ObsiGate — Auto-extracted module */ import { state } from './state.js'; -import { openFile } from './viewer.js'; -import { safeCreateIcons } from './utils.js'; - // --------------------------------------------------------------------------- // Right Sidebar Manager // --------------------------------------------------------------------------- -export const RightSidebarManager = { +const RightSidebarManager = { init() { this.loadState(); this.initToggle(); @@ -19,11 +16,11 @@ export const RightSidebarManager = { const savedWidth = localStorage.getItem("obsigate-right-sidebar-width"); if (savedVisible !== null) { - state.rightSidebarVisible = savedVisible === "true"; + rightSidebarVisible = savedVisible === "true"; } if (savedWidth) { - state.rightSidebarWidth = parseInt(savedWidth) || 280; + rightSidebarWidth = parseInt(savedWidth) || 280; } this.applyState(); @@ -37,9 +34,9 @@ export const RightSidebarManager = { if (!sidebar) return; - if (state.rightSidebarVisible) { + if (rightSidebarVisible) { sidebar.classList.remove("hidden"); - sidebar.style.width = `${state.rightSidebarWidth}px`; + sidebar.style.width = `${rightSidebarWidth}px`; if (handle) handle.classList.remove("hidden"); if (tocBtn) { tocBtn.classList.add("active"); @@ -67,8 +64,8 @@ export const RightSidebarManager = { }, toggle() { - state.rightSidebarVisible = !state.rightSidebarVisible; - localStorage.setItem("obsigate-right-sidebar-visible", state.rightSidebarVisible); + rightSidebarVisible = !rightSidebarVisible; + localStorage.setItem("obsigate-right-sidebar-visible", rightSidebarVisible); this.applyState(); }, @@ -108,7 +105,7 @@ export const RightSidebarManager = { newWidth = Math.max(200, Math.min(400, newWidth)); sidebar.style.width = `${newWidth}px`; - state.rightSidebarWidth = newWidth; + rightSidebarWidth = newWidth; }; const onMouseUp = () => { @@ -119,7 +116,7 @@ export const RightSidebarManager = { document.body.style.cursor = ""; document.body.style.userSelect = ""; - localStorage.setItem("obsigate-right-sidebar-width", state.rightSidebarWidth); + localStorage.setItem("obsigate-right-sidebar-width", rightSidebarWidth); }; handle.addEventListener("mousedown", onMouseDown); @@ -131,12 +128,12 @@ export const RightSidebarManager = { // --------------------------------------------------------------------------- // Theme // --------------------------------------------------------------------------- -export function initTheme() { +function initTheme() { const saved = localStorage.getItem("obsigate-theme") || "dark"; applyTheme(saved); } -export function applyTheme(theme) { +function applyTheme(theme) { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("obsigate-theme", theme); @@ -161,12 +158,12 @@ export function applyTheme(theme) { } } -export function toggleTheme() { +function toggleTheme() { const current = document.documentElement.getAttribute("data-theme"); applyTheme(current === "dark" ? "light" : "dark"); } -export function initHeaderMenu() { +function initHeaderMenu() { const menuBtn = document.getElementById("header-menu-btn"); const menuDropdown = document.getElementById("header-menu-dropdown"); @@ -203,7 +200,7 @@ function closeHeaderMenu() { // --------------------------------------------------------------------------- // Custom Dropdowns // --------------------------------------------------------------------------- -export function initCustomDropdowns() { +function initCustomDropdowns() { document.querySelectorAll(".custom-dropdown").forEach((dropdown) => { const trigger = dropdown.querySelector(".custom-dropdown-trigger"); const options = dropdown.querySelectorAll(".custom-dropdown-option"); @@ -335,7 +332,7 @@ function populateCustomDropdown(dropdownId, optionsList, defaultValue) { // --------------------------------------------------------------------------- /** Display a brief toast message at the bottom of the viewport. */ -export function showToast(message, type) { +function showToast(message, type) { console.log("showToast called with:", message, type); type = type || "info"; let container = document.getElementById("toast-container"); @@ -365,7 +362,7 @@ export function showToast(message, type) { // --------------------------------------------------------------------------- // Sidebar toggle (desktop) // --------------------------------------------------------------------------- -export function initSidebarToggle() { +function initSidebarToggle() { const toggleBtn = document.getElementById("sidebar-toggle-btn"); const sidebar = document.getElementById("sidebar"); const resizeHandle = document.getElementById("sidebar-resize-handle"); @@ -391,7 +388,7 @@ export function initSidebarToggle() { // --------------------------------------------------------------------------- // Mobile sidebar // --------------------------------------------------------------------------- -export function initMobile() { +function initMobile() { const hamburger = document.getElementById("hamburger-btn"); const overlay = document.getElementById("sidebar-overlay"); const sidebar = document.getElementById("sidebar"); @@ -417,7 +414,7 @@ function closeMobileSidebar() { // --------------------------------------------------------------------------- // Resizable sidebar (horizontal) // --------------------------------------------------------------------------- -export function initSidebarResize() { +function initSidebarResize() { const handle = document.getElementById("sidebar-resize-handle"); const sidebar = document.getElementById("sidebar"); if (!handle || !sidebar) return; @@ -458,7 +455,7 @@ export function initSidebarResize() { // --------------------------------------------------------------------------- // Resizable tag section (vertical) // --------------------------------------------------------------------------- -export function initTagResize() { +function initTagResize() { const handle = document.getElementById("tag-resize-handle"); const tagSection = document.getElementById("tag-cloud-section"); if (!handle || !tagSection) return; @@ -489,11 +486,374 @@ export function initTagResize() { handle.addEventListener("mousedown", (e) => { e.preventDefault(); startY = e.clientY; - 4 + startHeight = tagSection.getBoundingClientRect().height; + document.body.classList.add("resizing-v"); + handle.classList.add("active"); + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }); +} -... [OUTPUT TRUNCATED - 30698 chars omitted out of 80698 total] ... +// --------------------------------------------------------------------------- +// Frontmatter Accent Card Builder +// --------------------------------------------------------------------------- -