${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; // 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); } } 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(); 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; `; // Image preview 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: 1px solid #dee2e6; `; // Parse markdown to extract image info const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/); if (imageMatch) { const [, altText, imageSrc] = imageMatch; const img = document.createElement('img'); img.src = imageSrc; img.alt = altText; img.style.cssText = ` max-width: 100%; max-height: 300px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); `; imagePreview.appendChild(img); } // Image controls const controlPanel = document.createElement('div'); controlPanel.className = 'ui-edit-image-controls'; controlPanel.style.cssText = ` display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; `; // Alt text editor const altTextContainer = document.createElement('div'); altTextContainer.style.cssText = `grid-column: 1 / -1; margin-bottom: 8px;`; const altTextLabel = document.createElement('label'); altTextLabel.textContent = 'Alt Text:'; altTextLabel.style.cssText = `display: block; margin-bottom: 4px; font-weight: bold;`; const altTextInput = document.createElement('input'); altTextInput.type = 'text'; altTextInput.value = imageMatch ? imageMatch[1] : ''; altTextInput.style.cssText = ` width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; `; // Add keyboard shortcuts to alt text input altTextInput.addEventListener('keydown', this.handleKeydown); altTextContainer.appendChild(altTextLabel); altTextContainer.appendChild(altTextInput); // Image manipulation buttons const buttons = [ { text: 'Replace Image', action: () => this.replaceImage(sectionId) }, { text: 'Resize', action: () => this.resizeImage(sectionId) }, { text: 'Add Caption', action: () => this.addImageCaption(sectionId) }, { text: 'Remove Image', action: () => this.removeImage(sectionId) } ]; buttons.forEach(({ text, action }) => { const btn = this.createButton(text, 'ui-edit-image-btn', action); btn.style.fontSize = '12px'; btn.style.padding = '6px 12px'; controlPanel.appendChild(btn); }); // Standard editor controls const editorControls = document.createElement('div'); editorControls.className = 'ui-edit-controls'; editorControls.style.cssText = ` display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; `; const acceptBtn = this.createButton('✓ Accept', 'ui-edit-accept', (e) => { // Update alt text if changed if (imageMatch && altTextInput.value !== imageMatch[1]) { const newMarkdown = section.currentMarkdown.replace( /!\[(.*?)\]/, `![${altTextInput.value}]` ); this.sectionManager.updateContent(sectionId, newMarkdown); } this.handleAccept(e); }); const cancelBtn = this.createButton('✗ Cancel', 'ui-edit-cancel', this.handleCancel); const resetBtn = this.createButton('↺ Reset', 'ui-edit-reset', this.handleReset); acceptBtn.style.background = '#28a745'; cancelBtn.style.background = '#dc3545'; resetBtn.style.background = '#fd7e14'; editorControls.appendChild(acceptBtn); editorControls.appendChild(cancelBtn); editorControls.appendChild(resetBtn); // Assemble the editor editorContainer.appendChild(imagePreview); editorContainer.appendChild(altTextContainer); editorContainer.appendChild(controlPanel); editorContainer.appendChild(editorControls); element.appendChild(editorContainer); altTextInput.focus(); this.editingSections.add(sectionId); } /** * Image manipulation methods */ replaceImage(sectionId) { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { const section = this.sectionManager.sections.get(sectionId); const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/); if (imageMatch) { const newMarkdown = section.currentMarkdown.replace( /!\[(.*?)\]\((.*?)\)/, `![${imageMatch[1]}](${event.target.result})` ); this.sectionManager.updateContent(sectionId, newMarkdown); this.hideEditor(sectionId); setTimeout(() => this.showImageEditor(sectionId, this.sectionManager.sections.get(sectionId)), 100); } }; reader.readAsDataURL(file); } }); input.click(); } resizeImage(sectionId) { const section = this.sectionManager.sections.get(sectionId); const size = prompt('Enter image width (e.g., 300px, 50%, or leave empty for auto):', ''); if (size !== null) { const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/); if (imageMatch) { const style = size ? ` style="width: ${size};"` : ''; const newMarkdown = section.currentMarkdown.replace( /!\[(.*?)\]\((.*?)\)/, `${content}
`; } this.setupSectionElement(element); } findSectionElement(sectionId) { return this.container.querySelector(`[data-section-id="${sectionId}"]`); } setupSectionElement(element) { element.className = 'ui-edit-section'; element.style.cssText = ` margin: 16px 0; padding: 12px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; border: 2px solid transparent; `; 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)'; }; element._mouseleaveHandler = () => { element.style.backgroundColor = ''; element.style.borderColor = 'transparent'; }; element.addEventListener('mouseenter', element._mouseenterHandler); element.addEventListener('mouseleave', element._mouseleaveHandler); } /** * 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() { const panel = document.createElement('div'); panel.className = 'ui-edit-status-panel'; panel.style.cssText = ` position: fixed; top: 20px; right: 20px; background: white; border: 1px solid #ddd; border-radius: 8px; padding: 12px 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; z-index: 1000; min-width: 180px; `; const statusText = document.createElement('div'); statusText.className = 'ui-edit-status-text'; statusText.textContent = 'Ready'; const detailsText = document.createElement('div'); detailsText.className = 'ui-edit-status-details'; detailsText.style.cssText = ` font-size: 12px; color: #666; margin-top: 4px; `; panel.appendChild(statusText); panel.appendChild(detailsText); return panel; } /** * Update status display with current status information * @param {Object} status - Status object from SectionManager */ updateStatusDisplay(status) { let statusPanel = document.querySelector('.ui-edit-status-panel'); if (!statusPanel) { statusPanel = this.createStatusPanel(); document.body.appendChild(statusPanel); } const statusText = statusPanel.querySelector('.ui-edit-status-text'); const detailsText = statusPanel.querySelector('.ui-edit-status-details'); // Update status text and color based on state switch (status.state) { case 'ready': statusText.textContent = '✓ Ready'; statusText.style.color = '#28a745'; break; case 'editing': statusText.textContent = '✏️ Editing'; statusText.style.color = '#007bff'; break; case 'modified': statusText.textContent = '⚠️ Modified'; statusText.style.color = '#ffc107'; break; default: statusText.textContent = 'Unknown'; statusText.style.color = '#6c757d'; } // Update details const details = []; details.push(`${status.totalSections} sections`); if (status.editingSections.length > 0) { details.push(`${status.editingSections.length} editing`); } if (status.modifiedSections > 0) { details.push(`${status.modifiedSections} modified`); } detailsText.textContent = details.join(' • '); // Update timestamp const now = new Date().toLocaleTimeString(); statusPanel.setAttribute('title', `Last update: ${now}`); } } /** * 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.showStatus()); } saveDocument() { try { const markdown = this.sectionManager.getDocumentMarkdown(); // Generate intelligent filename const filename = this.generateSaveFilename(); // Create a download link const blob = new Blob([markdown], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showMessage(`Document saved as ${filename}!`, 'success'); console.log('Document saved:', filename, markdown.length, 'characters'); } catch (error) { this.showMessage('Failed to save document: ' + error.message, 'error'); console.error('Save failed:', error); } } resetAllSections() { if (!confirm('Reset all sections to their original content? This will lose all changes and cannot be undone.')) { return; } try { const sections = this.sectionManager.getAllSections(); sections.forEach(section => { this.sectionManager.resetSection(section.id); }); this.showMessage('All sections reset to original content', 'info'); console.log('All sections reset'); } catch (error) { this.showMessage('Failed to reset sections: ' + error.message, 'error'); console.error('Reset failed:', error); } } showStatus() { const sections = this.sectionManager.getSectionStatus(); const totalSections = sections.length; const editedSections = sections.filter(s => s.hasChanges).length; const currentlyEditing = sections.filter(s => s.isEditing).length; const statusHtml = `Total Sections: ${totalSections}
Modified Sections: ${editedSections}
Currently Editing: ${currentlyEditing}