diff --git a/markitect/clean_document_manager.py b/markitect/clean_document_manager.py
index b3270667..d9b71ad9 100644
--- a/markitect/clean_document_manager.py
+++ b/markitect/clean_document_manager.py
@@ -1182,6 +1182,9 @@ class CleanDocumentManager:
const documentNavigator = {{
navElement: null,
isExpanded: false,
+ isDragging: false,
+ dragOffset: {{ x: 0, y: 0 }},
+ originalPosition: {{ top: '80px', left: '20px' }},
createControl: function() {{
console.log("📋 Creating DocumentNavigator control for view mode...");
@@ -1192,6 +1195,7 @@ class CleanDocumentManager:
@@ -1230,8 +1234,12 @@ class CleanDocumentManager:
// Handle click to build navigation on-demand
toggleBtn.addEventListener('click', () => {{
- console.log("📋 Navigator toggle clicked - building navigation...");
- this.buildNavigation();
+ if (this.isExpanded) {{
+ this.collapse();
+ }} else {{
+ console.log("📋 Navigator toggle clicked - building navigation...");
+ this.buildNavigation();
+ }}
}});
// Close button handler
@@ -1262,9 +1270,69 @@ class CleanDocumentManager:
buildNavigation: function() {{
const panel = this.navElement.querySelector('.navigator-panel');
const content = this.navElement.querySelector('.navigator-content');
+ const header = this.navElement.querySelector('.navigator-header');
+
+ // Style the header to show icon, title, and close button in one line
+ // Match the height of the collapsed icon state (40px)
+ header.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 40px;
+ padding: 0 1rem;
+ border-bottom: 1px solid #eee;
+ margin-bottom: 0;
+ `;
+
+ const icon = header.querySelector('.navigator-icon');
+ if (icon) {{
+ icon.style.cssText = `
+ font-size: 16px;
+ color: #666;
+ margin-right: 0.5rem;
+ cursor: grab;
+ user-select: none;
+ `;
+
+ // Make icon draggable
+ this.setupDragHandlers(icon);
+ }}
+
+ const title = header.querySelector('h3');
+ if (title) {{
+ title.style.cssText = `
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 600;
+ flex-grow: 1;
+ line-height: 1;
+ `;
+ }}
+
+ const closeBtn = header.querySelector('.navigator-close');
+ if (closeBtn) {{
+ closeBtn.style.cssText = `
+ background: none;
+ border: none;
+ font-size: 14px;
+ cursor: pointer;
+ color: #666;
+ padding: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ `;
+ }}
// Build navigation content from current DOM
- const headings = document.querySelectorAll('h1, h2, h3');
+ const allHeadings = document.querySelectorAll('h1, h2, h3');
+ // Filter out headings that contain "Contents" or similar navigation-related text
+ const headings = Array.from(allHeadings).filter(heading => {{
+ const text = heading.textContent.trim().toLowerCase();
+ return !text.includes('contents') && !text.includes('table of contents') && !text.includes('navigation');
+ }});
console.log("📋 Found headings for navigation:", headings.length);
if (headings.length === 0) {{
@@ -1292,22 +1360,74 @@ class CleanDocumentManager:
content.innerHTML = navHtml;
}}
+ // Style the content area to work with compact header
+ content.style.cssText = `
+ padding: 0.5rem;
+ max-height: calc(80vh - 40px);
+ overflow-y: auto;
+ `;
+
// Show panel
this.expand();
- }},
+ }},
expand: function() {{
this.isExpanded = true;
const panel = this.navElement.querySelector('.navigator-panel');
+ const toggleBtn = this.navElement.querySelector('.navigator-toggle');
this.navElement.style.width = '280px';
panel.style.display = 'block';
+ toggleBtn.style.display = 'none';
}},
collapse: function() {{
this.isExpanded = false;
const panel = this.navElement.querySelector('.navigator-panel');
+ const toggleBtn = this.navElement.querySelector('.navigator-toggle');
panel.style.display = 'none';
this.navElement.style.width = '40px';
+ toggleBtn.style.display = 'block';
+
+ // Reset position to original location
+ this.navElement.style.top = this.originalPosition.top;
+ this.navElement.style.left = this.originalPosition.left;
+ }},
+
+ setupDragHandlers: function(dragElement) {{
+ dragElement.addEventListener('mousedown', (e) => {{
+ this.isDragging = true;
+ const rect = this.navElement.getBoundingClientRect();
+ this.dragOffset.x = e.clientX - rect.left;
+ this.dragOffset.y = e.clientY - rect.top;
+
+ dragElement.style.cursor = 'grabbing';
+
+ e.preventDefault();
+ }});
+
+ document.addEventListener('mousemove', (e) => {{
+ if (!this.isDragging || !this.isExpanded) return;
+
+ const newX = e.clientX - this.dragOffset.x;
+ const newY = e.clientY - this.dragOffset.y;
+
+ // Keep within viewport bounds
+ const maxX = window.innerWidth - this.navElement.offsetWidth;
+ const maxY = window.innerHeight - this.navElement.offsetHeight;
+
+ const boundedX = Math.max(0, Math.min(newX, maxX));
+ const boundedY = Math.max(0, Math.min(newY, maxY));
+
+ this.navElement.style.left = boundedX + 'px';
+ this.navElement.style.top = boundedY + 'px';
+ }});
+
+ document.addEventListener('mouseup', () => {{
+ if (this.isDragging) {{
+ this.isDragging = false;
+ dragElement.style.cursor = 'grab';
+ }}
+ }});
}}
}};
@@ -1384,6 +1504,9 @@ document.addEventListener('DOMContentLoaded', function() {
const documentNavigator = {
navElement: null,
isExpanded: false,
+ isDragging: false,
+ dragOffset: { x: 0, y: 0 },
+ originalPosition: { top: '80px', left: '20px' },
createControl: function() {
console.log("📋 Creating DocumentNavigator control for edit mode...");
@@ -1394,6 +1517,7 @@ document.addEventListener('DOMContentLoaded', function() {
@@ -1432,8 +1556,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Handle click to build navigation on-demand
toggleBtn.addEventListener('click', () => {
- console.log("📋 Navigator toggle clicked - building navigation...");
- this.buildNavigation();
+ if (this.isExpanded) {
+ this.collapse();
+ } else {
+ console.log("📋 Navigator toggle clicked - building navigation...");
+ this.buildNavigation();
+ }
});
// Close button handler
@@ -1449,9 +1577,69 @@ document.addEventListener('DOMContentLoaded', function() {
buildNavigation: function() {
const panel = this.navElement.querySelector('.navigator-panel');
const content = this.navElement.querySelector('.navigator-content');
+ const header = this.navElement.querySelector('.navigator-header');
+
+ // Style the header to show icon, title, and close button in one line
+ // Match the height of the collapsed icon state (40px)
+ header.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 40px;
+ padding: 0 1rem;
+ border-bottom: 1px solid #eee;
+ margin-bottom: 0;
+ `;
+
+ const icon = header.querySelector('.navigator-icon');
+ if (icon) {
+ icon.style.cssText = `
+ font-size: 16px;
+ color: #666;
+ margin-right: 0.5rem;
+ cursor: grab;
+ user-select: none;
+ `;
+
+ // Make icon draggable
+ this.setupDragHandlers(icon);
+ }
+
+ const title = header.querySelector('h3');
+ if (title) {
+ title.style.cssText = `
+ margin: 0;
+ font-size: 0.9rem;
+ font-weight: 600;
+ flex-grow: 1;
+ line-height: 1;
+ `;
+ }
+
+ const closeBtn = header.querySelector('.navigator-close');
+ if (closeBtn) {
+ closeBtn.style.cssText = `
+ background: none;
+ border: none;
+ font-size: 14px;
+ cursor: pointer;
+ color: #666;
+ padding: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ `;
+ }
// Build navigation content from current DOM
- const headings = document.querySelectorAll('h1, h2, h3');
+ const allHeadings = document.querySelectorAll('h1, h2, h3');
+ // Filter out headings that contain "Contents" or similar navigation-related text
+ const headings = Array.from(allHeadings).filter(heading => {
+ const text = heading.textContent.trim().toLowerCase();
+ return !text.includes('contents') && !text.includes('table of contents') && !text.includes('navigation');
+ });
console.log("📋 Found headings for navigation:", headings.length);
if (headings.length === 0) {
@@ -1479,6 +1667,13 @@ document.addEventListener('DOMContentLoaded', function() {
content.innerHTML = navHtml;
}
+ // Style the content area to work with compact header
+ content.style.cssText = `
+ padding: 0.5rem;
+ max-height: calc(80vh - 40px);
+ overflow-y: auto;
+ `;
+
// Show panel
this.expand();
},
@@ -1486,15 +1681,60 @@ document.addEventListener('DOMContentLoaded', function() {
expand: function() {
this.isExpanded = true;
const panel = this.navElement.querySelector('.navigator-panel');
+ const toggleBtn = this.navElement.querySelector('.navigator-toggle');
this.navElement.style.width = '300px';
panel.style.display = 'block';
+ toggleBtn.style.display = 'none';
},
collapse: function() {
this.isExpanded = false;
const panel = this.navElement.querySelector('.navigator-panel');
+ const toggleBtn = this.navElement.querySelector('.navigator-toggle');
panel.style.display = 'none';
this.navElement.style.width = '40px';
+ toggleBtn.style.display = 'block';
+
+ // Reset position to original location
+ this.navElement.style.top = this.originalPosition.top;
+ this.navElement.style.left = this.originalPosition.left;
+ },
+
+ setupDragHandlers: function(dragElement) {
+ dragElement.addEventListener('mousedown', (e) => {
+ this.isDragging = true;
+ const rect = this.navElement.getBoundingClientRect();
+ this.dragOffset.x = e.clientX - rect.left;
+ this.dragOffset.y = e.clientY - rect.top;
+
+ dragElement.style.cursor = 'grabbing';
+
+ e.preventDefault();
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!this.isDragging || !this.isExpanded) return;
+
+ const newX = e.clientX - this.dragOffset.x;
+ const newY = e.clientY - this.dragOffset.y;
+
+ // Keep within viewport bounds
+ const maxX = window.innerWidth - this.navElement.offsetWidth;
+ const maxY = window.innerHeight - this.navElement.offsetHeight;
+
+ const boundedX = Math.max(0, Math.min(newX, maxX));
+ const boundedY = Math.max(0, Math.min(newY, maxY));
+
+ this.navElement.style.left = boundedX + 'px';
+ this.navElement.style.top = boundedY + 'px';
+ });
+
+ document.addEventListener('mouseup', () => {
+ if (this.isDragging) {
+ this.isDragging = false;
+ dragElement.style.cursor = 'grab';
+ }
+ });
}
};