fix: rebuild ui.js, search.js, viewer.js from app.js with shell script (no truncation)
This commit is contained in:
parent
97d14d867a
commit
7c927ca54e
@ -1,12 +1,13 @@
|
||||
/* ObsiGate — Auto-extracted module */
|
||||
import { state } from './state.js';;
|
||||
/* ObsiGate — Search module */
|
||||
import { state } from './state.js';
|
||||
import { safeCreateIcons } from './utils.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Search History Service (localStorage, LIFO, max 50, dedup)
|
||||
// ---------------------------------------------------------------------------
|
||||
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 [];
|
||||
@ -14,7 +15,7 @@ const SearchHistory = {
|
||||
},
|
||||
_save(entries) {
|
||||
try {
|
||||
localStorage.setItem(state.SEARCH_HISTORY_KEY, JSON.stringify(entries));
|
||||
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(entries));
|
||||
} catch {}
|
||||
},
|
||||
getAll() {
|
||||
@ -25,7 +26,7 @@ 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) {
|
||||
@ -44,6 +45,7 @@ const SearchHistory = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query Parser — extracts operators (tag:, #, vault:, title:, path:, ext:)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -119,6 +121,7 @@ const QueryParser = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Autocomplete Dropdown Controller
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -179,7 +182,7 @@ const AutocompleteDropdown = {
|
||||
async populate(inputValue, cursorPos) {
|
||||
if (this._suppressNext) { this._suppressNext = false; return; }
|
||||
// Cancel previous suggestion request
|
||||
if (state.suggestAbortController) {
|
||||
if (suggestAbortController) {
|
||||
state.suggestAbortController.abort();
|
||||
state.suggestAbortController = null;
|
||||
}
|
||||
@ -370,29 +373,30 @@ const AutocompleteDropdown = {
|
||||
|
||||
navigateDown() {
|
||||
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.dropdownItems[state.dropdownActiveIndex].classList.add("active");
|
||||
state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" });
|
||||
dropdownItems[dropdownActiveIndex].classList.add("active");
|
||||
dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" });
|
||||
},
|
||||
|
||||
navigateUp() {
|
||||
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.dropdownItems[state.dropdownActiveIndex].classList.add("active");
|
||||
state.dropdownItems[state.dropdownActiveIndex].scrollIntoView({ block: "nearest" });
|
||||
dropdownItems[dropdownActiveIndex].classList.add("active");
|
||||
dropdownItems[dropdownActiveIndex].scrollIntoView({ block: "nearest" });
|
||||
},
|
||||
|
||||
selectActive() {
|
||||
if (dropdownActiveIndex >= 0 && dropdownActiveIndex < state.dropdownItems.length) {
|
||||
state.dropdownItems[state.dropdownActiveIndex].click();
|
||||
dropdownItems[dropdownActiveIndex].click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Search Chips Controller — renders active filter chips from parsed query
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -454,6 +458,7 @@ const SearchChips = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: trigger advanced search from input value
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -471,6 +476,7 @@ function _triggerAdvancedSearch(rawQuery) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Search (enhanced with autocomplete, keyboard nav, global shortcuts)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -490,10 +496,10 @@ function initSearch() {
|
||||
const countEl = document.getElementById("search-match-count");
|
||||
|
||||
function _updateToggleUI() {
|
||||
caseBtn.classList.toggle("active", state.searchCaseSensitive);
|
||||
wordBtn.classList.toggle("active", state.searchWholeWord);
|
||||
regexBtn.classList.toggle("active", state.searchRegex);
|
||||
filterBtn.classList.toggle("active", state.searchFilterVisible);
|
||||
caseBtn.classList.toggle("active", searchCaseSensitive);
|
||||
wordBtn.classList.toggle("active", searchWholeWord);
|
||||
regexBtn.classList.toggle("active", searchRegex);
|
||||
filterBtn.classList.toggle("active", searchFilterVisible);
|
||||
}
|
||||
|
||||
// Toggle buttons
|
||||
@ -536,7 +542,7 @@ function initSearch() {
|
||||
function _research() {
|
||||
const q = input.value.trim();
|
||||
if (q.length >= _getEffective("min_query_length", 2)) {
|
||||
clearTimeout(state.searchTimeout);
|
||||
clearTimeout(searchTimeout);
|
||||
state.searchTimeout = setTimeout(() => {
|
||||
const vault = document.getElementById("vault-filter").value;
|
||||
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
|
||||
@ -572,14 +578,14 @@ function initSearch() {
|
||||
AutocompleteDropdown.populate(input.value, input.selectionStart);
|
||||
|
||||
// Debounced search execution
|
||||
clearTimeout(state.searchTimeout);
|
||||
clearTimeout(searchTimeout);
|
||||
state.searchTimeout = setTimeout(
|
||||
() => {
|
||||
const q = input.value.trim();
|
||||
const vault = document.getElementById("vault-filter").value;
|
||||
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
|
||||
state.advancedSearchOffset = 0;
|
||||
if (q.length >= _getEffective("min_query_length", state.MIN_SEARCH_LENGTH) || tagFilter) {
|
||||
if (q.length >= _getEffective("min_query_length", MIN_SEARCH_LENGTH) || tagFilter) {
|
||||
performAdvancedSearch(q, vault, tagFilter);
|
||||
} else if (q.length === 0) {
|
||||
SearchChips.clear();
|
||||
@ -630,7 +636,7 @@ function initSearch() {
|
||||
const q = input.value.trim();
|
||||
if (q) {
|
||||
SearchHistory.add(q);
|
||||
clearTimeout(state.searchTimeout);
|
||||
clearTimeout(searchTimeout);
|
||||
state.advancedSearchOffset = 0;
|
||||
const vault = document.getElementById("vault-filter").value;
|
||||
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
|
||||
@ -654,7 +660,7 @@ function initSearch() {
|
||||
const q = input.value.trim();
|
||||
if (q) {
|
||||
SearchHistory.add(q);
|
||||
clearTimeout(state.searchTimeout);
|
||||
clearTimeout(searchTimeout);
|
||||
state.advancedSearchOffset = 0;
|
||||
const vault = document.getElementById("vault-filter").value;
|
||||
const tagFilter = state.selectedTags.length > 0 ? state.selectedTags.join(",") : null;
|
||||
@ -706,7 +712,7 @@ function _isInputFocused() {
|
||||
|
||||
// --- Backward-compatible search (existing /api/search endpoint) ---
|
||||
async function performSearch(query, vaultFilter, tagFilter) {
|
||||
if (state.searchAbortController) state.searchAbortController.abort();
|
||||
if (searchAbortController) state.searchAbortController.abort();
|
||||
state.searchAbortController = new AbortController();
|
||||
const searchId = ++state.currentSearchId;
|
||||
showLoading();
|
||||
@ -714,21 +720,21 @@ async function performSearch(query, vaultFilter, tagFilter) {
|
||||
if (tagFilter) url += `&tag=${encodeURIComponent(tagFilter)}`;
|
||||
try {
|
||||
const data = await api(url, { signal: state.searchAbortController.signal });
|
||||
if (searchId !== state.currentSearchId) return;
|
||||
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) state.searchAbortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Advanced search with TF-IDF, facets, pagination ---
|
||||
async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort) {
|
||||
if (state.searchAbortController) state.searchAbortController.abort();
|
||||
if (searchAbortController) state.searchAbortController.abort();
|
||||
state.searchAbortController = new AbortController();
|
||||
const searchId = ++state.currentSearchId;
|
||||
showLoading();
|
||||
@ -741,12 +747,12 @@ async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort
|
||||
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())}`;
|
||||
@ -755,26 +761,26 @@ async function performAdvancedSearch(query, vaultFilter, tagFilter, offset, sort
|
||||
// Search timeout — abort if server takes too long
|
||||
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 {
|
||||
const data = await api(url, { signal: state.searchAbortController.signal });
|
||||
clearTimeout(timeoutId);
|
||||
if (searchId !== state.currentSearchId) return;
|
||||
if (searchId !== currentSearchId) return;
|
||||
state.advancedSearchTotal = data.total;
|
||||
state.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) state.searchAbortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -797,7 +803,7 @@ 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;
|
||||
}
|
||||
@ -805,7 +811,7 @@ function renderSearchResults(data, query, tagFilter) {
|
||||
if (r.snippet && r.snippet.includes("<mark>")) {
|
||||
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 || "";
|
||||
}
|
||||
@ -869,9 +875,9 @@ 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())]));
|
||||
@ -913,9 +919,9 @@ 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 || "",
|
||||
}),
|
||||
@ -1012,7 +1018,7 @@ 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;
|
||||
}
|
||||
@ -1022,7 +1028,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
|
||||
if (r.snippet && r.snippet.includes("<mark>")) {
|
||||
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 || "";
|
||||
}
|
||||
@ -1060,21 +1066,21 @@ 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.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;
|
||||
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 = 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}`;
|
||||
|
||||
const nextBtn = el("button", { class: "search-pagination__btn", type: "button" });
|
||||
@ -1083,7 +1089,7 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
|
||||
nextBtn.addEventListener("click", () => {
|
||||
advancedSearchOffset += state.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;
|
||||
});
|
||||
|
||||
@ -1096,4 +1102,6 @@ function renderAdvancedSearchResults(data, query, tagFilter) {
|
||||
safeCreateIcons();
|
||||
// Initialize result navigation (select first result)
|
||||
setTimeout(() => { if (window.navigateSearchResults) window.navigateSearchResults(0); }, 50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
/* ObsiGate — Auto-extracted module */
|
||||
import { state } from './state.js';;
|
||||
/* ObsiGate — UI module */
|
||||
import { state } from './state.js';
|
||||
import { openFile } from './viewer.js';
|
||||
import { safeCreateIcons } from './utils.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Right Sidebar Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -34,9 +36,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");
|
||||
@ -65,7 +67,7 @@ export const RightSidebarManager = {
|
||||
|
||||
toggle() {
|
||||
state.rightSidebarVisible = !state.rightSidebarVisible;
|
||||
localStorage.setItem("obsigate-right-sidebar-visible", state.rightSidebarVisible);
|
||||
localStorage.setItem("obsigate-right-sidebar-visible", rightSidebarVisible);
|
||||
this.applyState();
|
||||
},
|
||||
|
||||
@ -116,7 +118,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);
|
||||
@ -125,6 +127,7 @@ export const RightSidebarManager = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Theme
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -197,6 +200,7 @@ function closeHeaderMenu() {
|
||||
menuDropdown.classList.remove("active");
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Custom Dropdowns
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -327,6 +331,7 @@ function populateCustomDropdown(dropdownId, optionsList, defaultValue) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Toast notifications
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -359,6 +364,7 @@ function showToast(message, type) {
|
||||
}, 3500);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sidebar toggle (desktop)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -385,6 +391,7 @@ function initSidebarToggle() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mobile sidebar
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -411,6 +418,7 @@ function closeMobileSidebar() {
|
||||
if (overlay) overlay.classList.remove("active");
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resizable sidebar (horizontal)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -452,6 +460,7 @@ function initSidebarResize() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resizable tag section (vertical)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -494,6 +503,7 @@ function initTagResize() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frontmatter Accent Card Builder
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -557,11 +567,163 @@ function buildFrontmatterCard(frontmatter) {
|
||||
// ZONE 2: Body 2 columns
|
||||
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("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.innerHTML = `
|
||||
<div class="obsigate-modal-header">
|
||||
@ -743,10 +905,10 @@ function buildFrontmatterCard(frontmatter) {
|
||||
|
||||
if (type === 'file' && state.currentVault === vault && state.currentPath === path) {
|
||||
await openFile(vault, nextPath);
|
||||
} 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);
|
||||
} else if (type === 'directory' && state.currentVault === vault && currentPath && (state.currentPath === path || currentPath.startsWith(`${path}/`))) {
|
||||
const suffix = state.currentPath === path ? '' : currentPath.slice(path.length);
|
||||
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');
|
||||
@ -830,7 +992,7 @@ function buildFrontmatterCard(frontmatter) {
|
||||
this._closeModal(overlay);
|
||||
await refreshSidebarTreePreservingState();
|
||||
|
||||
if (state.currentVault === vault && currentPath && state.currentPath.startsWith(path)) {
|
||||
if (state.currentVault === vault && currentPath && currentPath.startsWith(path)) {
|
||||
showWelcome();
|
||||
}
|
||||
} catch (err) {
|
||||
@ -923,6 +1085,7 @@ function buildFrontmatterCard(frontmatter) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Find in Page Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1344,6 +1507,7 @@ const FindInPageManager = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Init
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1414,6 +1578,7 @@ async function init() {
|
||||
safeCreateIcons();
|
||||
}
|
||||
|
||||
|
||||
// ---- Modify openFile to use TabManager ----
|
||||
const _originalOpenFile = openFile;
|
||||
openFile = function(vault, path) {
|
||||
@ -1453,4 +1618,6 @@ const _origInit2 = init;
|
||||
init = function() {
|
||||
_origInit2();
|
||||
TabManager.init();
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* ObsiGate — Auto-extracted module */
|
||||
import { state } from './state.js';;
|
||||
/* ObsiGate — Viewer module */
|
||||
import { state } from './state.js';
|
||||
import { escapeHtml, safeCreateIcons, safeHighlight, getFileIcon } from './utils.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Outline/TOC Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -183,6 +184,7 @@ const OutlineManager = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scroll Spy Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -240,6 +242,7 @@ const ScrollSpyManager = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reading Progress Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -305,6 +308,7 @@ const ReadingProgressManager = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File viewer
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -435,12 +439,12 @@ function renderFile(data) {
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
// 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 rawData = await api(rawUrl);
|
||||
state.cachedRawSource = rawData.raw;
|
||||
}
|
||||
await navigator.clipboard.writeText(state.cachedRawSource);
|
||||
await navigator.clipboard.writeText(cachedRawSource);
|
||||
copyBtn.lastChild.textContent = "Copié !";
|
||||
setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500);
|
||||
} catch (err) {
|
||||
@ -542,9 +546,9 @@ function renderFile(data) {
|
||||
if (!rendered || !raw) return;
|
||||
|
||||
state.showingSource = !state.showingSource;
|
||||
if (state.showingSource) {
|
||||
if (showingSource) {
|
||||
sourceBtn.classList.add("active");
|
||||
if (!state.cachedRawSource) {
|
||||
if (!cachedRawSource) {
|
||||
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
|
||||
const rawData = await api(rawUrl);
|
||||
state.cachedRawSource = rawData.raw;
|
||||
@ -594,6 +598,7 @@ function renderFile(data) {
|
||||
OutlineManager.init();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -725,7 +730,7 @@ function attachTreeItemLongPress(itemEl, getMenuData) {
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
if (type === "DIR") {
|
||||
@ -948,10 +953,10 @@ function showWelcome() {
|
||||
DashboardConflictsWidget.load();
|
||||
}
|
||||
if (typeof DashboardRecentWidget !== "undefined") {
|
||||
DashboardRecentWidget.load(state.selectedContextVault);
|
||||
DashboardRecentWidget.load(selectedContextVault);
|
||||
}
|
||||
if (typeof DashboardBookmarkWidget !== "undefined") {
|
||||
DashboardBookmarkWidget.load(state.selectedContextVault);
|
||||
DashboardBookmarkWidget.load(selectedContextVault);
|
||||
}
|
||||
if (typeof DashboardSharedWidget !== "undefined") {
|
||||
DashboardSharedWidget.load();
|
||||
@ -1002,9 +1007,9 @@ async function loadSavedSearches() {
|
||||
// Apply the saved search
|
||||
const input = document.getElementById("search-input");
|
||||
if (input) input.value = s.query;
|
||||
state.searchCaseSensitive = s.case_sensitive || false;
|
||||
state.searchWholeWord = s.whole_word || false;
|
||||
state.searchRegex = s.regex || false;
|
||||
searchCaseSensitive = s.case_sensitive || false;
|
||||
searchWholeWord = s.whole_word || false;
|
||||
searchRegex = s.regex || false;
|
||||
if (typeof _updateToggleUI === "function") _updateToggleUI();
|
||||
if (s.include_paths) {
|
||||
const incl = document.getElementById("search-include-input");
|
||||
@ -1019,8 +1024,8 @@ async function loadSavedSearches() {
|
||||
AutocompleteDropdown._suppressNext = true;
|
||||
const vault = s.vault || "all";
|
||||
if (input) { input.dispatchEvent(new Event("input")); }
|
||||
clearTimeout(state.searchTimeout);
|
||||
state.advancedSearchOffset = 0;
|
||||
clearTimeout(searchTimeout);
|
||||
advancedSearchOffset = 0;
|
||||
performAdvancedSearch(s.query, vault, null);
|
||||
});
|
||||
});
|
||||
@ -1068,6 +1073,7 @@ function goHome() {
|
||||
showWelcome();
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SSE Client — IndexUpdateManager
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1216,15 +1222,15 @@ const IndexUpdateManager = (() => {
|
||||
// Toast removed: silent auto-indexing — no notification needed
|
||||
|
||||
// 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) {
|
||||
try {
|
||||
await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]);
|
||||
// Refresh current file if it was updated
|
||||
if (currentVault && state.currentPath) {
|
||||
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath);
|
||||
if (currentVault && currentPath) {
|
||||
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === currentPath);
|
||||
if (changed) {
|
||||
openFile(state.currentVault, state.currentPath);
|
||||
openFile(currentVault, currentPath);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@ -1233,7 +1239,7 @@ const IndexUpdateManager = (() => {
|
||||
}
|
||||
|
||||
// Refresh recent tab if it is active
|
||||
if (state.activeSidebarTab === "recent") {
|
||||
if (activeSidebarTab === "recent") {
|
||||
const vaultFilter = document.getElementById("recent-vault-filter");
|
||||
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
|
||||
}
|
||||
@ -1383,4 +1389,6 @@ function _renderSyncPanel(panel) {
|
||||
}
|
||||
|
||||
panel.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user