ObsiGate/frontend/popout.html

204 lines
7.3 KiB
HTML

<!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: var(--bg-primary);
}
.popout-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
color: var(--text-primary);
z-index: 100;
transition: opacity 0.3s;
}
</style>
</head>
<body class="popup-mode">
<div class="popout-loading" id="loading">Chargement...</div>
<div class="main-layout" style="height: 100vh;">
<main class="content-area" id="content-area" style="margin-top: 15px;">
<!-- JavaScript will inject breadcrumb, title, and markdown content here -->
</main>
</div>
<script>
async function initPopout() {
const pathParts = window.location.pathname.split('/');
const vault = decodeURIComponent(pathParts[2]);
const path = decodeURIComponent(pathParts.slice(3).join('/'));
if (!vault || !path) {
document.getElementById('loading').textContent = "Paramètres invalides";
return;
}
try {
const response = await fetch(`/api/file/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, {
credentials: 'include'
});
if (!response.ok) throw new Error("Erreur lors du chargement (Essayez de vous reconnecter sur le site principal)");
const data = await response.json();
document.title = data.title + " - ObsiGate";
const area = document.getElementById('content-area');
area.innerHTML = "";
// Build breadcrumb (identical structure to app.js)
const parts = path.split("/");
const breadcrumb = document.createElement("div");
breadcrumb.className = "breadcrumb";
breadcrumb.appendChild(createBreadcrumbSep());
breadcrumb.appendChild(createBreadcrumbItem(vault));
parts.forEach((part, i) => {
breadcrumb.appendChild(createBreadcrumbSep());
const name = i === parts.length - 1 ? part.replace(/\.md$/i, "") : part;
breadcrumb.appendChild(createBreadcrumbItem(name));
});
area.appendChild(breadcrumb);
// Build file header
const header = document.createElement("div");
header.className = "file-header";
const titleDiv = document.createElement("div");
titleDiv.className = "file-title";
titleDiv.textContent = data.title;
header.appendChild(titleDiv);
// Tags
if (data.tags && data.tags.length > 0) {
const tagsDiv = document.createElement("div");
tagsDiv.className = "file-tags";
data.tags.forEach(t => {
const span = document.createElement("span");
span.className = "file-tag";
span.textContent = "#" + t;
tagsDiv.appendChild(span);
});
header.appendChild(tagsDiv);
}
// Action buttons
const actionsDiv = document.createElement("div");
actionsDiv.className = "file-actions";
const copyBtn = document.createElement("button");
copyBtn.className = "btn-action";
copyBtn.title = "Copier la source";
copyBtn.innerHTML = '<i data-lucide="copy" style="width:14px;height:14px"></i> Copier';
copyBtn.addEventListener("click", async () => {
try {
const rawUrl = `/api/file/${encodeURIComponent(vault)}/raw?path=${encodeURIComponent(path)}`;
const rawData = await (await fetch(rawUrl, {credentials: 'include'})).json();
await navigator.clipboard.writeText(rawData.raw);
copyBtn.textContent = "Copié !";
setTimeout(() => (copyBtn.innerHTML = '<i data-lucide="copy" style="width:14px;height:14px"></i> Copier'), 1500);
if (window.lucide) window.lucide.createIcons();
} catch (e) {}
});
actionsDiv.appendChild(copyBtn);
const dlBtn = document.createElement("button");
dlBtn.className = "btn-action";
dlBtn.title = "Télécharger";
dlBtn.innerHTML = '<i data-lucide="download" style="width:14px;height:14px"></i> Télécharger';
dlBtn.addEventListener("click", () => {
const a = document.createElement("a");
a.href = `/api/file/${encodeURIComponent(vault)}/download?path=${encodeURIComponent(path)}`;
a.download = path.split('/').pop();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
actionsDiv.appendChild(dlBtn);
header.appendChild(actionsDiv);
area.appendChild(header);
// Frontmatter
if (data.frontmatter && Object.keys(data.frontmatter).length > 0) {
const fmSection = document.createElement("div");
const fmToggle = document.createElement("div");
fmToggle.className = "frontmatter-toggle";
fmToggle.textContent = "▶ Frontmatter";
const fmContent = document.createElement("div");
fmContent.className = "frontmatter-content";
fmContent.textContent = JSON.stringify(data.frontmatter, null, 2);
fmToggle.addEventListener("click", () => {
fmContent.classList.toggle("open");
fmToggle.textContent = fmContent.classList.contains("open") ? "▼ Frontmatter" : "▶ Frontmatter";
});
fmSection.appendChild(fmToggle);
fmSection.appendChild(fmContent);
area.appendChild(fmSection);
}
// Markdown content wrapper
const mdDiv = document.createElement("div");
mdDiv.className = "md-content";
mdDiv.id = "file-rendered-content";
mdDiv.innerHTML = data.html;
area.appendChild(mdDiv);
// Render lucide icons
if (window.lucide) {
window.lucide.createIcons();
}
// 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;
}
}
function createBreadcrumbSep() {
const sep = document.createElement("span");
sep.className = "sep";
sep.textContent = " / ";
return sep;
}
function createBreadcrumbItem(text) {
const item = document.createElement("span");
item.className = "breadcrumb-item";
item.textContent = text;
return item;
}
window.onload = initPopout;
</script>
</body>
</html>