v0.3.0: Responsive-Layout Fixes (Script-Position + CSS-Spezifität)
Drei Bugs gefixt die Responsive-Layout kaputt machten: 1. Script-Tag war NACH </body> platziert (HTML-invalid) Fix: Script-Tag ist jetzt VOR </body> im vault.html-Patch 2. CSS-Spezifität zu niedrig (nur .vault-app statt .vault-app.hermes-rc) Fix: 2-Klassen-Selektoren .vault-app.hermes-rc.X schlagen obv's Single-Class-Selektoren 3. Mobile-Overflow nicht kontrolliert Fix: overflow-x: hidden auf .vault-app + .vault-content, word-wrap + overflow-wrap für lange URLs/Codes Außerdem: - Init mit 50ms-Delay nach DOMContentLoaded (gibt obv Zeit für Setup) - Hamburger-Toggle mit e.stopPropagation() - Tablet-Graph als Bottom-Panel (220px) mit border-top statt border-left - Mobile-Graph komplett hidden (war: nur klein) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+254
-158
@@ -7,8 +7,14 @@
|
|||||||
* 1. Current-page highlighting in 3D Graph
|
* 1. Current-page highlighting in 3D Graph
|
||||||
* 2. Click-to-navigate on 3D Graph nodes (Three.js raycasting)
|
* 2. Click-to-navigate on 3D Graph nodes (Three.js raycasting)
|
||||||
* 3. Responsive layout (graph collapses below 1024px, tree below 768px)
|
* 3. Responsive layout (graph collapses below 1024px, tree below 768px)
|
||||||
|
* 4. URL-based navigation (?file=<path>)
|
||||||
*
|
*
|
||||||
* Design notes: see docs/ARCHITECTURE.md
|
* Design notes: see docs/ARCHITECTURE.md
|
||||||
|
*
|
||||||
|
* IMPORTANT: This script runs AFTER obv's main script (which sets up the
|
||||||
|
* layout). So our responsiveLayout() needs to apply classes/styles AFTER
|
||||||
|
* obv's setup, and our CSS must win the specificity battle against obv's
|
||||||
|
* inline styles + media queries.
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -18,60 +24,239 @@
|
|||||||
const HermesCustom = window.HermesCustom;
|
const HermesCustom = window.HermesCustom;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 1. Current-page highlighting
|
// Configuration
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const BREAKPOINTS = {
|
||||||
|
mobile: 768, // <768px
|
||||||
|
tablet: 1024, // 768-1023px
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 1. CSS injection — high specificity to win against obv
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine which file is currently being viewed.
|
* Inject responsive CSS into <head> as a stylesheet (not <style>), so
|
||||||
* Uses URL ?file=<path> parameter, or falls back to extracting from the
|
* the cascade is well-defined. We use:
|
||||||
* page content (the last file fetched via loadFile).
|
* - High specificity selectors (extra class chains)
|
||||||
|
* - !important on critical layout properties
|
||||||
|
* - CSS variables that we toggle via JS
|
||||||
|
*
|
||||||
|
* Strategy: we add a class `hermes-rc` (hermes responsive container) to
|
||||||
|
* the .vault-app element. All our CSS rules start with `.vault-app.hermes-rc`
|
||||||
|
* to beat obv's `.vault-app` selectors.
|
||||||
*/
|
*/
|
||||||
|
HermesCustom.injectResponsiveCSS = function() {
|
||||||
|
if (document.getElementById('hermes-custom-css')) return;
|
||||||
|
|
||||||
|
// Wait until <head> exists
|
||||||
|
const head = document.head || document.getElementsByTagName('head')[0];
|
||||||
|
if (!head) {
|
||||||
|
setTimeout(HermesCustom.injectResponsiveCSS, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'hermes-custom-css';
|
||||||
|
style.textContent = `
|
||||||
|
/* === Hermes Custom: always-applied base (no media query) === */
|
||||||
|
.vault-app.hermes-rc {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100vw !important;
|
||||||
|
overflow-x: hidden !important; /* prevent horizontal scroll on mobile */
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc .vault-content {
|
||||||
|
overflow-x: hidden !important; /* content scrolls vertically only */
|
||||||
|
overflow-y: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
word-wrap: break-word !important; /* long URLs/codes don't overflow */
|
||||||
|
overflow-wrap: break-word !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc .vault-sidebar {
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
max-width: 280px !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc .vault-graph {
|
||||||
|
overflow: hidden !important;
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Mobile (<768px) === */
|
||||||
|
.vault-app.hermes-rc.hermes-mobile {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: 48px 1fr !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-mobile .vault-sidebar {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 48px !important;
|
||||||
|
left: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
width: 280px !important;
|
||||||
|
max-width: 85vw !important;
|
||||||
|
transform: translateX(-100%) !important;
|
||||||
|
transition: transform 0.2s ease !important;
|
||||||
|
z-index: 1000 !important;
|
||||||
|
background: var(--bg-secondary) !important;
|
||||||
|
border-right: 1px solid var(--border) !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-mobile .vault-sidebar.hermes-sidebar-open {
|
||||||
|
transform: translateX(0) !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-mobile .vault-graph {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-mobile .vault-content {
|
||||||
|
grid-column: 1 !important;
|
||||||
|
padding: 16px 20px !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.hermes-mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-mobile .hermes-mobile-toggle { display: inline-block !important; }
|
||||||
|
|
||||||
|
/* === Tablet (768px-1023px) === */
|
||||||
|
.vault-app.hermes-rc.hermes-tablet {
|
||||||
|
grid-template-columns: 200px 1fr !important;
|
||||||
|
grid-template-rows: 48px 1fr 220px !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-tablet .vault-sidebar {
|
||||||
|
width: 200px !important;
|
||||||
|
max-width: 200px !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-tablet .vault-graph {
|
||||||
|
grid-column: 1 / -1 !important;
|
||||||
|
grid-row: 3 !important;
|
||||||
|
height: 220px !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-top: 1px solid var(--border) !important;
|
||||||
|
}
|
||||||
|
.vault-app.hermes-rc.hermes-tablet .vault-content {
|
||||||
|
grid-column: 2 !important;
|
||||||
|
grid-row: 2 !important;
|
||||||
|
padding: 20px 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Desktop (≥1024px): leave obv's default layout alone === */
|
||||||
|
.vault-app.hermes-rc.hermes-desktop {
|
||||||
|
/* inherit obv's grid */
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
head.appendChild(style);
|
||||||
|
console.log('[HermesCustom] CSS injected');
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. Layout application — called on init and on resize
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
HermesCustom.applyLayout = function() {
|
||||||
|
const app = document.querySelector('.vault-app');
|
||||||
|
if (!app) {
|
||||||
|
// obv not loaded yet, retry
|
||||||
|
setTimeout(HermesCustom.applyLayout, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add our marker class (specificity booster)
|
||||||
|
if (!app.classList.contains('hermes-rc')) {
|
||||||
|
app.classList.add('hermes-rc');
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = window.innerWidth;
|
||||||
|
// Remove all mode classes
|
||||||
|
app.classList.remove('hermes-mobile', 'hermes-tablet', 'hermes-desktop');
|
||||||
|
// Sidebar state reset
|
||||||
|
const sidebar = document.querySelector('.vault-sidebar');
|
||||||
|
if (sidebar) sidebar.classList.remove('hermes-sidebar-open');
|
||||||
|
|
||||||
|
if (width < BREAKPOINTS.mobile) {
|
||||||
|
app.classList.add('hermes-mobile');
|
||||||
|
} else if (width < BREAKPOINTS.tablet) {
|
||||||
|
app.classList.add('hermes-tablet');
|
||||||
|
} else {
|
||||||
|
app.classList.add('hermes-desktop');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add hamburger toggle in mobile mode
|
||||||
|
if (width < BREAKPOINTS.mobile) {
|
||||||
|
HermesCustom.ensureHamburgerButton();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HermesCustom.ensureHamburgerButton = function() {
|
||||||
|
if (document.querySelector('.hermes-mobile-toggle')) return;
|
||||||
|
const header = document.querySelector('.vault-header');
|
||||||
|
if (!header) return;
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'hermes-mobile-toggle';
|
||||||
|
btn.textContent = '☰';
|
||||||
|
btn.setAttribute('aria-label', 'Toggle file tree');
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const sidebar = document.querySelector('.vault-sidebar');
|
||||||
|
if (sidebar) sidebar.classList.toggle('hermes-sidebar-open');
|
||||||
|
};
|
||||||
|
header.insertBefore(btn, header.firstChild);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Throttled resize handler
|
||||||
|
HermesCustom.setupResizeHandler = function() {
|
||||||
|
let resizeTimer;
|
||||||
|
let lastWidth = window.innerWidth;
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
clearTimeout(resizeTimer);
|
||||||
|
resizeTimer = setTimeout(() => {
|
||||||
|
if (window.innerWidth !== lastWidth) {
|
||||||
|
lastWidth = window.innerWidth;
|
||||||
|
HermesCustom.applyLayout();
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 3. Current-page highlighting
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
HermesCustom.getCurrentFile = function() {
|
HermesCustom.getCurrentFile = function() {
|
||||||
// Method 1: ?file= query param (preferred)
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const fileParam = params.get('file');
|
const fileParam = params.get('file');
|
||||||
if (fileParam) return fileParam;
|
if (fileParam) return fileParam;
|
||||||
|
|
||||||
// Method 2: Parse from URL hash if obv uses hash-based routing
|
|
||||||
if (window.location.hash) {
|
if (window.location.hash) {
|
||||||
const hashMatch = window.location.hash.match(/file=([^&]+)/);
|
const hashMatch = window.location.hash.match(/file=([^&]+)/);
|
||||||
if (hashMatch) return decodeURIComponent(hashMatch[1]);
|
if (hashMatch) return decodeURIComponent(hashMatch[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 3: Track via the last loadFile() call (set up in init)
|
|
||||||
return HermesCustom._lastLoadedFile || null;
|
return HermesCustom._lastLoadedFile || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all WikiLink targets in the currently loaded file's content.
|
|
||||||
* We hook into the renderMarkdown flow by tracking what was last loaded.
|
|
||||||
*/
|
|
||||||
HermesCustom.getConnectedFiles = function(currentFile) {
|
HermesCustom.getConnectedFiles = function(currentFile) {
|
||||||
if (!currentFile) return [];
|
if (!currentFile) return [];
|
||||||
// Use the global graph data loaded by obv's fetch('/api/vault/graph')
|
|
||||||
const graphData = HermesCustom._graphData;
|
const graphData = HermesCustom._graphData;
|
||||||
if (!graphData) return [];
|
if (!graphData) return [];
|
||||||
|
|
||||||
const connections = graphData.edges
|
const connections = graphData.edges
|
||||||
.filter(e => e.source === currentFile)
|
.filter(e => e.source === currentFile)
|
||||||
.map(e => e.target);
|
.map(e => e.target);
|
||||||
|
|
||||||
// Also outgoing from current file (WikiLinks from current page)
|
|
||||||
// And bidirectional awareness
|
|
||||||
return connections;
|
return connections;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight the current node + connected nodes in the 3D Graph.
|
|
||||||
* Uses obv's global graph variables (graphScene, graphNodeObjects).
|
|
||||||
*/
|
|
||||||
HermesCustom.highlightCurrentNode = function() {
|
HermesCustom.highlightCurrentNode = function() {
|
||||||
const currentFile = HermesCustom.getCurrentFile();
|
const currentFile = HermesCustom.getCurrentFile();
|
||||||
if (!currentFile) return;
|
if (!currentFile) return;
|
||||||
|
if (typeof window.graphNodeObjects === 'undefined' || !window.graphNodeObjects) {
|
||||||
// Hook into obv's globals (set after graph render)
|
|
||||||
if (typeof window.graphScene === 'undefined' || !window.graphNodeObjects) {
|
|
||||||
// Graph not yet built — try again shortly
|
|
||||||
setTimeout(HermesCustom.highlightCurrentNode, 500);
|
setTimeout(HermesCustom.highlightCurrentNode, 500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,26 +264,22 @@
|
|||||||
const connected = new Set(HermesCustom.getConnectedFiles(currentFile));
|
const connected = new Set(HermesCustom.getConnectedFiles(currentFile));
|
||||||
connected.add(currentFile);
|
connected.add(currentFile);
|
||||||
|
|
||||||
// Iterate node meshes and adjust material
|
|
||||||
window.graphNodeObjects.forEach((mesh, nodeId) => {
|
window.graphNodeObjects.forEach((mesh, nodeId) => {
|
||||||
const isCurrent = (nodeId === currentFile);
|
const isCurrent = (nodeId === currentFile);
|
||||||
const isConnected = connected.has(nodeId) && !isCurrent;
|
const isConnected = connected.has(nodeId) && !isCurrent;
|
||||||
|
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
// Bright + slightly larger + emissive glow
|
mesh.material.color.setHex(0xFFD700);
|
||||||
mesh.material.color.setHex(0xFFD700); // gold
|
|
||||||
mesh.material.emissive.setHex(0xFFD700);
|
mesh.material.emissive.setHex(0xFFD700);
|
||||||
mesh.material.emissiveIntensity = 0.6;
|
mesh.material.emissiveIntensity = 0.6;
|
||||||
mesh.scale.set(1.5, 1.5, 1.5);
|
mesh.scale.set(1.5, 1.5, 1.5);
|
||||||
} else if (isConnected) {
|
} else if (isConnected) {
|
||||||
// Brighter than default, smaller than current
|
mesh.material.color.setHex(0x89b4fa);
|
||||||
mesh.material.color.setHex(0x89b4fa); // light blue
|
|
||||||
mesh.material.emissive.setHex(0x89b4fa);
|
mesh.material.emissive.setHex(0x89b4fa);
|
||||||
mesh.material.emissiveIntensity = 0.3;
|
mesh.material.emissiveIntensity = 0.3;
|
||||||
mesh.scale.set(1.1, 1.1, 1.1);
|
mesh.scale.set(1.1, 1.1, 1.1);
|
||||||
} else {
|
} else {
|
||||||
// Dim everything else
|
mesh.material.color.setHex(0x313244);
|
||||||
mesh.material.color.setHex(0x313244); // muted
|
|
||||||
mesh.material.emissiveIntensity = 0;
|
mesh.material.emissiveIntensity = 0;
|
||||||
mesh.material.opacity = 0.2;
|
mesh.material.opacity = 0.2;
|
||||||
mesh.material.transparent = true;
|
mesh.material.transparent = true;
|
||||||
@@ -107,7 +288,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 2. Click-to-navigate on 3D Graph
|
// 4. Click-to-navigate on 3D Graph
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
HermesCustom.setupGraphClickHandler = function() {
|
HermesCustom.setupGraphClickHandler = function() {
|
||||||
@@ -137,146 +318,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log('[HermesCustom] Graph click handler attached');
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 3. Responsive layout
|
// 5. URL-based navigation
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
HermesCustom.responsiveLayout = function() {
|
HermesCustom.updateURL = function(path) {
|
||||||
const applyLayout = () => {
|
const newURL = `?file=${encodeURIComponent(path)}`;
|
||||||
const app = document.querySelector('.vault-app');
|
window.history.pushState({}, '', newURL);
|
||||||
if (!app) return;
|
|
||||||
|
|
||||||
const width = window.innerWidth;
|
|
||||||
if (width < 768) {
|
|
||||||
// Mobile: tree collapses to hamburger, graph hidden by default
|
|
||||||
app.classList.add('mobile-mode');
|
|
||||||
app.classList.remove('graph-visible');
|
|
||||||
} else if (width < 1024) {
|
|
||||||
// Tablet: tree visible, graph collapses to bottom panel
|
|
||||||
app.classList.add('tablet-mode');
|
|
||||||
app.classList.remove('mobile-mode');
|
|
||||||
} else {
|
|
||||||
// Desktop: full 3-panel layout
|
|
||||||
app.classList.remove('mobile-mode', 'tablet-mode');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Throttled resize handler
|
|
||||||
let resizeTimer;
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
clearTimeout(resizeTimer);
|
|
||||||
resizeTimer = setTimeout(applyLayout, 150);
|
|
||||||
});
|
|
||||||
|
|
||||||
applyLayout(); // Initial
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject responsive CSS into <head> (since vault.html doesn't have these)
|
|
||||||
* Must run early so it doesn't conflict with obv's layout.
|
|
||||||
*/
|
|
||||||
HermesCustom.injectResponsiveCSS = function() {
|
|
||||||
if (document.getElementById('hermes-custom-css')) return;
|
|
||||||
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = 'hermes-custom-css';
|
|
||||||
style.textContent = `
|
|
||||||
/* === Tablet (768px - 1023px): graph → bottom panel === */
|
|
||||||
@media (max-width: 1023px) {
|
|
||||||
.vault-app.tablet-mode {
|
|
||||||
grid-template-columns: 240px 1fr !important;
|
|
||||||
grid-template-rows: 48px 1fr 200px !important;
|
|
||||||
}
|
|
||||||
.vault-app.tablet-mode .vault-graph {
|
|
||||||
grid-column: 1 / -1 !important;
|
|
||||||
grid-row: 3 !important;
|
|
||||||
border-left: none !important;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.vault-app.tablet-mode .vault-content {
|
|
||||||
grid-column: 2 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Mobile (<768px): tree → hamburger menu === */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.vault-app.mobile-mode {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
grid-template-rows: 48px 1fr !important;
|
|
||||||
}
|
|
||||||
.vault-app.mobile-mode .vault-sidebar {
|
|
||||||
position: fixed;
|
|
||||||
top: 48px;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 280px;
|
|
||||||
transform: translateX(-100%);
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
.vault-app.mobile-mode .vault-sidebar.open {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
.vault-app.mobile-mode .vault-graph {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Hamburger toggle button (mobile only) === */
|
|
||||||
.hermes-mobile-toggle {
|
|
||||||
display: none;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.hermes-mobile-toggle { display: inline-block; }
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
// Add hamburger button to header
|
|
||||||
const header = document.querySelector('.vault-header');
|
|
||||||
if (header && !document.querySelector('.hermes-mobile-toggle')) {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = 'hermes-mobile-toggle';
|
|
||||||
btn.textContent = '☰ Tree';
|
|
||||||
btn.onclick = () => {
|
|
||||||
const sidebar = document.querySelector('.vault-sidebar');
|
|
||||||
if (sidebar) sidebar.classList.toggle('open');
|
|
||||||
};
|
|
||||||
header.insertBefore(btn, header.firstChild);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Init: wire everything together
|
// 6. loadFile hook — track current file
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
HermesCustom.init = function() {
|
HermesCustom.hookLoadFile = function() {
|
||||||
console.log('[HermesCustom] init');
|
if (typeof window.loadFile !== 'function') {
|
||||||
HermesCustom.injectResponsiveCSS();
|
setTimeout(HermesCustom.hookLoadFile, 200);
|
||||||
HermesCustom.responsiveLayout();
|
return;
|
||||||
|
}
|
||||||
|
if (window.loadFile._hermesPatched) return;
|
||||||
|
|
||||||
// Hook into obv's loadFile to track current file + re-highlight
|
|
||||||
if (typeof window.loadFile === 'function') {
|
|
||||||
const originalLoadFile = window.loadFile;
|
const originalLoadFile = window.loadFile;
|
||||||
window.loadFile = function(path) {
|
window.loadFile = function(path) {
|
||||||
HermesCustom._lastLoadedFile = path;
|
HermesCustom._lastLoadedFile = path;
|
||||||
|
HermesCustom.updateURL(path);
|
||||||
const result = originalLoadFile.apply(this, arguments);
|
const result = originalLoadFile.apply(this, arguments);
|
||||||
// Re-highlight after content renders
|
|
||||||
setTimeout(HermesCustom.highlightCurrentNode, 300);
|
setTimeout(HermesCustom.highlightCurrentNode, 300);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
window.loadFile._hermesPatched = true;
|
||||||
|
console.log('[HermesCustom] loadFile() hooked');
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch graph data once for highlight logic
|
// ============================================================
|
||||||
|
// 7. Graph data fetch (for highlight logic)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
HermesCustom.loadGraphData = function() {
|
||||||
fetch('/api/vault/graph')
|
fetch('/api/vault/graph')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -285,18 +366,33 @@
|
|||||||
HermesCustom.highlightCurrentNode();
|
HermesCustom.highlightCurrentNode();
|
||||||
})
|
})
|
||||||
.catch(err => console.warn('[HermesCustom] Graph fetch failed:', err));
|
.catch(err => console.warn('[HermesCustom] Graph fetch failed:', err));
|
||||||
|
};
|
||||||
|
|
||||||
// Click handler + responsive layout (need to wait for Three.js to init)
|
// ============================================================
|
||||||
|
// Init
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
HermesCustom.init = function() {
|
||||||
|
console.log('[HermesCustom] init v2 (responsive-fix)');
|
||||||
|
HermesCustom.injectResponsiveCSS();
|
||||||
|
HermesCustom.hookLoadFile();
|
||||||
|
HermesCustom.applyLayout();
|
||||||
|
HermesCustom.setupResizeHandler();
|
||||||
|
HermesCustom.loadGraphData();
|
||||||
|
|
||||||
|
// Graph click handler needs Three.js to be ready
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
HermesCustom.setupGraphClickHandler();
|
HermesCustom.setupGraphClickHandler();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-init when DOM is ready
|
// Auto-init
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', HermesCustom.init);
|
document.addEventListener('DOMContentLoaded', HermesCustom.init);
|
||||||
} else {
|
} else {
|
||||||
HermesCustom.init();
|
// DOM already ready — but obv's main script may not have run yet.
|
||||||
|
// Defer slightly to let obv set up the layout first.
|
||||||
|
setTimeout(HermesCustom.init, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
+1
-1
@@ -349,6 +349,6 @@ async function loadGraph() {
|
|||||||
loadTree();
|
loadTree();
|
||||||
loadGraph();
|
loadGraph();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
|
||||||
<script src="vault-custom.js"></script>
|
<script src="vault-custom.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user