feat: implement initial ObsiGate application with backend API, indexing, search, and basic frontend.

This commit is contained in:
2026-03-24 12:56:00 -04:00
parent d6ae501f51
commit c5e395005f
4 changed files with 318 additions and 2 deletions
+162 -2
View File
@@ -2261,6 +2261,150 @@
area.scrollTop = 0;
}
// ---------------------------------------------------------------------------
// Recent files
// ---------------------------------------------------------------------------
let _recentRefreshTimer = null;
let _recentTimestampTimer = null;
let _recentFilesCache = [];
async function loadRecentFiles(vaultFilter) {
const listEl = document.getElementById("recent-list");
const emptyEl = document.getElementById("recent-empty");
if (!listEl) return;
let url = "/api/recent";
if (vaultFilter) url += `?vault=${encodeURIComponent(vaultFilter)}`;
try {
const data = await api(url);
_recentFilesCache = data.files || [];
renderRecentList(_recentFilesCache);
} catch (err) {
console.error("Failed to load recent files:", err);
listEl.innerHTML = '';
if (emptyEl) { emptyEl.classList.remove("hidden"); }
}
}
function renderRecentList(files) {
const listEl = document.getElementById("recent-list");
const emptyEl = document.getElementById("recent-empty");
if (!listEl) return;
listEl.innerHTML = "";
if (!files || files.length === 0) {
if (emptyEl) { emptyEl.classList.remove("hidden"); safeCreateIcons(); }
return;
}
if (emptyEl) emptyEl.classList.add("hidden");
files.forEach((f) => {
const item = el("div", { class: "recent-item", "data-vault": f.vault, "data-path": f.path });
// Header row: time + vault badge
const header = el("div", { class: "recent-item-header" });
const timeSpan = el("span", { class: "recent-time" }, [
icon("clock", 11),
document.createTextNode(f.mtime_human),
]);
const badge = el("span", { class: "recent-vault-badge" }, [document.createTextNode(f.vault)]);
header.appendChild(timeSpan);
header.appendChild(badge);
item.appendChild(header);
// Title
const titleEl = el("div", { class: "recent-item-title" }, [
document.createTextNode(f.title || f.path.split("/").pop()),
]);
item.appendChild(titleEl);
// Path breadcrumb
const pathParts = f.path.split("/");
if (pathParts.length > 1) {
const pathEl = el("div", { class: "recent-item-path" }, [
document.createTextNode(pathParts.slice(0, -1).join(" / ")),
]);
item.appendChild(pathEl);
}
// Preview
if (f.preview) {
const previewEl = el("div", { class: "recent-item-preview" }, [
document.createTextNode(f.preview),
]);
item.appendChild(previewEl);
}
// Tags
if (f.tags && f.tags.length > 0) {
const tagsEl = el("div", { class: "recent-item-tags" });
f.tags.forEach((t) => {
tagsEl.appendChild(el("span", { class: "tag-pill" }, [document.createTextNode(t)]));
});
item.appendChild(tagsEl);
}
// Click handler
item.addEventListener("click", () => {
openFile(f.vault, f.path);
closeMobileSidebar();
});
listEl.appendChild(item);
});
safeCreateIcons();
}
function _humanizeDelta(mtime) {
const delta = (Date.now() / 1000) - mtime;
if (delta < 60) return "à l'instant";
if (delta < 3600) return `il y a ${Math.floor(delta / 60)} min`;
if (delta < 86400) return `il y a ${Math.floor(delta / 3600)} h`;
if (delta < 604800) return `il y a ${Math.floor(delta / 86400)} j`;
return new Date(mtime * 1000).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" });
}
function _refreshRecentTimestamps() {
if (activeSidebarTab !== "recent" || !_recentFilesCache.length) return;
const items = document.querySelectorAll(".recent-item");
items.forEach((item, i) => {
if (i < _recentFilesCache.length) {
const timeSpan = item.querySelector(".recent-time");
if (timeSpan) {
// keep the icon, update text
const textNode = timeSpan.lastChild;
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
textNode.textContent = _humanizeDelta(_recentFilesCache[i].mtime);
}
}
}
});
}
function _populateRecentVaultFilter() {
const select = document.getElementById("recent-vault-filter");
if (!select) return;
// keep first option "Tous les vaults"
while (select.options.length > 1) select.remove(1);
allVaults.forEach((v) => {
const opt = document.createElement("option");
opt.value = v.name;
opt.textContent = v.name;
select.appendChild(opt);
});
}
function initRecentTab() {
const select = document.getElementById("recent-vault-filter");
if (select) {
select.addEventListener("change", () => {
loadRecentFiles(select.value || null);
});
}
// Periodic timestamp refresh (every 60s)
_recentTimestampTimer = setInterval(_refreshRecentTimestamps, 60000);
}
// ---------------------------------------------------------------------------
// Sidebar tabs
// ---------------------------------------------------------------------------
@@ -2283,14 +2427,21 @@
});
const filterInput = document.getElementById("sidebar-filter-input");
if (filterInput) {
filterInput.placeholder = tab === "vaults" ? "Filtrer fichiers..." : "Filtrer tags...";
const placeholders = { vaults: "Filtrer fichiers...", tags: "Filtrer tags...", recent: "" };
filterInput.placeholder = placeholders[tab] || "";
}
const query = filterInput
? (sidebarFilterCaseSensitive ? filterInput.value.trim() : filterInput.value.trim().toLowerCase())
: "";
if (query) {
if (tab === "vaults") performTreeSearch(query);
else filterTagCloud(query);
else if (tab === "tags") filterTagCloud(query);
}
// Auto-load recent files when switching to the recent tab
if (tab === "recent") {
_populateRecentVaultFilter();
const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
}
}
@@ -2477,6 +2628,7 @@
_setField("cfg-title-boost", data.title_boost);
_setField("cfg-tag-boost", data.tag_boost);
_setField("cfg-prefix-exp", data.prefix_max_expansions);
_setField("cfg-recent-limit", data.recent_files_limit || 20);
// Watcher config
_setCheckbox("cfg-watcher-enabled", data.watcher_enabled !== false);
_setCheckbox("cfg-watcher-polling", data.watcher_use_polling === true);
@@ -2527,6 +2679,7 @@
title_boost: _getFieldNum("cfg-title-boost", 3.0),
tag_boost: _getFieldNum("cfg-tag-boost", 2.0),
prefix_max_expansions: _getFieldNum("cfg-prefix-exp", 50),
recent_files_limit: _getFieldNum("cfg-recent-limit", 20),
watcher_enabled: _getCheckbox("cfg-watcher-enabled"),
watcher_use_polling: _getCheckbox("cfg-watcher-polling"),
watcher_polling_interval: _getFieldNum("cfg-watcher-interval", 5.0),
@@ -3871,6 +4024,12 @@
}
}
// Refresh recent tab if it is active
if (activeSidebarTab === "recent") {
const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
}
setTimeout(() => {
connectionState = "connected";
_updateBadge();
@@ -4039,6 +4198,7 @@
initEditor();
initSyncStatus();
initLoginForm();
initRecentTab();
// Check auth status first
const authOk = await AuthManager.initAuth();