feat: Implement the initial ObsiGate vanilla JavaScript single-page application frontend.

This commit is contained in:
Bruno Charest 2026-03-26 20:32:28 -04:00
parent 3ae430aaa6
commit ec97d75e59

View File

@ -1942,15 +1942,15 @@
/**
* Refreshes the sidebar tree while preserving the expanded state of vaults and folders.
* Optimized to avoid a full sidebar wipe and minimize visible loading states.
*/
async function refreshSidebarTreePreservingState() {
// 1. Capture expanded vaults
// 1. Capture expanded states
const expandedVaults = Array.from(document.querySelectorAll(".vault-item")).filter(v => {
const children = document.getElementById(`vault-children-${v.dataset.vault}`);
return children && !children.classList.contains("collapsed");
}).map(v => v.dataset.vault);
// 2. Capture expanded directories
const expandedDirs = Array.from(document.querySelectorAll(".tree-item[data-path]")).filter(item => {
const vault = item.dataset.vault;
const path = item.dataset.path;
@ -1958,39 +1958,53 @@
return children && !children.classList.contains("collapsed");
}).map(item => ({ vault: item.dataset.vault, path: item.dataset.path }));
// 3. Capture selected path
const selectedItem = document.querySelector(".tree-item.path-selected");
const selectedState = selectedItem ? { vault: selectedItem.dataset.vault, path: selectedItem.dataset.path } : null;
// 4. Reload vaults (rebuilds root list)
await loadVaults();
// 2. Soft update: load vaults to update names/counts without wiping the tree
try {
const vaults = await api("/api/vaults");
allVaults = vaults;
vaults.forEach(v => {
const vItem = document.querySelector(`.vault-item[data-vault="${CSS.escape(v.name)}"]`);
if (vItem) {
const badge = vItem.querySelector(".badge-small");
if (badge) badge.textContent = `(${v.file_count})`;
}
});
} catch (e) {
console.warn("Soft vault refresh failed, falling back to full reload", e);
await loadVaults();
}
// 5. Re-expand vaults
// 3. Refresh expanded vaults
// If we didn't wipe the tree, we only need to call loadDirectory to update the children
for (const vName of expandedVaults) {
const vItem = document.querySelector(`.vault-item[data-vault="${CSS.escape(vName)}"]`);
if (vItem) {
await toggleVault(vItem, vName, true);
const container = document.getElementById(`vault-children-${vName}`);
if (container) {
await loadDirectory(vName, "", container);
}
}
// 6. Re-expand directories (parents first)
// 4. Re-expand directories (parents first)
expandedDirs.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
for (const dir of expandedDirs) {
const dItem = document.querySelector(`.tree-item[data-vault="${CSS.escape(dir.vault)}"][data-path="${CSS.escape(dir.path)}"]`);
const container = document.getElementById(`dir-${dir.vault}-${dir.path}`);
if (dItem && container && container.classList.contains("collapsed")) {
if (dItem && container) {
// If it was already expanded but currently has its old content, loadDirectory will update it
try {
await loadDirectory(dir.vault, dir.path, container);
container.classList.remove("collapsed");
const chev = dItem.querySelector("[data-lucide]");
if (chev) chev.setAttribute("data-lucide", "chevron-down");
} catch (e) {
console.error(`Failed to re-expand ${dir.vault}/${dir.path}`, e);
console.error(`Failed to refresh directory ${dir.vault}/${dir.path}`, e);
}
}
}
// 7. Restore selection
// 5. Restore selection
if (selectedState) {
await focusPathInSidebar(selectedState.vault, selectedState.path, { alignToTop: false });
}
@ -2091,8 +2105,11 @@
}
async function loadDirectory(vaultName, dirPath, container) {
// Show inline loading indicator while fetching directory contents
container.innerHTML = '<div class="tree-loading"><div class="loading-spinner" style="width:16px;height:16px;border-width:2px"></div></div>';
// Only show the loading spinner if the container is currently empty
const isEmpty = container.children.length === 0;
if (isEmpty) {
container.innerHTML = '<div class="tree-loading"><div class="loading-spinner" style="width:16px;height:16px;border-width:2px"></div></div>';
}
var data;
try {
@ -4289,6 +4306,7 @@
function smallBadge(count) {
const s = document.createElement("span");
s.className = "badge-small";
s.style.cssText = "font-size:0.68rem;color:var(--text-muted);margin-left:4px";
s.textContent = `(${count})`;
return s;