feat: implement unified DocumentNavigator with lazy loading for all modes
- Add DocumentNavigator UI element for document navigation across viewing and editing modes - Implement lazy loading approach where control appears immediately but navigation content builds on-demand - Position controls on left side following UI convention for consistent navigation experience - Add scroll spy functionality for current section detection - Include responsive design with mobile auto-hide - Create comprehensive development guardrails to prevent JavaScript corruption - Add JavaScript validation tool for syntax error detection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1176,6 +1176,149 @@ class CleanDocumentManager:
|
||||
}} catch (error) {{
|
||||
console.error("Scroll indicators failed to initialize:", error);
|
||||
}}
|
||||
|
||||
// Step 4: Initialize DocumentNavigator (lazy loading for all modes)
|
||||
try {{
|
||||
const documentNavigator = {{
|
||||
navElement: null,
|
||||
isExpanded: false,
|
||||
|
||||
createControl: function() {{
|
||||
console.log("📋 Creating DocumentNavigator control for view mode...");
|
||||
|
||||
this.navElement = document.createElement('nav');
|
||||
this.navElement.className = 'document-navigator';
|
||||
this.navElement.innerHTML = `
|
||||
<button class="navigator-toggle" aria-label="Document Navigation">☰</button>
|
||||
<div class="navigator-panel" style="display: none;">
|
||||
<div class="navigator-header">
|
||||
<h3>Contents</h3>
|
||||
<button class="navigator-close">✕</button>
|
||||
</div>
|
||||
<div class="navigator-content">Loading...</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Position on left side following UI convention
|
||||
this.navElement.style.cssText = `
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #e1e5e9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(8px);
|
||||
width: 40px;
|
||||
transition: width 0.3s ease;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
`;
|
||||
|
||||
// Style toggle button
|
||||
const toggleBtn = this.navElement.querySelector('.navigator-toggle');
|
||||
toggleBtn.style.cssText = `
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
transition: color 0.2s ease;
|
||||
`;
|
||||
|
||||
// Handle click to build navigation on-demand
|
||||
toggleBtn.addEventListener('click', () => {{
|
||||
console.log("📋 Navigator toggle clicked - building navigation...");
|
||||
this.buildNavigation();
|
||||
}});
|
||||
|
||||
// Close button handler
|
||||
const closeBtn = this.navElement.querySelector('.navigator-close');
|
||||
closeBtn.addEventListener('click', () => {{
|
||||
this.collapse();
|
||||
}});
|
||||
|
||||
// Responsive behavior
|
||||
window.addEventListener('resize', () => {{
|
||||
if (window.innerWidth <= 768) {{
|
||||
this.navElement.style.display = 'none';
|
||||
}} else {{
|
||||
this.navElement.style.display = '';
|
||||
}}
|
||||
}});
|
||||
|
||||
document.body.appendChild(this.navElement);
|
||||
|
||||
// Hide on mobile
|
||||
if (window.innerWidth <= 768) {{
|
||||
this.navElement.style.display = 'none';
|
||||
}}
|
||||
|
||||
console.log("📋 DocumentNavigator control created");
|
||||
}},
|
||||
|
||||
buildNavigation: function() {{
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
const content = this.navElement.querySelector('.navigator-content');
|
||||
|
||||
// Build navigation content from current DOM
|
||||
const headings = document.querySelectorAll('h1, h2, h3');
|
||||
console.log("📋 Found headings for navigation:", headings.length);
|
||||
|
||||
if (headings.length === 0) {{
|
||||
content.innerHTML = '<p style="padding: 1rem; color: #666;">No headings found</p>';
|
||||
}} else {{
|
||||
let navHtml = '';
|
||||
headings.forEach((heading, index) => {{
|
||||
if (!heading.id) {{
|
||||
heading.id = `heading-${{index + 1}}`;
|
||||
}}
|
||||
const level = parseInt(heading.tagName.substring(1));
|
||||
const indent = (level - 1) * 1;
|
||||
navHtml += `
|
||||
<a href="#${{heading.id}}"
|
||||
style="display: block; padding: 0.5rem; margin-left: ${{indent}}rem;
|
||||
text-decoration: none; color: #333; font-size: 0.9rem;
|
||||
border-radius: 4px; cursor: pointer;"
|
||||
onmouseover="this.style.backgroundColor='#f5f5f5'"
|
||||
onmouseout="this.style.backgroundColor=''"
|
||||
onclick="event.preventDefault(); document.getElementById('${{heading.id}}').scrollIntoView({{behavior: 'smooth'}}); if (window.innerWidth <= 768) setTimeout(() => documentNavigator.collapse(), 500);">
|
||||
${{heading.textContent.trim()}}
|
||||
</a>
|
||||
`;
|
||||
}});
|
||||
content.innerHTML = navHtml;
|
||||
}}
|
||||
|
||||
// Show panel
|
||||
this.expand();
|
||||
}},
|
||||
|
||||
expand: function() {{
|
||||
this.isExpanded = true;
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
this.navElement.style.width = '280px';
|
||||
panel.style.display = 'block';
|
||||
}},
|
||||
|
||||
collapse: function() {{
|
||||
this.isExpanded = false;
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
panel.style.display = 'none';
|
||||
this.navElement.style.width = '40px';
|
||||
}}
|
||||
}};
|
||||
|
||||
// Initialize the DocumentNavigator control
|
||||
documentNavigator.createControl();
|
||||
|
||||
// Make globally available for mobile collapse
|
||||
window.documentNavigator = documentNavigator;
|
||||
}} catch (error) {{
|
||||
console.error("DocumentNavigator failed to initialize:", error);
|
||||
}}
|
||||
}});
|
||||
|
||||
// Handle CDN loading errors
|
||||
@@ -1237,6 +1380,127 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Create document controls
|
||||
documentControls.create();
|
||||
|
||||
// Create DocumentNavigator for edit mode (lazy loading)
|
||||
const documentNavigator = {
|
||||
navElement: null,
|
||||
isExpanded: false,
|
||||
|
||||
createControl: function() {
|
||||
console.log("📋 Creating DocumentNavigator control for edit mode...");
|
||||
|
||||
this.navElement = document.createElement('nav');
|
||||
this.navElement.className = 'document-navigator edit-mode';
|
||||
this.navElement.innerHTML = `
|
||||
<button class="navigator-toggle" aria-label="Document Navigation">☰</button>
|
||||
<div class="navigator-panel" style="display: none;">
|
||||
<div class="navigator-header">
|
||||
<h3>Contents</h3>
|
||||
<button class="navigator-close">✕</button>
|
||||
</div>
|
||||
<div class="navigator-content">Loading...</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Position on left side following UI convention
|
||||
this.navElement.style.cssText = `
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 20px;
|
||||
z-index: 1001;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #e1e5e9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(8px);
|
||||
width: 40px;
|
||||
transition: width 0.3s ease;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
`;
|
||||
|
||||
// Style toggle button
|
||||
const toggleBtn = this.navElement.querySelector('.navigator-toggle');
|
||||
toggleBtn.style.cssText = `
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
transition: color 0.2s ease;
|
||||
`;
|
||||
|
||||
// Handle click to build navigation on-demand
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
console.log("📋 Navigator toggle clicked - building navigation...");
|
||||
this.buildNavigation();
|
||||
});
|
||||
|
||||
// Close button handler
|
||||
const closeBtn = this.navElement.querySelector('.navigator-close');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.collapse();
|
||||
});
|
||||
|
||||
document.body.appendChild(this.navElement);
|
||||
console.log("📋 DocumentNavigator control created");
|
||||
},
|
||||
|
||||
buildNavigation: function() {
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
const content = this.navElement.querySelector('.navigator-content');
|
||||
|
||||
// Build navigation content from current DOM
|
||||
const headings = document.querySelectorAll('h1, h2, h3');
|
||||
console.log("📋 Found headings for navigation:", headings.length);
|
||||
|
||||
if (headings.length === 0) {
|
||||
content.innerHTML = '<p style="padding: 1rem; color: #666;">No headings found</p>';
|
||||
} else {
|
||||
let navHtml = '';
|
||||
headings.forEach((heading, index) => {
|
||||
if (!heading.id) {
|
||||
heading.id = `heading-${index + 1}`;
|
||||
}
|
||||
const level = parseInt(heading.tagName.substring(1));
|
||||
const indent = (level - 1) * 1;
|
||||
navHtml += `
|
||||
<a href="#${heading.id}"
|
||||
style="display: block; padding: 0.5rem; margin-left: ${indent}rem;
|
||||
text-decoration: none; color: #333; font-size: 0.9rem;
|
||||
border-radius: 4px; cursor: pointer;"
|
||||
onmouseover="this.style.backgroundColor='#f5f5f5'"
|
||||
onmouseout="this.style.backgroundColor=''"
|
||||
onclick="event.preventDefault(); document.getElementById('${heading.id}').scrollIntoView({behavior: 'smooth'});">
|
||||
${heading.textContent.trim()}
|
||||
</a>
|
||||
`;
|
||||
});
|
||||
content.innerHTML = navHtml;
|
||||
}
|
||||
|
||||
// Show panel
|
||||
this.expand();
|
||||
},
|
||||
|
||||
expand: function() {
|
||||
this.isExpanded = true;
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
this.navElement.style.width = '300px';
|
||||
panel.style.display = 'block';
|
||||
},
|
||||
|
||||
collapse: function() {
|
||||
this.isExpanded = false;
|
||||
const panel = this.navElement.querySelector('.navigator-panel');
|
||||
panel.style.display = 'none';
|
||||
this.navElement.style.width = '40px';
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the DocumentNavigator control
|
||||
documentNavigator.createControl();
|
||||
|
||||
// Wire up event handlers
|
||||
documentControls.setEventHandlers({
|
||||
'save-document': () => {
|
||||
|
||||
Reference in New Issue
Block a user