diff --git a/markitect/static/js/components/debug-panel.js b/markitect/static/js/components/debug-panel.js new file mode 100644 index 00000000..d22706a0 --- /dev/null +++ b/markitect/static/js/components/debug-panel.js @@ -0,0 +1,191 @@ +/** + * DebugPanel Component + * + * Extracted from monolithic editor.js as part of architecture refactoring. + * Handles debug message display and management for client-side debugging. + * + * Dependencies: + * - None (standalone component) + */ + +/** + * DebugPanel - Manages debug message display and interaction + */ +class DebugPanel { + constructor() { + this.messages = []; + this.isActive = false; + this.maxMessages = 1000; // Keep last 1000 messages + } + + /** + * Add a debug message + */ + addMessage(message, category = 'INFO') { + const messageObj = { + message, + category, + timestamp: new Date().toLocaleTimeString() + }; + + this.messages.push(messageObj); + + // Keep only last maxMessages + if (this.messages.length > this.maxMessages) { + this.messages = this.messages.slice(-this.maxMessages); + } + + // Auto-update if panel is visible + if (this.isActive) { + this.update(); + } + } + + /** + * Toggle the debug panel on/off + */ + toggle() { + const debugContainer = document.getElementById('debug-messages-container'); + const debugButton = document.getElementById('toggle-debug'); + + if (!debugContainer || !debugButton) { + console.warn('DebugPanel: Required DOM elements not found'); + return; + } + + if (this.isActive) { + this.hide(); + } else { + this.show(); + } + } + + /** + * Show the debug panel + */ + show() { + const debugContainer = document.getElementById('debug-messages-container'); + const debugButton = document.getElementById('toggle-debug'); + + if (!debugContainer || !debugButton) { + console.warn('DebugPanel: Required DOM elements not found'); + return; + } + + debugContainer.style.display = 'block'; + debugButton.textContent = '🔍 Debug (ON)'; + debugButton.style.background = '#28a745'; + this.isActive = true; + this.update(); + } + + /** + * Hide the debug panel + */ + hide() { + const debugContainer = document.getElementById('debug-messages-container'); + const debugButton = document.getElementById('toggle-debug'); + + if (!debugContainer || !debugButton) { + console.warn('DebugPanel: Required DOM elements not found'); + return; + } + + debugContainer.style.display = 'none'; + debugButton.textContent = '🔍 Debug'; + debugButton.style.background = '#6c757d'; + this.isActive = false; + } + + /** + * Update the debug panel with current messages + */ + update() { + const debugContainer = document.getElementById('debug-messages-container'); + if (!debugContainer || !this.isActive) { + return; + } + + if (this.messages.length === 0) { + debugContainer.innerHTML = '
No debug messages yet. Click sections to generate debug output.
'; + return; + } + + // Show the last 50 messages in reverse order (newest first) + const recentMessages = this.messages.slice(-50).reverse(); + + const messagesHtml = recentMessages.map(msg => { + const categoryColor = { + 'INFO': '#17a2b8', + 'WARNING': '#ffc107', + 'ERROR': '#dc3545', + 'SUCCESS': '#28a745', + 'DEBUG': '#6f42c1' + }[msg.category] || '#6c757d'; + + return ` +
+ [${msg.timestamp}] + ${msg.category}: + ${msg.message} +
+ `; + }).join(''); + + debugContainer.innerHTML = ` +
+ Debug Messages (${this.messages.length} total, showing last ${recentMessages.length}) + +
+
+ ${messagesHtml} +
+ `; + + // Add event listener for clear button + const clearBtn = debugContainer.querySelector('#debug-clear-btn'); + if (clearBtn) { + clearBtn.addEventListener('click', () => { + this.clear(); + }); + } + + // Auto-scroll to bottom to show newest messages + const scrollContainer = debugContainer.querySelector('div[style*="overflow-y"]'); + if (scrollContainer) { + scrollContainer.scrollTop = scrollContainer.scrollHeight; + } + } + + /** + * Clear all debug messages + */ + clear() { + this.messages = []; + this.update(); + } + + /** + * Get the number of messages + */ + getMessageCount() { + return this.messages.length; + } + + /** + * Get recent messages + */ + getRecentMessages(count = 10) { + return this.messages.slice(-count); + } +} + +// Export for use in tests and other modules +if (typeof module !== 'undefined' && module.exports) { + module.exports = { DebugPanel }; +} + +// Export for browser use +if (typeof window !== 'undefined') { + window.DebugPanel = DebugPanel; +} \ No newline at end of file diff --git a/markitect/static/js/components/document-controls.js b/markitect/static/js/components/document-controls.js new file mode 100644 index 00000000..fb83ebd8 --- /dev/null +++ b/markitect/static/js/components/document-controls.js @@ -0,0 +1,279 @@ +/** + * DocumentControls Component + * + * Extracted from monolithic editor.js as part of architecture refactoring. + * Handles the floating control panel and document-level actions. + * + * Dependencies: + * - None (standalone component) + */ + +/** + * DocumentControls - Manages the floating control panel and its buttons + */ +class DocumentControls { + constructor() { + this.controlPanel = null; + this.buttons = new Map(); + this.eventHandlers = new Map(); + this.isVisible = true; + } + + /** + * Create the control panel and add it to the DOM + */ + create() { + if (this.controlPanel) { + this.destroy(); // Remove existing panel + } + + // Also remove any existing panel with the same ID in the DOM + const existingPanel = document.getElementById('markitect-global-controls'); + if (existingPanel && existingPanel.parentNode) { + existingPanel.parentNode.removeChild(existingPanel); + } + + // Create the floating control panel + this.controlPanel = document.createElement('div'); + this.controlPanel.id = 'markitect-global-controls'; + this.controlPanel.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: rgba(248, 249, 250, 0.95); + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + backdrop-filter: blur(8px); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 14px; + min-width: 200px; + `; + + // Add title + const title = document.createElement('div'); + title.style.cssText = ` + font-weight: 600; + margin-bottom: 8px; + color: #495057; + border-bottom: 1px solid #dee2e6; + padding-bottom: 4px; + `; + title.textContent = 'Document Controls'; + + // Create button container + const buttonContainer = document.createElement('div'); + buttonContainer.id = 'button-container'; + buttonContainer.style.cssText = ` + display: flex; + flex-direction: column; + gap: 6px; + `; + + this.controlPanel.appendChild(title); + this.controlPanel.appendChild(buttonContainer); + + // Add default buttons + this.addDefaultButtons(); + + // Add debug messages container + this.addDebugContainer(); + + // Add to DOM + document.body.appendChild(this.controlPanel); + } + + /** + * Add default buttons to the control panel + */ + addDefaultButtons() { + // Save Document button + this.addButton('save-document', '💾 Save Document', '#28a745'); + + // Reset All button + this.addButton('reset-all', '🔄 Reset All', '#ffc107', '#212529'); + + // Show Status button + this.addButton('show-status', '📊 Show Status', '#17a2b8'); + + // Debug button + this.addButton('toggle-debug', '🔍 Debug', '#6c757d'); + } + + /** + * Add debug container to the control panel + */ + addDebugContainer() { + const debugContainer = document.createElement('div'); + debugContainer.id = 'debug-messages-container'; + debugContainer.style.cssText = ` + margin-top: 12px; + max-height: 300px; + overflow-y: auto; + border: 1px solid #dee2e6; + border-radius: 4px; + background: #f8f9fa; + padding: 8px; + font-family: 'Courier New', monospace; + font-size: 12px; + line-height: 1.4; + display: none; + `; + + this.controlPanel.appendChild(debugContainer); + } + + /** + * Add a button to the control panel + */ + addButton(id, text, backgroundColor, textColor = 'white') { + const buttonContainer = this.controlPanel.querySelector('#button-container'); + if (!buttonContainer) { + throw new Error('Button container not found. Call create() first.'); + } + + const button = document.createElement('button'); + button.id = id; + button.textContent = text; + button.style.cssText = ` + background: ${backgroundColor}; + color: ${textColor}; + border: none; + padding: 8px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: background-color 0.2s; + `; + + buttonContainer.appendChild(button); + this.buttons.set(id, button); + + return button; + } + + /** + * Remove a button from the control panel + */ + removeButton(id) { + const button = this.buttons.get(id); + if (button && button.parentNode) { + button.parentNode.removeChild(button); + this.buttons.delete(id); + this.eventHandlers.delete(id); + } + } + + /** + * Set event handlers for buttons + */ + setEventHandlers(handlers) { + for (const [buttonId, handler] of Object.entries(handlers)) { + const button = this.buttons.get(buttonId); + if (button) { + // Remove existing handler if any + if (this.eventHandlers.has(buttonId)) { + button.removeEventListener('click', this.eventHandlers.get(buttonId)); + } + + // Add new handler + button.addEventListener('click', handler); + this.eventHandlers.set(buttonId, handler); + } + } + } + + /** + * Show the control panel + */ + show() { + if (this.controlPanel) { + this.controlPanel.style.display = 'block'; + this.isVisible = true; + } + } + + /** + * Hide the control panel + */ + hide() { + if (this.controlPanel) { + this.controlPanel.style.display = 'none'; + this.isVisible = false; + } + } + + /** + * Update status display (can be extended as needed) + */ + updateStatus(status) { + // This method can be extended to show status information + // For now, it just stores the status for potential display + this.lastStatus = status; + + // Could update a status indicator in the panel if needed + if (status && this.controlPanel) { + const title = this.controlPanel.querySelector('div'); + if (title) { + const statusText = `Document Controls (${status.totalSections} sections, ${status.editingSections} editing)`; + // Could update title or add status indicator + } + } + } + + /** + * Get the control panel element + */ + getControlPanel() { + return this.controlPanel; + } + + /** + * Destroy the control panel and clean up + */ + destroy() { + if (this.controlPanel && this.controlPanel.parentNode) { + this.controlPanel.parentNode.removeChild(this.controlPanel); + } + + // Clean up references + this.controlPanel = null; + this.buttons.clear(); + this.eventHandlers.clear(); + this.isVisible = true; + } + + /** + * Check if the control panel is visible + */ + isVisible() { + return this.isVisible && this.controlPanel && this.controlPanel.style.display !== 'none'; + } + + /** + * Get all button IDs + */ + getButtonIds() { + return Array.from(this.buttons.keys()); + } + + /** + * Get a specific button by ID + */ + getButton(id) { + return this.buttons.get(id); + } +} + +// Export for use in tests and other modules +if (typeof module !== 'undefined' && module.exports) { + module.exports = { DocumentControls }; +} + +// Export for browser use +if (typeof window !== 'undefined') { + window.DocumentControls = DocumentControls; +} \ No newline at end of file