feat: implement initial frontend SPA with advanced search, query parsing, and autocomplete suggestions.

This commit is contained in:
Bruno Charest 2026-03-24 09:41:03 -04:00
parent 1bfe2359d9
commit 5e300f9ada
3 changed files with 148 additions and 3 deletions

View File

@ -1455,6 +1455,14 @@ async def api_diagnostics(current_user=Depends(require_admin)):
if FRONTEND_DIR.exists():
app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static")
@app.get("/popout/{vault_name}/{path:path}")
async def serve_popout(vault_name: str, path: str):
"""Serve the minimalist popout page for a specific file."""
popout_file = FRONTEND_DIR / "popout.html"
if popout_file.exists():
return HTMLResponse(content=popout_file.read_text(encoding="utf-8"))
raise HTTPException(status_code=404, detail="Popout template not found")
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
"""Serve the SPA index.html for all non-API routes."""

View File

@ -2179,9 +2179,8 @@
document.createTextNode("Nouvelle fenêtre"),
]);
openNewWindowBtn.addEventListener("click", () => {
const currentUrl = window.location.origin + window.location.pathname;
const fileUrl = `${currentUrl}?popup=true#file=${encodeURIComponent(data.vault)}:${encodeURIComponent(data.path)}`;
window.open(fileUrl, '_blank', 'menubar=no,toolbar=no,location=no,status=no,width=1000,height=800');
const popoutUrl = `/popout/${encodeURIComponent(data.vault)}/${encodeURIComponent(data.path)}`;
window.open(popoutUrl, `popout_${data.vault}_${data.path.replace(/[^a-zA-Z0-9]/g, '_')}`, 'width=1000,height=700,menubar=no,toolbar=no,location=no,status=no,resizable=yes,scrollbars=no');
});
// Frontmatter

138
frontend/popout.html Normal file
View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ObsiGate - Popout</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" id="hljs-theme-dark">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: #0d1117; /* GitHub-like dark background */
color: #e6edf3;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.popout-container {
display: flex;
flex-direction: column;
height: 100vh;
border: 1px solid #30363d;
box-sizing: border-box;
}
.popout-header {
padding: 12px 20px;
background: #161b22;
border-bottom: 1px solid #30363d;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.popout-title {
font-size: 0.9rem;
font-weight: 600;
color: #7d8590;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.popout-content {
flex: 1;
padding: 3.5rem 2rem;
overflow-y: auto;
}
.main-wrapper {
max-width: 840px;
margin: 0 auto;
padding: 2.5rem;
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
box-shadow: 0 12px 48px rgba(0,0,0,0.3);
}
.markdown-body {
line-height: 1.7;
color: #e6edf3;
}
/* Hide default scrollbar but keep functionality */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #484f58; }
.popout-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: #0d1117;
z-index: 100;
transition: opacity 0.3s;
}
</style>
</head>
<body class="popup-mode">
<div class="popout-loading" id="loading">Chargement...</div>
<div class="popout-container">
<div class="popout-header">
<div class="popout-title" id="file-title">ObsiGate</div>
<div style="font-size: 0.75rem; color: #7d8590;" id="vault-name"></div>
</div>
<div class="popout-content">
<div class="main-wrapper">
<div id="content-area" class="markdown-body"></div>
</div>
</div>
</div>
<script>
async function initPopout() {
const pathParts = window.location.pathname.split('/');
// Expected: /popout/{vault}/{path...}
const vault = decodeURIComponent(pathParts[2]);
const path = decodeURIComponent(pathParts.slice(3).join('/'));
if (!vault || !path) {
document.getElementById('loading').textContent = "Paramètres invalides";
return;
}
document.getElementById('vault-name').textContent = vault;
document.getElementById('file-title').textContent = path.split('/').pop();
try {
const response = await fetch(`/api/file/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`);
if (!response.ok) throw new Error("Erreur lors du chargement du fichier");
const data = await response.json();
document.getElementById('content-area').innerHTML = data.html;
document.title = data.title + " - ObsiGate";
// Highlight code blocks
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
// Hide loading
document.getElementById('loading').style.opacity = '0';
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 300);
} catch (err) {
document.getElementById('loading').textContent = err.message;
}
}
window.onload = initPopout;
</script>
</body>
</html>