feat: Introduce a dedicated popout page for standalone file viewing, including content rendering and actions.
This commit is contained in:
parent
46e054f5dd
commit
f963c37012
@ -14,57 +14,8 @@
|
||||
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;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
.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;
|
||||
@ -72,7 +23,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0d1117;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
z-index: 100;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
@ -81,22 +33,15 @@
|
||||
<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 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('/');
|
||||
// Expected: /popout/{vault}/{path...}
|
||||
const vault = decodeURIComponent(pathParts[2]);
|
||||
const path = decodeURIComponent(pathParts.slice(3).join('/'));
|
||||
|
||||
@ -105,19 +50,123 @@
|
||||
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)}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) throw new Error("Erreur lors du chargement du fichier");
|
||||
if (!response.ok) throw new Error("Erreur lors du chargement (Essayez de vous reconnecter sur le site principal)");
|
||||
|
||||
const data = await response.json();
|
||||
document.getElementById('content-area').innerHTML = data.html;
|
||||
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);
|
||||
@ -134,6 +183,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user