${section.currentMarkdown}
`; } this.setupSectionElement(element); debug('SECTION: Section element setup complete for ' + section.id, 'SECTION'); return element; } handleSectionClick(event) { debug('CLICK: Click detected on target: ' + event.target.tagName + ' ' + (event.target.className || ''), 'CLICK'); // Don't handle clicks on form elements, buttons, or links if (event.target.closest('textarea, button, input, a')) { debug('CLICK: Ignoring click on form element', 'CLICK'); return; } const sectionElement = event.target.closest('.ui-edit-section'); debug('CLICK: Found section element: ' + (sectionElement ? sectionElement.getAttribute('data-section-id') : 'null'), 'CLICK'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); debug('CLICK: Section ID: ' + sectionId, 'CLICK'); if (!sectionId) return; // Track the click event this.trackEvent('section-click', { sectionId, target: event.target.tagName.toLowerCase(), event, timestamp: Date.now() }); // Check if this section is already being edited const section = this.sectionManager.sections.get(sectionId); if (section && section.isEditing()) { console.log('Section already being edited:', sectionId); return; } try { console.log('Starting edit for section:', sectionId); this.sectionManager.startEditing(sectionId); } catch (error) { console.error('Failed to start editing:', error); } } /** * Handle hover events (mouseenter/mouseleave) * @param {Event} event - Mouse event */ handleSectionHover(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); if (!sectionId) return; const eventType = event.type === 'mouseenter' ? 'section-hover-enter' : 'section-hover-leave'; // Track the hover event this.trackEvent(eventType, { sectionId, event, timestamp: Date.now() }); } /** * Handle drag and drop events for section reordering * @param {Event} event - Drag event */ handleDragStart(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); if (!sectionId) return; // Store the section ID being dragged event.dataTransfer.setData('text/plain', sectionId); event.dataTransfer.effectAllowed = 'move'; // Track the drag start event this.trackEvent('section-drag-start', { sectionId, event, timestamp: Date.now() }); } handleDragOver(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); if (!sectionId) return; event.preventDefault(); // Allow drop event.dataTransfer.dropEffect = 'move'; // Track the drag over event this.trackEvent('section-drag-over', { sectionId, event, timestamp: Date.now() }); } handleDrop(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const targetSectionId = sectionElement.getAttribute('data-section-id'); const draggedSectionId = event.dataTransfer.getData('text/plain'); if (!targetSectionId || !draggedSectionId || targetSectionId === draggedSectionId) return; event.preventDefault(); // Track the drop event this.trackEvent('section-drop', { draggedSectionId, targetSectionId, event, timestamp: Date.now() }); // Emit section reorder event this.sectionManager.emit('section-reorder', { draggedSectionId, targetSectionId, action: 'reorder' }); } /** * Handle focus events for accessibility * @param {Event} event - Focus event */ handleFocus(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); if (!sectionId) return; const eventType = event.type === 'focusin' ? 'section-focus-in' : 'section-focus-out'; // Track the focus event this.trackEvent(eventType, { sectionId, target: event.target.tagName.toLowerCase(), event, timestamp: Date.now() }); } /** * Handle context menu events for right-click operations * @param {Event} event - Context menu event */ handleContextMenu(event) { const sectionElement = event.target.closest('.ui-edit-section'); if (!sectionElement) return; const sectionId = sectionElement.getAttribute('data-section-id'); if (!sectionId) return; // Track the context menu event this.trackEvent('section-context-menu', { sectionId, x: event.clientX, y: event.clientY, event, timestamp: Date.now() }); // Prevent default context menu for sections event.preventDefault(); // Show custom context menu this.showSectionContextMenu(sectionId, event.clientX, event.clientY); } /** * Show custom context menu for section operations * @param {string} sectionId - Section ID * @param {number} x - X coordinate * @param {number} y - Y coordinate */ showSectionContextMenu(sectionId, x, y) { // Remove existing context menu const existingMenu = document.querySelector('.ui-edit-context-menu'); if (existingMenu) { existingMenu.remove(); } const menu = document.createElement('div'); menu.className = 'ui-edit-context-menu'; menu.style.cssText = ` position: fixed; left: ${x}px; top: ${y}px; background: white; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; min-width: 150px; `; const section = this.sectionManager.sections.get(sectionId); const isEditing = section && section.isEditing(); const menuItems = [ { text: 'Edit Section', action: () => this.sectionManager.startEditing(sectionId), disabled: isEditing }, { text: 'Copy Section', action: () => this.copySectionToClipboard(sectionId), disabled: false }, { text: 'Delete Section', action: () => this.deleteSection(sectionId), disabled: isEditing }, { text: '—', action: null, disabled: false }, // Separator { text: 'Move Up', action: () => this.moveSectionUp(sectionId), disabled: false }, { text: 'Move Down', action: () => this.moveSectionDown(sectionId), disabled: false } ]; menuItems.forEach(item => { if (item.text === '—') { const separator = document.createElement('div'); separator.style.cssText = 'height: 1px; background: #eee; margin: 4px 0;'; menu.appendChild(separator); } else { const menuItem = document.createElement('div'); menuItem.textContent = item.text; menuItem.style.cssText = ` padding: 8px 12px; cursor: ${item.disabled ? 'not-allowed' : 'pointer'}; color: ${item.disabled ? '#999' : '#333'}; background: ${item.disabled ? 'transparent' : 'white'}; `; if (!item.disabled) { menuItem.addEventListener('mouseenter', () => { menuItem.style.background = '#f0f0f0'; }); menuItem.addEventListener('mouseleave', () => { menuItem.style.background = 'white'; }); menuItem.addEventListener('click', () => { item.action(); menu.remove(); }); } menu.appendChild(menuItem); } }); document.body.appendChild(menu); // Remove menu when clicking elsewhere const removeMenu = (e) => { if (!menu.contains(e.target)) { menu.remove(); document.removeEventListener('click', removeMenu); } }; setTimeout(() => document.addEventListener('click', removeMenu), 100); } /** * Context menu actions */ copySectionToClipboard(sectionId) { const section = this.sectionManager.sections.get(sectionId); if (section) { navigator.clipboard.writeText(section.currentMarkdown).then(() => { console.log('Section copied to clipboard'); }); } } deleteSection(sectionId) { if (confirm('Are you sure you want to delete this section?')) { this.sectionManager.sections.delete(sectionId); const element = this.findSectionElement(sectionId); if (element) { element.remove(); } } } moveSectionUp(sectionId) { const element = this.findSectionElement(sectionId); if (element && element.previousElementSibling) { element.parentNode.insertBefore(element, element.previousElementSibling); } } moveSectionDown(sectionId) { const element = this.findSectionElement(sectionId); if (element && element.nextElementSibling) { element.parentNode.insertBefore(element.nextElementSibling, element); } } showEditor(sectionId, content) { const element = this.findSectionElement(sectionId); if (!element) return; this.hideCurrentEditor(); const section = this.sectionManager.sections.get(sectionId); const isImageSection = section && section.isImage(); if (isImageSection) { this.showImageEditor(sectionId, section); return; } const editorContainer = document.createElement('div'); editorContainer.className = 'ui-edit-editor-container'; const textarea = document.createElement('textarea'); textarea.className = 'ui-edit-textarea ui-edit-textarea-main'; textarea.value = content; textarea.style.cssText = ` flex: 1; min-height: 100px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 14px; line-height: 1.5; padding: 12px; border: 2px solid #007bff; border-radius: 6px; resize: vertical; outline: none; background: white; color: #333; `; // Setup auto-resize functionality this.setupAutoResize(textarea); const controls = document.createElement('div'); controls.className = 'ui-edit-controls'; controls.style.cssText = ` display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; `; const acceptBtn = this.createButton('Accept', 'ui-edit-button-accept', this.handleAccept); const cancelBtn = this.createButton('Cancel', 'ui-edit-button-cancel', this.handleCancel); controls.appendChild(acceptBtn); controls.appendChild(cancelBtn); editorContainer.appendChild(textarea); editorContainer.appendChild(controls); element.innerHTML = ''; element.appendChild(editorContainer); textarea.focus(); this.editingSections.add(sectionId); textarea.addEventListener('input', () => { this.sectionManager.updateContent(sectionId, textarea.value); }); // Add keyboard shortcuts textarea.addEventListener('keydown', this.handleKeydown); } /** * Show image editor with manipulation controls * @param {string} sectionId - The section ID * @param {Section} section - The section object */ showImageEditor(sectionId, section) { const element = this.findSectionElement(sectionId); if (!element) return; this.hideCurrentEditor(); // Track staging state for this editor const stagingState = { originalMarkdown: section.currentMarkdown, currentAltText: '', currentImageSrc: '', stagedImageSrc: null, stagedAltText: null, hasChanges: false }; const editorContainer = document.createElement('div'); editorContainer.className = 'ui-edit-image-editor-container'; editorContainer.style.cssText = ` display: flex; flex-direction: column; gap: 12px; margin-top: 12px; padding: 16px; background: #f8f9fa; border-radius: 8px; border: 2px solid #007bff; `; // Parse markdown to extract image info const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/); if (imageMatch) { const [, altText, imageSrc] = imageMatch; stagingState.currentAltText = altText; stagingState.currentImageSrc = imageSrc; } // Image preview with drop zone const imagePreview = document.createElement('div'); imagePreview.className = 'ui-edit-image-preview'; imagePreview.style.cssText = ` max-width: 100%; text-align: center; background: white; padding: 16px; border-radius: 6px; border: 2px dashed #007bff; transition: all 0.3s ease; cursor: pointer; min-height: 200px; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; `; // Function to update image preview const updateImagePreview = (imageSrc, altText) => { imagePreview.innerHTML = ''; if (imageSrc) { const img = document.createElement('img'); img.src = imageSrc; img.alt = altText || ''; img.style.cssText = ` max-width: 100%; max-height: 250px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); `; imagePreview.appendChild(img); // Add overlay for drop zone const overlay = document.createElement('div'); overlay.className = 'drop-overlay'; overlay.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 123, 255, 0.1); border-radius: 6px; display: none; align-items: center; justify-content: center; color: #007bff; font-weight: bold; font-size: 18px; `; overlay.textContent = '📁 Drop new image here'; imagePreview.appendChild(overlay); } else { // Show drop zone placeholder const placeholder = document.createElement('div'); placeholder.style.cssText = ` text-align: center; color: #6c757d; font-size: 16px; `; placeholder.innerHTML = `${content}
`; } this.setupSectionElement(element); } findSectionElement(sectionId) { return this.container.querySelector(`[data-section-id="${sectionId}"]`); } setupSectionElement(element) { element.className = 'ui-edit-section'; element.draggable = true; // Enable drag and drop element.tabIndex = 0; // Make focusable for accessibility element.style.cssText = ` margin: 16px 0; padding: 12px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; border: 2px solid transparent; position: relative; `; // Add drag handle indicator const dragHandle = document.createElement('div'); dragHandle.className = 'ui-edit-drag-handle'; dragHandle.innerHTML = '⋮⋮'; dragHandle.style.cssText = ` position: absolute; left: -8px; top: 50%; transform: translateY(-50%); color: #ccc; font-size: 16px; line-height: 1; cursor: grab; opacity: 0; transition: opacity 0.2s ease; user-select: none; `; element.appendChild(dragHandle); element.removeEventListener('mouseenter', element._mouseenterHandler); element.removeEventListener('mouseleave', element._mouseleaveHandler); element._mouseenterHandler = () => { element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)'; element.style.borderColor = 'rgba(0, 0, 0, 0.1)'; dragHandle.style.opacity = '1'; }; element._mouseleaveHandler = () => { element.style.backgroundColor = ''; element.style.borderColor = 'transparent'; dragHandle.style.opacity = '0'; }; element.addEventListener('mouseenter', element._mouseenterHandler); element.addEventListener('mouseleave', element._mouseleaveHandler); // Enhanced accessibility element.setAttribute('role', 'article'); element.setAttribute('aria-label', 'Editable section'); element.setAttribute('title', 'Click to edit, right-click for options, drag to reorder'); // Apply comprehensive styling enhancements this.applyComprehensiveStyling(element); } /** * Apply comprehensive styling enhancements to section elements * @param {HTMLElement} element - The section element */ applyComprehensiveStyling(element) { if (!element || !element.dataset.sectionId) return; const sectionId = element.dataset.sectionId; const section = this.sectionManager.sections.get(sectionId); if (!section) return; // Apply type-specific styling this.applyTypeSpecificStyling(element, section); // Apply state-based styling this.applyStateStyling(element, section); // Apply responsive design this.applyResponsiveStyling(element); // Apply accessibility enhancements this.enhanceAccessibility(element, section); // Apply length-based styling this.applyLengthBasedStyling(element, section); // Apply performance-optimized transitions this.applyOptimizedTransitions(element); // Apply CSS custom properties this.applyCSSCustomProperties(element, section); // Apply theme support this.applySectionTheme(element, 'light'); // Default theme // Apply content analysis styling this.analyzeContentForStyling(element, section); // Apply CSS reset and normalization this.applyCSSReset(element); // Setup animation support this.setupAnimationSupport(element); // Apply print-friendly styling this.applyPrintStyling(element); // Integrate with existing systems this.integrateWithMessageSystem(element); this.integrateWithControlPanel(element); } /** * Apply type-specific styling to section elements * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ applyTypeSpecificStyling(element, section) { // Add base class element.classList.add('markitect-section-editable'); // Add type-specific class const typeClass = `markitect-section-${section.type || 'paragraph'}`; element.classList.add(typeClass); // Set data attributes element.dataset.sectionType = section.type || 'paragraph'; element.dataset.sectionId = section.id; // Type-specific styling const typeStyles = { heading: { borderLeft: '4px solid #007acc', backgroundColor: 'rgba(0, 122, 204, 0.02)', fontWeight: '600' }, code: { borderLeft: '4px solid #28a745', backgroundColor: 'rgba(40, 167, 69, 0.02)', fontFamily: 'monospace' }, list: { borderLeft: '4px solid #ffc107', backgroundColor: 'rgba(255, 193, 7, 0.02)' }, quote: { borderLeft: '4px solid #6f42c1', backgroundColor: 'rgba(111, 66, 193, 0.02)', fontStyle: 'italic' }, image: { borderLeft: '4px solid #fd7e14', backgroundColor: 'rgba(253, 126, 20, 0.02)', textAlign: 'center' }, table: { borderLeft: '4px solid #20c997', backgroundColor: 'rgba(32, 201, 151, 0.02)' }, hr: { borderLeft: '4px solid #6c757d', backgroundColor: 'rgba(108, 117, 125, 0.02)', minHeight: '20px' } }; const styles = typeStyles[section.type] || typeStyles.paragraph || {}; Object.assign(element.style, styles); } /** * Apply state-based styling * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ applyStateStyling(element, section) { // Remove existing state classes element.classList.remove('section-original', 'section-editing', 'section-modified', 'section-saved'); // Apply current state class if (section.isEditing()) { element.classList.add('section-editing'); element.style.backgroundColor = 'rgba(0, 122, 204, 0.1)'; element.style.borderColor = '#007acc'; } else if (section.hasChanges()) { element.classList.add('section-modified'); element.style.backgroundColor = 'rgba(255, 193, 7, 0.1)'; element.style.borderColor = '#ffc107'; } else if (section.state === 'saved') { element.classList.add('section-saved'); element.style.backgroundColor = 'rgba(40, 167, 69, 0.1)'; element.style.borderColor = '#28a745'; } else { element.classList.add('section-original'); } } /** * Apply responsive design styling * @param {HTMLElement} element - Section element */ applyResponsiveStyling(element) { element.classList.add('section-responsive'); // Responsive styles element.style.cssText += ` max-width: 100%; min-width: 0; overflow-wrap: break-word; word-wrap: break-word; `; // Responsive behavior based on viewport const updateResponsiveStyles = () => { const width = window.innerWidth; if (width < 768) { // Mobile styles element.style.margin = '8px 0'; element.style.padding = '8px'; element.style.fontSize = '14px'; } else if (width < 1024) { // Tablet styles element.style.margin = '12px 0'; element.style.padding = '10px'; element.style.fontSize = '15px'; } else { // Desktop styles element.style.margin = '16px 0'; element.style.padding = '12px'; element.style.fontSize = '16px'; } }; updateResponsiveStyles(); window.addEventListener('resize', updateResponsiveStyles); } /** * Enhance accessibility features * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ enhanceAccessibility(element, section) { // Enhanced ARIA attributes element.setAttribute('aria-describedby', `section-desc-${section.id}`); element.setAttribute('aria-labelledby', `section-label-${section.id}`); // Screen reader support const srOnly = document.createElement('span'); srOnly.className = 'sr-only'; srOnly.id = `section-desc-${section.id}`; srOnly.textContent = `${section.type} section with ${section.currentMarkdown.length} characters`; srOnly.style.cssText = ` position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; `; element.appendChild(srOnly); // Enhanced keyboard navigation element.tabIndex = 0; element.setAttribute('aria-keyshortcuts', 'Enter Space'); // Focus enhancement element.addEventListener('focus', () => { element.style.outline = '2px solid #007acc'; element.style.outlineOffset = '2px'; }); element.addEventListener('blur', () => { element.style.outline = ''; element.style.outlineOffset = ''; }); } /** * Apply length-based visual indicators * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ applyLengthBasedStyling(element, section) { const length = section.currentMarkdown.length; // Remove existing length classes element.classList.remove('section-short', 'section-medium', 'section-long'); if (length < 100) { element.classList.add('section-short'); element.style.minHeight = '40px'; } else if (length < 500) { element.classList.add('section-medium'); element.style.minHeight = '60px'; } else { element.classList.add('section-long'); element.style.minHeight = '80px'; element.style.maxHeight = '400px'; element.style.overflowY = 'auto'; } // Word count indicator const wordCount = section.currentMarkdown.split(/\s+/).length; element.dataset.wordCount = wordCount.toString(); element.setAttribute('title', element.getAttribute('title') + ` (${wordCount} words)`); } /** * Apply performance-optimized CSS transitions * @param {HTMLElement} element - Section element */ applyOptimizedTransitions(element) { // GPU-accelerated transitions element.style.willChange = 'transform, opacity'; element.style.transform = 'translateZ(0)'; // Force GPU layer element.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)'; // Hover enhancements element.classList.add('section-hoverable'); const optimizedMouseEnter = () => { element.style.transform = 'translateZ(0) scale(1.01)'; element.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)'; }; const optimizedMouseLeave = () => { element.style.transform = 'translateZ(0) scale(1)'; element.style.boxShadow = ''; }; element.addEventListener('mouseenter', optimizedMouseEnter); element.addEventListener('mouseleave', optimizedMouseLeave); } /** * Apply CSS custom properties for advanced styling * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ applyCSSCustomProperties(element, section) { // CSS variables for dynamic theming element.style.setProperty('--section-primary-color', '#007acc'); element.style.setProperty('--section-background', 'rgba(248, 249, 250, 0.5)'); element.style.setProperty('--section-border-radius', '6px'); element.style.setProperty('--section-padding', '12px'); element.style.setProperty('--section-margin', '16px 0'); element.style.setProperty('--section-transition', 'all 0.2s ease'); // Type-specific CSS variables const typeColors = { heading: '#007acc', code: '#28a745', list: '#ffc107', quote: '#6f42c1', image: '#fd7e14', table: '#20c997', hr: '#6c757d' }; element.style.setProperty('--section-type-color', typeColors[section.type] || '#6c757d'); element.dataset.cssVariables = 'true'; } /** * Apply theme-based styling * @param {HTMLElement} element - Section element * @param {string} theme - Theme name ('light', 'dark', 'high-contrast') */ applySectionTheme(element, theme) { element.dataset.theme = theme; element.classList.remove('theme-light', 'theme-dark', 'theme-high-contrast'); element.classList.add(`theme-${theme}`); const themes = { light: { '--section-background': 'rgba(248, 249, 250, 0.5)', '--section-text-color': '#212529', '--section-border-color': 'rgba(0, 0, 0, 0.1)', '--section-hover-bg': 'rgba(0, 0, 0, 0.02)' }, dark: { '--section-background': 'rgba(33, 37, 41, 0.8)', '--section-text-color': '#f8f9fa', '--section-border-color': 'rgba(255, 255, 255, 0.2)', '--section-hover-bg': 'rgba(255, 255, 255, 0.05)' }, 'high-contrast': { '--section-background': '#ffffff', '--section-text-color': '#000000', '--section-border-color': '#000000', '--section-hover-bg': '#f0f0f0' } }; const themeStyles = themes[theme] || themes.light; Object.entries(themeStyles).forEach(([property, value]) => { element.style.setProperty(property, value); }); } /** * Analyze content for styling purposes * @param {HTMLElement} element - Section element * @param {Section} section - Section object */ analyzeContentForStyling(element, section) { const content = section.currentMarkdown.toLowerCase(); // Content-based classes if (content.includes('```') || content.includes('`')) { element.classList.add('contains-code'); } if (content.includes('$$') || content.includes('\\(') || content.includes('\\[')) { element.classList.add('contains-math'); } if (content.includes('http') || content.includes('[') && content.includes(']')) { element.classList.add('contains-links'); } if (content.includes('![')) { element.classList.add('contains-images'); } if (content.includes('|') && content.includes('-')) { element.classList.add('contains-tables'); } // Priority content indicators if (content.includes('important') || content.includes('warning') || content.includes('!')) { element.classList.add('priority-high'); element.style.borderLeftWidth = '6px'; } } /** * Apply CSS reset and normalization * @param {HTMLElement} element - Section element */ applyCSSReset(element) { element.classList.add('css-reset'); // Normalize box model element.style.boxSizing = 'border-box'; element.style.margin = '16px 0'; element.style.padding = '12px'; // Reset browser defaults const allChildren = element.querySelectorAll('*'); allChildren.forEach(child => { child.style.boxSizing = 'border-box'; }); } /** * Setup animation support for state transitions * @param {HTMLElement} element - Section element */ setupAnimationSupport(element) { element.classList.add('animation-ready'); // Animation utility method element._animate = (animationType) => { this.animateSectionTransition(element, animationType); }; } /** * Animate section transitions * @param {HTMLElement} element - Section element * @param {string} animationType - Type of animation */ animateSectionTransition(element, animationType) { element.classList.add('section-animating'); switch (animationType) { case 'enter': element.classList.add('transition-entering'); element.style.opacity = '0'; element.style.transform = 'translateY(-10px)'; setTimeout(() => { element.style.opacity = '1'; element.style.transform = 'translateY(0)'; }, 50); break; case 'leave': element.classList.add('transition-leaving'); element.style.opacity = '0'; element.style.transform = 'translateY(10px)'; break; case 'highlight': element.style.backgroundColor = 'rgba(255, 193, 7, 0.3)'; setTimeout(() => { element.style.backgroundColor = ''; }, 1000); break; } setTimeout(() => { element.classList.remove('section-animating', 'transition-entering', 'transition-leaving'); }, 300); } /** * Apply print-friendly styling * @param {HTMLElement} element - Section element */ applyPrintStyling(element) { element.classList.add('print-friendly'); element.dataset.printOptimized = 'true'; // Print-specific styles via CSS const printStyles = document.createElement('style'); printStyles.textContent = ` @media print { .print-friendly { break-inside: avoid; margin: 8px 0; padding: 8px; border: 1px solid #000; background: white !important; color: black !important; box-shadow: none !important; transform: none !important; } .ui-edit-drag-handle, .sr-only { display: none !important; } } `; if (!document.querySelector('#section-print-styles')) { printStyles.id = 'section-print-styles'; document.head.appendChild(printStyles); } } /** * Update dynamic styles based on section state * @param {HTMLElement} element - Section element * @param {Object} newStyles - Style updates */ updateSectionDynamicStyles(element, newStyles) { Object.entries(newStyles).forEach(([property, value]) => { if (property.startsWith('--')) { element.style.setProperty(property, value); } else { element.style[property] = value; } }); } /** * Integrate with message system styling * @param {HTMLElement} element - Section element */ integrateWithMessageSystem(element) { // Add message system integration class element.classList.add('message-system-ready'); // Store reference for message positioning element.dataset.messageAnchor = 'true'; } /** * Integrate with control panel styling * @param {HTMLElement} element - Section element */ integrateWithControlPanel(element) { // Add control panel integration class element.classList.add('control-panel-aware'); // Adjust positioning to avoid overlap with control panel const adjustForControlPanel = () => { const controlPanel = document.getElementById('markitect-global-controls'); if (controlPanel) { const rect = controlPanel.getBoundingClientRect(); const elementRect = element.getBoundingClientRect(); // Avoid overlap with control panel if (elementRect.right > rect.left && elementRect.top < rect.bottom) { element.style.marginRight = `${rect.width + 20}px`; } } }; // Check for overlap periodically setTimeout(adjustForControlPanel, 100); } /** * Setup auto-resize for textarea * @param {HTMLTextAreaElement} textarea - The textarea element */ setupAutoResize(textarea) { const autoResize = () => { const transition = textarea.style.transition; textarea.style.transition = 'none'; textarea.style.height = 'auto'; const contentHeight = textarea.scrollHeight; const padding = 24; const lineCount = textarea.value.split('\n').length; const minHeight = Math.max(60, lineCount * 24 + padding); const maxHeight = 360; const newHeight = Math.max(60, Math.min(maxHeight, Math.max(minHeight, contentHeight + 4))); textarea.style.height = newHeight + 'px'; textarea.style.transition = transition; }; textarea.addEventListener('input', autoResize); textarea.addEventListener('paste', () => setTimeout(autoResize, 10)); // Initial sizing setTimeout(autoResize, 20); } /** * Create status panel for real-time status display * @returns {HTMLElement} Status panel element */ // createStatusPanel method removed - floating status panel no longer needed // Status information is displayed in the control panel menu instead /** * Update status display with current status information * @param {Object} status - Status object from SectionManager */ updateStatusDisplay(status) { // Status information is now only displayed in the control panel menu // No floating status panel needed since status is integrated into the menu return; } } /** * Main Editor Integration */ class MarkitectCleanEditor { constructor(markdownContent, containerElement, options = {}) { debug('10: MarkitectCleanEditor constructor called', 'EDITOR'); this.options = { theme: 'github', keyboardShortcuts: true, autosave: false, originalFilename: null, ...options }; debug('11: Creating SectionManager', 'EDITOR'); this.sectionManager = new SectionManager(); debug('12: Creating DOMRenderer', 'EDITOR'); this.domRenderer = new DOMRenderer(this.sectionManager, containerElement); this.originalMarkdown = markdownContent; this.cleanMarkdownContent = options.cleanMarkdownContent || markdownContent; this.dogtagContent = options.dogtagContent || ''; debug('13: About to call initialize()', 'EDITOR'); this.initialize(); debug('14: initialize() completed', 'EDITOR'); } initialize() { try { debug('15: Starting initialize() - markdown length: ' + this.originalMarkdown.length, 'INIT'); const sections = this.sectionManager.createSectionsFromMarkdown(this.originalMarkdown); debug('16: Created ' + sections.length + ' sections', 'INIT'); // Mark the dogtag section as protected if we have a dogtag if (this.dogtagContent) { debug('17: Marking dogtag section', 'INIT'); this.markDogtagSection(sections); } // Mark base64 reference sections as protected debug('18: Marking base64 reference sections', 'INIT'); this.markBase64ReferenceSections(sections); console.log(`✓ Initialized clean editor with ${sections.length} sections`); // Add global control panel debug('19: Adding global controls', 'INIT'); this.addGlobalControls(); // Setup status tracking debug('19.5: Setting up status tracking', 'INIT'); this.setupStatusTracking(); debug('20: Initialize completed successfully', 'INIT'); return true; } catch (error) { debug('ERROR in initialize: ' + error.message, 'ERROR'); console.error('Failed to initialize clean editor:', error); return false; } } markDogtagSection(sections) { // Find the section that contains the dogtag content const dogtagText = this.dogtagContent.trim(); if (!dogtagText) return; for (const section of sections) { if (section.currentMarkdown.includes(dogtagText)) { section.isDogtagSection = true; console.log('Marked dogtag section as protected:', section.id); break; } } } markBase64ReferenceSections(sections) { // Find sections that contain base64 image references if (!window.markitectBase64References) return; const refIds = Object.keys(window.markitectBase64References); if (refIds.length === 0) return; for (const section of sections) { const markdown = section.currentMarkdown; // Check if this section contains base64 reference syntax for (const refId of refIds) { if (markdown.includes(`[${refId}]:`)) { section.isBase64RefSection = true; console.log('Marked base64 reference section as protected:', section.id); break; } } } } addGlobalControls() { // Create a floating control panel const controlPanel = document.createElement('div'); controlPanel.id = 'markitect-global-controls'; 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; `; 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'; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; flex-direction: column; gap: 6px; `; // Save Document button const saveButton = document.createElement('button'); saveButton.id = 'save-document'; saveButton.textContent = '💾 Save Document'; saveButton.style.cssText = ` background: #28a745; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background-color 0.2s; `; // Reset All button const resetButton = document.createElement('button'); resetButton.id = 'reset-all'; resetButton.textContent = '🔄 Reset All'; resetButton.style.cssText = ` background: #ffc107; color: #212529; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background-color 0.2s; `; // Show Status button const statusButton = document.createElement('button'); statusButton.id = 'show-status'; statusButton.textContent = '📊 Show Status'; statusButton.style.cssText = ` background: #17a2b8; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; transition: background-color 0.2s; `; buttonContainer.appendChild(saveButton); buttonContainer.appendChild(resetButton); buttonContainer.appendChild(statusButton); controlPanel.appendChild(title); controlPanel.appendChild(buttonContainer); document.body.appendChild(controlPanel); // Add event listeners document.getElementById('save-document').addEventListener('click', () => this.saveDocument()); document.getElementById('reset-all').addEventListener('click', () => this.resetAllSections()); document.getElementById('show-status').addEventListener('click', () => this.showDocumentStatus()); // Store reference for enhanced control panel methods this.controlPanel = controlPanel; // Initialize enhanced features this.setupControlPanelEnhancements(); } /** * Enhanced method to create floating control panel (for TDD compatibility) * @returns {HTMLElement} The control panel element */ createFloatingControlPanel() { // If control panel already exists, return it if (this.controlPanel) { return this.controlPanel; } // Create enhanced control panel this.addGlobalControls(); return this.controlPanel; } /** * Setup enhanced control panel features */ setupControlPanelEnhancements() { if (!this.controlPanel) return; // Add draggable functionality this.makeControlPanelDraggable(); // Add collapsible functionality this.addCollapsibleFeature(); // Add statistics display this.addStatisticsDisplay(); // Setup keyboard shortcuts this.setupControlPanelKeyboard(); // Apply responsive design this.setupResponsiveControlPanel(); // Load user preferences this.loadControlPanelPreferences(); // Setup animations this.setupControlPanelAnimations(); // Apply default theme this.setControlPanelTheme('light'); } /** * Make control panel draggable */ makeControlPanelDraggable() { if (!this.controlPanel) return; const title = this.controlPanel.querySelector('div'); if (title) { title.style.cursor = 'move'; title.draggable = true; let isDragging = false; let startX, startY, initialX, initialY; title.addEventListener('mousedown', (e) => { isDragging = true; startX = e.clientX; startY = e.clientY; initialX = this.controlPanel.offsetLeft; initialY = this.controlPanel.offsetTop; title.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; this.controlPanel.style.left = `${initialX + deltaX}px`; this.controlPanel.style.top = `${initialY + deltaY}px`; this.controlPanel.style.right = 'auto'; // Override right positioning }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; title.style.userSelect = ''; this.saveControlPanelPreferences(); } }); } } /** * Add collapsible/expandable functionality */ addCollapsibleFeature() { if (!this.controlPanel) return; const title = this.controlPanel.querySelector('div'); const buttonContainer = this.controlPanel.querySelector('div:last-child'); if (title && buttonContainer) { // Add toggle button const toggleBtn = document.createElement('span'); toggleBtn.textContent = '▼'; toggleBtn.className = 'panel-toggle'; toggleBtn.style.cssText = ` float: right; cursor: pointer; font-size: 12px; transition: transform 0.3s ease; `; title.appendChild(toggleBtn); // Toggle functionality let isCollapsed = false; toggleBtn.addEventListener('click', () => { this.toggleControlPanel(); }); } } /** * Toggle control panel collapsed state */ toggleControlPanel() { if (!this.controlPanel) return; const buttonContainer = this.controlPanel.querySelector('div:last-child'); const toggleBtn = this.controlPanel.querySelector('.panel-toggle'); if (buttonContainer && toggleBtn) { const isCollapsed = buttonContainer.style.display === 'none'; if (isCollapsed) { buttonContainer.style.display = 'flex'; toggleBtn.textContent = '▼'; toggleBtn.style.transform = 'rotate(0deg)'; this.controlPanel.classList.remove('collapsed'); } else { buttonContainer.style.display = 'none'; toggleBtn.textContent = '▶'; toggleBtn.style.transform = 'rotate(-90deg)'; this.controlPanel.classList.add('collapsed'); } this.saveControlPanelPreferences(); } } /** * Add real-time statistics display */ addStatisticsDisplay() { if (!this.controlPanel) return; const statsDiv = document.createElement('div'); statsDiv.className = 'control-panel-stats'; statsDiv.style.cssText = ` margin-top: 8px; padding-top: 8px; border-top: 1px solid #dee2e6; font-size: 12px; color: #6c757d; `; // Insert before button container const buttonContainer = this.controlPanel.querySelector('div:last-child'); this.controlPanel.insertBefore(statsDiv, buttonContainer); // Update stats periodically this.updateControlPanelStats(); setInterval(() => this.updateControlPanelStats(), 3000); } /** * Update control panel statistics */ updateControlPanelStats() { const statsDiv = this.controlPanel?.querySelector('.control-panel-stats'); if (!statsDiv) return; const status = this.sectionManager.getDocumentStatus(); const eventStats = this.domRenderer.getEventStats(); statsDiv.innerHTML = `Total Sections: ${totalSections}
Modified Sections: ${editedSections}
Currently Editing: ${currentlyEditing}
| Section | Type | State | Length | Changes |
|---|---|---|---|---|
| ${section.type || 'paragraph'} | ${section.state || 'original'} | ${section.currentMarkdown.length} chars | ${hasChanges ? '●' : '○'} |