fix: rebuild ui.js, search.js, viewer.js from app.js with shell script (no truncation)
CI / lint (push) Successful in 12s
CI / security (push) Successful in 8s
CI / test (push) Successful in 16s
CI / build (push) Successful in 3s

This commit is contained in:
2026-05-28 16:54:15 -04:00
parent 97d14d867a
commit 7c927ca54e
3 changed files with 269 additions and 86 deletions
+59 -51
View File
@@ -1,12 +1,13 @@
/* ObsiGate — Auto-extracted module */ /* ObsiGate — Search module */
import { state } from './state.js';; import { state } from './state.js';
import { safeCreateIcons } from './utils.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Search History Service (localStorage, LIFO, max 50, dedup) // Search History Service (localStorage, LIFO, max 50, dedup)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const SearchHistory = { const SearchHistory = {
_load() { _load() {
try { try {
const raw = localStorage.getItem(state.SEARCH_HISTORY_KEY); const raw = localStorage.getItem(SEARCH_HISTORY_KEY);
return raw ? JSON.parse(raw) : []; return raw ? JSON.parse(raw) : [];
} catch { } catch {
return []; return [];
@@ -14,7 +15,7 @@ const SearchHistory = {
}, },
_save(entries) { _save(entries) {
try { try {
localStorage.setItem(state.SEARCH_HISTORY_KEY, JSON.stringify(entries)); localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(entries));
} catch {} } catch {}
}, },
getAll() { getAll() {
@@ -25,7 +26,7 @@ const SearchHistory = {
const q = query.trim(); const q = query.trim();
let entries = this._load().filter((e) => e !== q); let entries = this._load().filter((e) => e !== q);
entries.unshift(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); this._save(entries);
}, },
remove(query) { remove(query) {
@@ -44,6 +45,7 @@ const SearchHistory = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Query Parser — extracts operators (tag:, #, vault:, title:, path:, ext:) // Query Parser — extracts operators (tag:, #, vault:, title:, path:, ext:)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -119,6 +121,7 @@ const QueryParser = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Autocomplete Dropdown Controller // Autocomplete Dropdown Controller
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -179,7 +182,7 @@ const AutocompleteDropdown = {
async populate(inputValue, cursorPos) { async populate(inputValue, cursorPos) {
if (this._suppressNext) { this._suppressNext = false; return; } if (this._suppressNext) { this._suppressNext = false; return; }
// Cancel previous suggestion request // Cancel previous suggestion request
if (state.suggestAbortController) { if (suggestAbortController) {
state.suggestAbortController.abort(); state.suggestAbortController.abort();
state.suggestAbortController = null; state.suggestAbortController = null;
} }
@@ -370,29 +373,30 @@ const AutocompleteDropdown = {
navigateDown() { navigateDown() {
if (!this.isVisible() || state.dropdownItems.length === 0) return; if (!this.isVisible() || state.dropdownItems.length === 0) return;
if (dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); if (dropdownActiveIndex >= 0) dropdownItems[dropdownActiveIndex].classList.remove("active");
state.dropdownActiveIndex = (dropdownActiveIndex + 1) % state.dropdownItems.length; state.dropdownActiveIndex = (dropdownActiveIndex + 1) % state.dropdownItems.length;
state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); dropdownItems[dropdownActiveIndex].classList.add("active");
state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" });
}, },
navigateUp() { navigateUp() {
if (!this.isVisible() || state.dropdownItems.length === 0) return; if (!this.isVisible() || state.dropdownItems.length === 0) return;
if (dropdownActiveIndex >= 0) state.dropdownItems[state.dropdownActiveIndex].classList.remove("active"); if (dropdownActiveIndex >= 0) dropdownItems[dropdownActiveIndex].classList.remove("active");
state.dropdownActiveIndex = dropdownActiveIndex <= 0 ? state.dropdownItems.length - 1 : dropdownActiveIndex - 1; state.dropdownActiveIndex = dropdownActiveIndex <= 0 ? state.dropdownItems.length - 1 : dropdownActiveIndex - 1;
state.dropdownItems[state.dropdownActiveIndex].classList.add("active"); dropdownItems[dropdownActiveIndex].classList.add("active");
state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" }); dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" });
}, },
selectActive() { selectActive() {
if (dropdownActiveIndex >= 0 && dropdownActiveIndex < state.dropdownItems.length) { if (dropdownActiveIndex >= 0 && dropdownActiveIndex < state.dropdownItems.length) {
state.dropdownItems[state.dropdownActiveIndex].click(); dropdownItems[dropdownActiveIndex].click();
return true; return true;
} }
return false; return false;
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Search Chips Controller — renders active filter chips from parsed query // Search Chips Controller — renders active filter chips from parsed query
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -454,6 +458,7 @@ const SearchChips = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helper: trigger advanced search from input value // Helper: trigger advanced search from input value
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -471,6 +476,7 @@ function _triggerAdvancedSearch(rawQuery) {
} }
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Search (enhanced with autocomplete, keyboard nav, global shortcuts) // Search (enhanced with autocomplete, keyboard nav, global shortcuts)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -490,10 +496,10 @@ function initSearch() {
const countEl = document.getElementById("search-match-count"); const countEl = document.getElementById("search-match-count");
function _updateToggleUI() { function _updateToggleUI() {
caseBtn.classList.toggle("active", state.searchCaseSensitive); caseBtn.classList.toggle("active", searchCaseSensitive);
wordBtn.classList.toggle("active", state.searchWholeWord); wordBtn.classList.toggle("active", searchWholeWord);
regexBtn.classList.toggle("active", state.searchRegex); regexBtn.classList.toggle("active", searchRegex);
filterBtn.classList.toggle("active", state.searchFilterVisible); filterBtn.classList.toggle("active", searchFilterVisible);
} }
// Toggle buttons // Toggle buttons
@@ -536,7 +542,7 @@ function initSearch() {
function _research() { function _research() {
const q = input.value.trim(); const q = input.value.trim();
if (q.length >= _getEffective("min_query_length", 2)) { if (q.length >= _getEffective("min_query_length", 2)) {
clearTimeout(state.searchTimeout); clearTimeout(searchTimeout);
state.searchTimeout = setTimeout(() => { state.searchTimeout = setTimeout(() => {
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
@@ -572,14 +578,14 @@ function initSearch() {
AutocompleteDropdown.populate(input.value, input.selectionStart); AutocompleteDropdown.populate(input.value, input.selectionStart);
// Debounced search execution // Debounced search execution
clearTimeout(state.searchTimeout); clearTimeout(searchTimeout);
state.searchTimeout = setTimeout( state.searchTimeout = setTimeout(
() => { () => {
const q = input.value.trim(); const q = input.value.trim();
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
state.advancedSearchOffset = 0; state.advancedSearchOffset = 0;
if (q.length >= _getEffective("min_query_length", state.MIN_SEARCH_LENGTH) || tagFilter) { if (q.length >= _getEffective("min_query_length", MIN_SEARCH_LENGTH) || tagFilter) {
performAdvancedSearch(q, vault, tagFilter); performAdvancedSearch(q, vault, tagFilter);
} else if (q.length === 0) { } else if (q.length === 0) {
SearchChips.clear(); SearchChips.clear();
@@ -630,7 +636,7 @@ function initSearch() {
const q = input.value.trim(); const q = input.value.trim();
if (q) { if (q) {
SearchHistory.add(q); SearchHistory.add(q);
clearTimeout(state.searchTimeout); clearTimeout(searchTimeout);
state.advancedSearchOffset = 0; state.advancedSearchOffset = 0;
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
@@ -654,7 +660,7 @@ function initSearch() {
const q = input.value.trim(); const q = input.value.trim();
if (q) { if (q) {
SearchHistory.add(q); SearchHistory.add(q);
clearTimeout(state.searchTimeout); clearTimeout(searchTimeout);
state.advancedSearchOffset = 0; state.advancedSearchOffset = 0;
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null; const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
@@ -706,7 +712,7 @@ function _isInputFocused() {
// --- Backward-compatible search (existing /api/search endpoint) --- // --- Backward-compatible search (existing /api/search endpoint) ---
async function performSearch(query, vaultFilter, tagFilter) { async function performSearch(query, vaultFilter, tagFilter) {
if (state.searchAbortController) state.searchAbortController.abort(); if (searchAbortController) state.searchAbortController.abort();
state.searchAbortController = new AbortController(); state.searchAbortController = new AbortController();
const searchId = ++state.currentSearchId; const searchId = ++state.currentSearchId;
showLoading(); showLoading();
@@ -714,21 +720,21 @@ async function performSearch(query, vaultFilter, tagFilter) {
if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`; if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`;
try { try {
const data = await api(url, { signal: state.searchAbortController.signal }); const data = await api(url, { signal: state.searchAbortController.signal });
if (searchId !== state.currentSearchId) return; if (searchId !== currentSearchId) return;
renderSearchResults(data, query, tagFilter); renderSearchResults(data, query, tagFilter);
} catch (err) { } catch (err) {
if (err.name === "AbortError") return; if (err.name === "AbortError") return;
if (searchId !== state.currentSearchId) return; if (searchId !== currentSearchId) return;
showWelcome(); showWelcome();
} finally { } finally {
hideProgressBar(); hideProgressBar();
if (searchId === state.currentSearchId) state.searchAbortController = null; if (searchId === currentSearchId) state.searchAbortController = null;
} }
} }
// --- Advanced search with TF-IDF, facets, pagination --- // --- Advanced search with TF-IDF, facets, pagination ---
async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) { async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) {
if (state.searchAbortController) state.searchAbortController.abort(); if (searchAbortController) state.searchAbortController.abort();
state.searchAbortController = new AbortController(); state.searchAbortController = new AbortController();
const searchId = ++state.currentSearchId; const searchId = ++state.currentSearchId;
showLoading(); showLoading();
@@ -741,12 +747,12 @@ async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort
const parsed = QueryParser.parse(query); const parsed = QueryParser.parse(query);
SearchChips.update(parsed); 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}`; let url = `/api/search/advanced?q=${encodeURIComponent(query)}&vault=${encodeURIComponent(vaultFilter)}&limit=${effectiveLimit}&offset=${ofs}&sort=${sortBy}`;
if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`; if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`;
if (state.searchCaseSensitive) url += "&case_sensitive=true"; if (searchCaseSensitive) url += "&case_sensitive=true";
if (state.searchWholeWord) url += "&whole_word=true"; if (searchWholeWord) url += "&whole_word=true";
if (state.searchRegex) url += "&regex=true"; if (searchRegex) url += "&regex=true";
const includeEl = document.getElementById("search-include-input"); const includeEl = document.getElementById("search-include-input");
const excludeEl = document.getElementById("search-exclude-input"); const excludeEl = document.getElementById("search-exclude-input");
if (includeEl?.value.trim()) url += `&include_paths=${encodeURIComponent(includeEl.value.trim())}`; if (includeEl?.value.trim()) url += `&include_paths=${encodeURIComponent(includeEl.value.trim())}`;
@@ -755,26 +761,26 @@ async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort
// Search timeout — abort if server takes too long // Search timeout — abort if server takes too long
const timeoutId = setTimeout( const timeoutId = setTimeout(
() => { () => {
if (state.searchAbortController) state.searchAbortController.abort(); if (searchAbortController) state.searchAbortController.abort();
}, },
_getEffective("search_timeout_ms", state.SEARCH_TIMEOUT_MS), _getEffective("search_timeout_ms", SEARCH_TIMEOUT_MS),
); );
try { try {
const data = await api(url, { signal: state.searchAbortController.signal }); const data = await api(url, { signal: state.searchAbortController.signal });
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (searchId !== state.currentSearchId) return; if (searchId !== currentSearchId) return;
state.advancedSearchTotal = data.total; state.advancedSearchTotal = data.total;
state.advancedSearchOffset = ofs; state.advancedSearchOffset = ofs;
renderAdvancedSearchResults(data, query, tagFilter); renderAdvancedSearchResults(data, query, tagFilter);
} catch (err) { } catch (err) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (err.name === "AbortError") return; if (err.name === "AbortError") return;
if (searchId !== state.currentSearchId) return; if (searchId !== currentSearchId) return;
showWelcome(); showWelcome();
} finally { } finally {
hideProgressBar(); hideProgressBar();
if (searchId === state.currentSearchId) state.searchAbortController = null; if (searchId === currentSearchId) state.searchAbortController = null;
} }
} }
@@ -797,7 +803,7 @@ function renderSearchResults(data, query, tagFilter) {
const titleDiv = el("div", { class: "search-result-title" }); const titleDiv = el("div", { class: "search-result-title" });
if (query && query.trim()) { if (query && query.trim()) {
highlightSearchText(titleDiv, r.title, query, state.searchCaseSensitive); highlightSearchText(titleDiv, r.title, query, searchCaseSensitive);
} else { } else {
titleDiv.textContent = r.title; titleDiv.textContent = r.title;
} }
@@ -805,7 +811,7 @@ function renderSearchResults(data, query, tagFilter) {
if (r.snippet && r.snippet.includes("<mark>")) { if (r.snippet && r.snippet.includes("<mark>")) {
snippetDiv.innerHTML = r.snippet; snippetDiv.innerHTML = r.snippet;
} else if (query && query.trim() && r.snippet) { } else if (query && query.trim() && r.snippet) {
highlightSearchText(snippetDiv, r.snippet, query, state.searchCaseSensitive); highlightSearchText(snippetDiv, r.snippet, query, searchCaseSensitive);
} else { } else {
snippetDiv.textContent = r.snippet || ""; snippetDiv.textContent = r.snippet || "";
} }
@@ -869,9 +875,9 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
// Active filter badges // Active filter badges
const filtersRow = el("div", { class: "search-filters-row" }); const filtersRow = el("div", { class: "search-filters-row" });
if (state.searchCaseSensitive) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode("Aa")])); if (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 (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 (searchRegex) filtersRow.appendChild(el("span", { class: "search-filter-badge" }, [document.createTextNode(".*")]));
const inclEl = document.getElementById("search-include-input"); const inclEl = document.getElementById("search-include-input");
const exclEl = document.getElementById("search-exclude-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())])); if (inclEl?.value.trim()) filtersRow.appendChild(el("span", { class: "search-filter-badge path" }, [document.createTextNode("incl: " + inclEl.value.trim())]));
@@ -913,9 +919,9 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
body: JSON.stringify({ body: JSON.stringify({
query: query, query: query,
vault: document.getElementById("vault-filter")?.value || "all", vault: document.getElementById("vault-filter")?.value || "all",
case_sensitive: state.searchCaseSensitive, case_sensitive: searchCaseSensitive,
whole_word: state.searchWholeWord, whole_word: searchWholeWord,
regex: state.searchRegex, regex: searchRegex,
include_paths: inclEl?.value || "", include_paths: inclEl?.value || "",
exclude_paths: exclEl?.value || "", exclude_paths: exclEl?.value || "",
}), }),
@@ -1012,7 +1018,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
const titleDiv = el("div", { class: "search-result-title" }); const titleDiv = el("div", { class: "search-result-title" });
if (freeText) { if (freeText) {
highlightSearchText(titleDiv, r.title, freeText, state.searchCaseSensitive); highlightSearchText(titleDiv, r.title, freeText, searchCaseSensitive);
} else { } else {
titleDiv.textContent = r.title; titleDiv.textContent = r.title;
} }
@@ -1022,7 +1028,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
if (r.snippet && r.snippet.includes("<mark>")) { if (r.snippet && r.snippet.includes("<mark>")) {
snippetDiv.innerHTML = r.snippet; snippetDiv.innerHTML = r.snippet;
} else if (freeText && r.snippet) { } else if (freeText && r.snippet) {
highlightSearchText(snippetDiv, r.snippet, freeText, state.searchCaseSensitive); highlightSearchText(snippetDiv, r.snippet, freeText, searchCaseSensitive);
} else { } else {
snippetDiv.textContent = r.snippet || ""; snippetDiv.textContent = r.snippet || "";
} }
@@ -1060,21 +1066,21 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
area.appendChild(container); area.appendChild(container);
// Pagination // Pagination
if (data.total > state.ADVANCED_SEARCH_LIMIT) { if (data.total > ADVANCED_SEARCH_LIMIT) {
const paginationDiv = el("div", { class: "search-pagination" }); const paginationDiv = el("div", { class: "search-pagination" });
const prevBtn = el("button", { class: "search-pagination__btn", type: "button" }); const prevBtn = el("button", { class: "search-pagination__btn", type: "button" });
prevBtn.textContent = "← Précédent"; prevBtn.textContent = "← Précédent";
prevBtn.disabled = state.advancedSearchOffset === 0; prevBtn.disabled = state.advancedSearchOffset === 0;
prevBtn.addEventListener("click", () => { prevBtn.addEventListener("click", () => {
state.advancedSearchOffset = Math.max(0, advancedSearchOffset - state.ADVANCED_SEARCH_LIMIT); state.advancedSearchOffset = Math.max(0, advancedSearchOffset - ADVANCED_SEARCH_LIMIT);
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset);
document.getElementById("content-area").scrollTop = 0; document.getElementById("content-area").scrollTop = 0;
}); });
const info = el("span", { class: "search-pagination__info" }); const info = el("span", { class: "search-pagination__info" });
const from = advancedSearchOffset + 1; const from = advancedSearchOffset + 1;
const to = Math.min(advancedSearchOffset + state.ADVANCED_SEARCH_LIMIT, data.total); const to = Math.min(advancedSearchOffset + ADVANCED_SEARCH_LIMIT, data.total);
info.textContent = `${from}–${to} sur ${data.total}`; info.textContent = `${from}–${to} sur ${data.total}`;
const nextBtn = el("button", { class: "search-pagination__btn", type: "button" }); const nextBtn = el("button", { class: "search-pagination__btn", type: "button" });
@@ -1083,7 +1089,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
nextBtn.addEventListener("click", () => { nextBtn.addEventListener("click", () => {
advancedSearchOffset += state.ADVANCED_SEARCH_LIMIT; advancedSearchOffset += state.ADVANCED_SEARCH_LIMIT;
const vault = document.getElementById("vault-filter").value; const vault = document.getElementById("vault-filter").value;
performAdvancedSearch(query, vault, tagFilter, state.advancedSearchOffset); performAdvancedSearch(query, vault, tagFilter, advancedSearchOffset);
document.getElementById("content-area").scrollTop = 0; document.getElementById("content-area").scrollTop = 0;
}); });
@@ -1097,3 +1103,5 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
// Initialize result navigation (select first result) // Initialize result navigation (select first result)
setTimeout(() => { if (window.navigateSearchResults) window.navigateSearchResults(0); }, 50); setTimeout(() => { if (window.navigateSearchResults) window.navigateSearchResults(0); }, 50);
} }
+180 -13
View File
@@ -1,5 +1,7 @@
/* ObsiGate — Auto-extracted module */ /* ObsiGate — UI module */
import { state } from './state.js';; import { state } from './state.js';
import { openFile } from './viewer.js';
import { safeCreateIcons } from './utils.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Right Sidebar Manager // Right Sidebar Manager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -34,9 +36,9 @@ export const RightSidebarManager = {
if (!sidebar) return; if (!sidebar) return;
if (state.rightSidebarVisible) { if (rightSidebarVisible) {
sidebar.classList.remove("hidden"); sidebar.classList.remove("hidden");
sidebar.style.width = `${state.rightSidebarWidth}px`; sidebar.style.width = `${rightSidebarWidth}px`;
if (handle) handle.classList.remove("hidden"); if (handle) handle.classList.remove("hidden");
if (tocBtn) { if (tocBtn) {
tocBtn.classList.add("active"); tocBtn.classList.add("active");
@@ -65,7 +67,7 @@ export const RightSidebarManager = {
toggle() { toggle() {
state.rightSidebarVisible = !state.rightSidebarVisible; state.rightSidebarVisible = !state.rightSidebarVisible;
localStorage.setItem("obsigate-right-sidebar-visible", state.rightSidebarVisible); localStorage.setItem("obsigate-right-sidebar-visible", rightSidebarVisible);
this.applyState(); this.applyState();
}, },
@@ -116,7 +118,7 @@ export const RightSidebarManager = {
document.body.style.cursor = ""; document.body.style.cursor = "";
document.body.style.userSelect = ""; document.body.style.userSelect = "";
localStorage.setItem("obsigate-right-sidebar-width", state.rightSidebarWidth); localStorage.setItem("obsigate-right-sidebar-width", rightSidebarWidth);
}; };
handle.addEventListener("mousedown", onMouseDown); handle.addEventListener("mousedown", onMouseDown);
@@ -125,6 +127,7 @@ export const RightSidebarManager = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Theme // Theme
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -197,6 +200,7 @@ function closeHeaderMenu() {
menuDropdown.classList.remove("active"); menuDropdown.classList.remove("active");
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Custom Dropdowns // Custom Dropdowns
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -327,6 +331,7 @@ function populateCustomDropdown(dropdownId, optionsList, defaultValue) {
}); });
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Toast notifications // Toast notifications
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -359,6 +364,7 @@ function showToast(message, type) {
}, 3500); }, 3500);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Sidebar toggle (desktop) // Sidebar toggle (desktop)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -385,6 +391,7 @@ function initSidebarToggle() {
}); });
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Mobile sidebar // Mobile sidebar
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -411,6 +418,7 @@ function closeMobileSidebar() {
if (overlay) overlay.classList.remove("active"); if (overlay) overlay.classList.remove("active");
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Resizable sidebar (horizontal) // Resizable sidebar (horizontal)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -452,6 +460,7 @@ function initSidebarResize() {
}); });
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Resizable tag section (vertical) // Resizable tag section (vertical)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -494,6 +503,7 @@ function initTagResize() {
}); });
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Frontmatter Accent Card Builder // Frontmatter Accent Card Builder
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -557,11 +567,163 @@ function buildFrontmatterCard(frontmatter) {
// ZONE 2: Body 2 columns // ZONE 2: Body 2 columns
const leftCol = el("div", { class: "ac-col" }, [ const leftCol = el("div", { class: "ac-col" }, [
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("auteur")]), el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.auteur || "—")])]), el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("auteur")]), el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.auteur || "—")])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("catégorie")]), el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.catégorie || frontmatter.categorie || el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("catégorie")]), el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.catégorie || frontmatter.categorie || "—")])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("statut")]), el("span", { class: "ac-v" }, [document.createTextNode(frontmatter.statut || "—")])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("aliases")]), el("span", { class: "ac-v muted" }, [document.createTextNode(frontmatter.aliases && frontmatter.aliases.length > 0 ? frontmatter.aliases.join(", ") : "[]")])]),
]);
... [OUTPUT TRUNCATED - 6724 chars omitted out of 56724 total] ... const rightCol = el("div", { class: "ac-col" }, [
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("creation_date")]), el("span", { class: "ac-v mono" }, [document.createTextNode(formatDate(frontmatter.creation_date))])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("modification_date")]), el("span", { class: "ac-v mono" }, [document.createTextNode(formatDate(frontmatter.modification_date))])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("publish")]), el("span", { class: "ac-v" }, [document.createTextNode(String(frontmatter.publish || false))])]),
el("div", { class: "ac-row" }, [el("span", { class: "ac-k" }, [document.createTextNode("favoris")]), el("span", { class: "ac-v" }, [document.createTextNode(String(frontmatter.favoris || false))])]),
]);
'div'); const acBody = el("div", { class: "ac-body" }, [leftCol, rightCol]);
// ZONE 3: Tags row
const tagPills = [];
if (frontmatter.tags && frontmatter.tags.length > 0) {
frontmatter.tags.forEach((tag) => {
tagPills.push(el("span", { class: "ac-tag" }, [document.createTextNode(tag)]));
});
}
const acTagsRow = el("div", { class: "ac-tags-row" }, [el("span", { class: "ac-tags-k" }, [document.createTextNode("tags")]), el("div", { class: "ac-tags-wrap" }, tagPills)]);
// ZONE 4: Flags row
const flagChips = [];
booleanFlags.forEach((flag) => {
const chipClass = flag.value ? "flag-chip on" : "flag-chip off";
flagChips.push(el("span", { class: chipClass }, [el("span", { class: "flag-dot" }), document.createTextNode(flag.key)]));
});
const acFlagsRow = el("div", { class: "ac-flags-row" }, [el("span", { class: "ac-flags-k" }, [document.createTextNode("flags")]), ...flagChips]);
// Assemble the card
const acCard = el("div", { class: "ac-card" }, [acTop, acBody, acTagsRow, acFlagsRow]);
// Toggle functionality
fmHeader.addEventListener("click", () => {
isOpen = !isOpen;
if (isOpen) {
acCard.style.display = "block";
chevron.classList.remove("closed");
chevron.classList.add("open");
} else {
acCard.style.display = "none";
chevron.classList.remove("open");
chevron.classList.add("closed");
}
safeCreateIcons();
});
// Wrap in section
const fmSection = el("div", { class: "fm-section" }, [fmHeader, acCard]);
return fmSection;
}
// ---------------------------------------------------------------------------
// File Operations Manager
// ---------------------------------------------------------------------------
const FileOperations = {
showCreateDirectoryModal(vault, parentPath) {
const overlay = this._createModalOverlay();
const modal = document.createElement('div');
modal.className = 'obsigate-modal';
modal.innerHTML = `
<div class="obsigate-modal-header">
<h3 class="obsigate-modal-title">Créer un dossier</h3>
</div>
<div class="obsigate-modal-body">
<div class="modal-form-group">
<label class="modal-label">Nom du dossier</label>
<input type="text" class="modal-input" id="dir-name-input" placeholder="nouveau-dossier" />
<div class="modal-hint">Dans: ${parentPath || '/'}</div>
<div class="modal-error" id="dir-error" style="display:none;"></div>
</div>
</div>
<div class="obsigate-modal-footer">
<button class="modal-btn" id="dir-cancel-btn">Annuler</button>
<button class="modal-btn primary" id="dir-create-btn">Créer</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
setTimeout(() => overlay.classList.add('active'), 10);
const input = modal.querySelector('#dir-name-input');
const errorDiv = modal.querySelector('#dir-error');
const createBtn = modal.querySelector('#dir-create-btn');
const cancelBtn = modal.querySelector('#dir-cancel-btn');
input.focus();
const validateName = (name) => {
if (!name.trim()) return 'Le nom ne peut pas être vide';
if (/[/\\:*?"<>|]/.test(name)) return 'Caractères interdits: / \\ : * ? " < > |';
return null;
};
input.addEventListener('input', () => {
const error = validateName(input.value);
if (error) {
errorDiv.textContent = error;
errorDiv.style.display = 'block';
input.classList.add('error');
} else {
errorDiv.style.display = 'none';
input.classList.remove('error');
}
});
const create = async () => {
const name = input.value.trim();
const error = validateName(name);
if (error) {
errorDiv.textContent = error;
errorDiv.style.display = 'block';
return;
}
const path = parentPath ? `${parentPath}/${name}` : name;
createBtn.disabled = true;
createBtn.textContent = 'Création...';
try {
await api(`/api/directory/${encodeURIComponent(vault)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path }),
});
showToast(`Dossier "${name}" créé`, 'success');
this._closeModal(overlay);
await refreshSidebarTreePreservingState();
} catch (err) {
showToast(err.message || 'Erreur lors de la création', 'error');
createBtn.disabled = false;
createBtn.textContent = 'Créer';
}
};
createBtn.addEventListener('click', create);
cancelBtn.addEventListener('click', () => this._closeModal(overlay));
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') create();
if (e.key === 'Escape') this._closeModal(overlay);
});
},
showCreateFileModal(vault, parentPath) {
const overlay = this._createModalOverlay();
const modal = document.createElement('div');
modal.className = 'obsigate-modal'; modal.className = 'obsigate-modal';
modal.innerHTML = ` modal.innerHTML = `
<div class="obsigate-modal-header"> <div class="obsigate-modal-header">
@@ -743,10 +905,10 @@ function buildFrontmatterCard(frontmatter) {
if (type === 'file' && state.currentVault === vault && state.currentPath === path) { if (type === 'file' && state.currentVault === vault && state.currentPath === path) {
await openFile(vault, nextPath); await openFile(vault, nextPath);
} else if (type === 'directory' && state.currentVault === vault && currentPath && (state.currentPath === path || state.currentPath.startsWith(`${path}/`))) { } else if (type === 'directory' && state.currentVault === vault && currentPath && (state.currentPath === path || currentPath.startsWith(`${path}/`))) {
const suffix = state.currentPath === path ? '' : state.currentPath.slice(path.length); const suffix = state.currentPath === path ? '' : currentPath.slice(path.length);
state.currentPath = `${nextPath}${suffix}`; state.currentPath = `${nextPath}${suffix}`;
await focusPathInSidebar(vault, state.currentPath, { alignToTop: false }); await focusPathInSidebar(vault, currentPath, { alignToTop: false });
} }
showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success'); showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success');
@@ -830,7 +992,7 @@ function buildFrontmatterCard(frontmatter) {
this._closeModal(overlay); this._closeModal(overlay);
await refreshSidebarTreePreservingState(); await refreshSidebarTreePreservingState();
if (state.currentVault === vault && currentPath && state.currentPath.startsWith(path)) { if (state.currentVault === vault && currentPath && currentPath.startsWith(path)) {
showWelcome(); showWelcome();
} }
} catch (err) { } catch (err) {
@@ -923,6 +1085,7 @@ function buildFrontmatterCard(frontmatter) {
} }
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Find in Page Manager // Find in Page Manager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1344,6 +1507,7 @@ const FindInPageManager = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Init // Init
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1414,6 +1578,7 @@ async function init() {
safeCreateIcons(); safeCreateIcons();
} }
// ---- Modify openFile to use TabManager ---- // ---- Modify openFile to use TabManager ----
const _originalOpenFile = openFile; const _originalOpenFile = openFile;
openFile = function(vault, path) { openFile = function(vault, path) {
@@ -1454,3 +1619,5 @@ init = function() {
_origInit2(); _origInit2();
TabManager.init(); TabManager.init();
}; };
+27 -19
View File
@@ -1,5 +1,6 @@
/* ObsiGate — Auto-extracted module */ /* ObsiGate — Viewer module */
import { state } from './state.js';; import { state } from './state.js';
import { escapeHtml, safeCreateIcons, safeHighlight, getFileIcon } from './utils.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Outline/TOC Manager // Outline/TOC Manager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -183,6 +184,7 @@ const OutlineManager = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Scroll Spy Manager // Scroll Spy Manager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -240,6 +242,7 @@ const ScrollSpyManager = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Reading Progress Manager // Reading Progress Manager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -305,6 +308,7 @@ const ReadingProgressManager = {
}, },
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// File viewer // File viewer
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -435,12 +439,12 @@ function renderFile(data) {
copyBtn.addEventListener("click", async () => { copyBtn.addEventListener("click", async () => {
try { try {
// Fetch raw content if not already cached // Fetch raw content if not already cached
if (!state.cachedRawSource) { if (!cachedRawSource) {
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`; const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
const rawData = await api(rawUrl); const rawData = await api(rawUrl);
state.cachedRawSource = rawData.raw; state.cachedRawSource = rawData.raw;
} }
await navigator.clipboard.writeText(state.cachedRawSource); await navigator.clipboard.writeText(cachedRawSource);
copyBtn.lastChild.textContent = "Copié !"; copyBtn.lastChild.textContent = "Copié !";
setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500); setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500);
} catch (err) { } catch (err) {
@@ -542,9 +546,9 @@ function renderFile(data) {
if (!rendered || !raw) return; if (!rendered || !raw) return;
state.showingSource = !state.showingSource; state.showingSource = !state.showingSource;
if (state.showingSource) { if (showingSource) {
sourceBtn.classList.add("active"); sourceBtn.classList.add("active");
if (!state.cachedRawSource) { if (!cachedRawSource) {
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`; const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
const rawData = await api(rawUrl); const rawData = await api(rawUrl);
state.cachedRawSource = rawData.raw; state.cachedRawSource = rawData.raw;
@@ -594,6 +598,7 @@ function renderFile(data) {
OutlineManager.init(); OutlineManager.init();
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helpers // Helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -725,7 +730,7 @@ function attachTreeItemLongPress(itemEl, getMenuData) {
} }
function getVaultIcon(vaultName, size = 16) { function getVaultIcon(vaultName, size = 16) {
const v = state.allVaults.find((val) => val.name === vaultName); const v = allVaults.find((val) => val.name === vaultName);
const type = v ? v.type : "VAULT"; const type = v ? v.type : "VAULT";
if (type === "DIR") { if (type === "DIR") {
@@ -948,10 +953,10 @@ function showWelcome() {
DashboardConflictsWidget.load(); DashboardConflictsWidget.load();
} }
if (typeof DashboardRecentWidget !== "undefined") { if (typeof DashboardRecentWidget !== "undefined") {
DashboardRecentWidget.load(state.selectedContextVault); DashboardRecentWidget.load(selectedContextVault);
} }
if (typeof DashboardBookmarkWidget !== "undefined") { if (typeof DashboardBookmarkWidget !== "undefined") {
DashboardBookmarkWidget.load(state.selectedContextVault); DashboardBookmarkWidget.load(selectedContextVault);
} }
if (typeof DashboardSharedWidget !== "undefined") { if (typeof DashboardSharedWidget !== "undefined") {
DashboardSharedWidget.load(); DashboardSharedWidget.load();
@@ -1002,9 +1007,9 @@ async function loadSavedSearches() {
// Apply the saved search // Apply the saved search
const input = document.getElementById("search-input"); const input = document.getElementById("search-input");
if (input) input.value = s.query; if (input) input.value = s.query;
state.searchCaseSensitive = s.case_sensitive || false; searchCaseSensitive = s.case_sensitive || false;
state.searchWholeWord = s.whole_word || false; searchWholeWord = s.whole_word || false;
state.searchRegex = s.regex || false; searchRegex = s.regex || false;
if (typeof _updateToggleUI === "function") _updateToggleUI(); if (typeof _updateToggleUI === "function") _updateToggleUI();
if (s.include_paths) { if (s.include_paths) {
const incl = document.getElementById("search-include-input"); const incl = document.getElementById("search-include-input");
@@ -1019,8 +1024,8 @@ async function loadSavedSearches() {
AutocompleteDropdown._suppressNext = true; AutocompleteDropdown._suppressNext = true;
const vault = s.vault || "all"; const vault = s.vault || "all";
if (input) { input.dispatchEvent(new Event("input")); } if (input) { input.dispatchEvent(new Event("input")); }
clearTimeout(state.searchTimeout); clearTimeout(searchTimeout);
state.advancedSearchOffset = 0; advancedSearchOffset = 0;
performAdvancedSearch(s.query, vault, null); performAdvancedSearch(s.query, vault, null);
}); });
}); });
@@ -1068,6 +1073,7 @@ function goHome() {
showWelcome(); showWelcome();
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// SSE Client — IndexUpdateManager // SSE Client — IndexUpdateManager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1216,15 +1222,15 @@ const IndexUpdateManager = (() => {
// Toast removed: silent auto-indexing — no notification needed // Toast removed: silent auto-indexing — no notification needed
// Refresh sidebar and tags if affected vault matches current context // Refresh sidebar and tags if affected vault matches current context
const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(state.selectedContextVault); const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(selectedContextVault);
if (affectsCurrentVault) { if (affectsCurrentVault) {
try { try {
await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]); await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]);
// Refresh current file if it was updated // Refresh current file if it was updated
if (currentVault && state.currentPath) { if (currentVault && currentPath) {
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath); const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === currentPath);
if (changed) { if (changed) {
openFile(state.currentVault, state.currentPath); openFile(currentVault, currentPath);
} }
} }
} catch (err) { } catch (err) {
@@ -1233,7 +1239,7 @@ const IndexUpdateManager = (() => {
} }
// Refresh recent tab if it is active // Refresh recent tab if it is active
if (state.activeSidebarTab === "recent") { if (activeSidebarTab === "recent") {
const vaultFilter = document.getElementById("recent-vault-filter"); const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null); loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
} }
@@ -1384,3 +1390,5 @@ function _renderSyncPanel(panel) {
panel.innerHTML = html; panel.innerHTML = html;
} }