diff --git a/markitect/static/js/components/dom-renderer.js b/markitect/static/js/components/dom-renderer.js
index d0e8bdcd..c302f80f 100644
--- a/markitect/static/js/components/dom-renderer.js
+++ b/markitect/static/js/components/dom-renderer.js
@@ -396,47 +396,391 @@ class DOMRenderer {
}
/**
- * Show editor for image sections
+ * Show advanced image editor with drag & drop, file upload, and preview
*/
showImageEditor(sectionId, section) {
debug('showImageEditor: called for image section: ' + sectionId, 'EDITOR');
+ // Track staging state for this editor
+ const stagingState = {
+ originalMarkdown: section.currentMarkdown,
+ currentAltText: '',
+ currentImageSrc: '',
+ stagedImageSrc: null,
+ stagedAltText: null,
+ hasChanges: false
+ };
+
+ // Parse markdown to extract image info
+ const imageMatch = section.currentMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
+ if (imageMatch) {
+ const [, altText, imageSrc] = imageMatch;
+ stagingState.currentAltText = altText;
+ stagingState.currentImageSrc = imageSrc;
+ }
+
+ // Create image editor content area
const editorContent = document.createElement('div');
- editorContent.innerHTML = `
-
- Image Editor
- Edit the markdown for this image:
-
-
-
-
-
-
+ editorContent.className = 'ui-edit-image-content';
+ editorContent.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ flex: 1;
+ min-width: 0;
`;
+ // Image preview with drop zone
+ const imagePreview = document.createElement('div');
+ imagePreview.className = 'ui-edit-image-preview';
+ imagePreview.style.cssText = `
+ width: 100%;
+ height: 180px;
+ text-align: center;
+ background: white;
+ padding: 12px;
+ border-radius: 8px;
+ border: 2px dashed #007bff;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ box-sizing: border-box;
+ overflow: hidden;
+ `;
+
+ // 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: 150px;
+ 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: 16px;
+ `;
+ 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: 14px;
+ `;
+ placeholder.innerHTML = `
+ 📁
+ Drop image here or click to select
+ Supports JPG, PNG, GIF, WebP
+ `;
+ imagePreview.appendChild(placeholder);
+ }
+ };
+
+ // Initialize preview
+ updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
+
+ // File input for image selection
+ const fileInput = document.createElement('input');
+ fileInput.type = 'file';
+ fileInput.accept = 'image/*';
+ fileInput.style.display = 'none';
+
+ // Function to handle image file selection
+ const handleImageFile = (file) => {
+ if (file && file.type.startsWith('image/')) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ stagingState.stagedImageSrc = event.target.result;
+ stagingState.hasChanges = true;
+ updateImagePreview(stagingState.stagedImageSrc, altTextInput.value);
+ updateChangeIndicator();
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ // Drag and drop functionality
+ imagePreview.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#28a745';
+ imagePreview.style.backgroundColor = '#f8fff8';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'flex';
+ });
+
+ imagePreview.addEventListener('dragleave', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#007bff';
+ imagePreview.style.backgroundColor = 'white';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'none';
+ });
+
+ imagePreview.addEventListener('drop', (e) => {
+ e.preventDefault();
+ imagePreview.style.borderColor = '#007bff';
+ imagePreview.style.backgroundColor = 'white';
+ const overlay = imagePreview.querySelector('.drop-overlay');
+ if (overlay) overlay.style.display = 'none';
+
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ handleImageFile(files[0]);
+ }
+ });
+
+ // Click to select file
+ imagePreview.addEventListener('click', () => {
+ fileInput.click();
+ });
+
+ fileInput.addEventListener('change', (e) => {
+ if (e.target.files.length > 0) {
+ handleImageFile(e.target.files[0]);
+ }
+ });
+
+ // Alt text editor
+ const altTextContainer = document.createElement('div');
+ altTextContainer.className = 'ui-edit-alt-text-container';
+ altTextContainer.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ `;
+
+ const altTextLabel = document.createElement('label');
+ altTextLabel.textContent = 'Alt Text Description:';
+ altTextLabel.style.cssText = `
+ font-size: 13px;
+ font-weight: 600;
+ color: #333;
+ margin: 0;
+ `;
+
+ const altTextInput = document.createElement('input');
+ altTextInput.type = 'text';
+ altTextInput.value = stagingState.currentAltText;
+ altTextInput.style.cssText = `
+ width: 100%;
+ padding: 10px 12px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 14px;
+ box-sizing: border-box;
+ outline: none;
+ transition: border-color 0.2s ease;
+ `;
+
+ altTextInput.addEventListener('focus', () => {
+ altTextInput.style.borderColor = '#007bff';
+ });
+
+ altTextInput.addEventListener('blur', () => {
+ altTextInput.style.borderColor = '#ddd';
+ });
+
+ // Track alt text changes
+ altTextInput.addEventListener('input', () => {
+ stagingState.stagedAltText = altTextInput.value;
+ stagingState.hasChanges = altTextInput.value !== stagingState.currentAltText || stagingState.stagedImageSrc !== null;
+ updateChangeIndicator();
+ });
+
+ altTextContainer.appendChild(altTextLabel);
+ altTextContainer.appendChild(altTextInput);
+
+ // Change indicator
+ const changeIndicator = document.createElement('div');
+ changeIndicator.className = 'change-indicator';
+ changeIndicator.style.cssText = `
+ padding: 8px 12px;
+ background: #fff3cd;
+ border: 1px solid #ffeaa7;
+ border-radius: 6px;
+ color: #856404;
+ font-size: 12px;
+ text-align: center;
+ display: none;
+ font-weight: 500;
+ `;
+ changeIndicator.textContent = '⚠️ You have unsaved changes';
+
+ const updateChangeIndicator = () => {
+ if (stagingState.hasChanges) {
+ changeIndicator.style.display = 'block';
+ } else {
+ changeIndicator.style.display = 'none';
+ }
+ };
+
+ // Assemble content
+ editorContent.appendChild(imagePreview);
+ editorContent.appendChild(altTextContainer);
+ editorContent.appendChild(changeIndicator);
+ editorContent.appendChild(fileInput);
+
+ // Create controls
+ const controls = document.createElement('div');
+ controls.className = 'ui-edit-controls';
+ controls.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+ `;
+
+ const acceptBtn = document.createElement('button');
+ acceptBtn.textContent = '✓ Accept';
+ acceptBtn.style.cssText = `
+ padding: 8px 12px;
+ font-size: 12px;
+ border-radius: 6px;
+ border: none;
+ color: white;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s ease;
+ width: 100%;
+ text-align: center;
+ background: #28a745;
+ `;
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.textContent = '✗ Cancel';
+ cancelBtn.style.cssText = `
+ padding: 8px 12px;
+ font-size: 12px;
+ border-radius: 6px;
+ border: none;
+ color: white;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s ease;
+ width: 100%;
+ text-align: center;
+ background: #dc3545;
+ `;
+
+ const resetBtn = document.createElement('button');
+ resetBtn.textContent = '↺ Reset';
+ resetBtn.style.cssText = `
+ padding: 8px 12px;
+ font-size: 12px;
+ border-radius: 6px;
+ border: none;
+ color: white;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s ease;
+ width: 100%;
+ text-align: center;
+ background: #fd7e14;
+ `;
+
+ controls.appendChild(acceptBtn);
+ controls.appendChild(cancelBtn);
+ controls.appendChild(resetBtn);
+
+ // Event handlers
+ acceptBtn.addEventListener('click', () => {
+ // Apply staged changes only when accept is clicked
+ if (stagingState.hasChanges) {
+ let newMarkdown = stagingState.originalMarkdown;
+
+ // Apply image source change if staged
+ if (stagingState.stagedImageSrc !== null) {
+ const currentImageMatch = newMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
+ if (currentImageMatch) {
+ newMarkdown = newMarkdown.replace(
+ /!\[(.*?)\]\((.*?)\)/,
+ `![${currentImageMatch[1]}](${stagingState.stagedImageSrc})`
+ );
+ }
+ }
+
+ // Apply alt text change if staged
+ if (stagingState.stagedAltText !== null) {
+ newMarkdown = newMarkdown.replace(
+ /!\[(.*?)\]/,
+ `![${stagingState.stagedAltText}]`
+ );
+ }
+
+ // Update section with final changes
+ this.sectionManager.updateContent(sectionId, newMarkdown);
+ }
+
+ // Accept changes and hide editor
+ this.sectionManager.acceptChanges(sectionId);
+ this.currentFloatingMenu.hide();
+ this.currentFloatingMenu = null;
+ });
+
+ cancelBtn.addEventListener('click', () => {
+ // Discard all staged changes and hide editor
+ this.sectionManager.cancelChanges(sectionId);
+ this.currentFloatingMenu.hide();
+ this.currentFloatingMenu = null;
+ });
+
+ resetBtn.addEventListener('click', () => {
+ // Reset to original content
+ const originalImageMatch = stagingState.originalMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
+ if (originalImageMatch) {
+ const [, originalAltText, originalImageSrc] = originalImageMatch;
+ stagingState.currentAltText = originalAltText;
+ stagingState.currentImageSrc = originalImageSrc;
+ }
+
+ // Clear any staged changes
+ stagingState.stagedImageSrc = null;
+ stagingState.stagedAltText = null;
+ stagingState.hasChanges = false;
+
+ // Reset alt text input to original
+ altTextInput.value = stagingState.currentAltText;
+
+ // Reset preview to original
+ updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText);
+ updateChangeIndicator();
+ });
+
+ // Create floating menu
const floatingMenu = new FloatingMenu(sectionId, 'image', this);
this.currentFloatingMenu = floatingMenu;
this.editingSections.add(sectionId);
- floatingMenu.show(editorContent);
-
- // Add event listeners
- const textarea = editorContent.querySelector('textarea');
- const acceptBtn = editorContent.querySelector('#accept-image');
- const cancelBtn = editorContent.querySelector('#cancel-image');
-
- acceptBtn.addEventListener('click', () => {
- this.sectionManager.updateContent(sectionId, textarea.value);
- this.sectionManager.acceptChanges(sectionId);
- floatingMenu.hide();
- this.currentFloatingMenu = null; // Clear reference
- });
-
- cancelBtn.addEventListener('click', () => {
- this.sectionManager.cancelChanges(sectionId);
- floatingMenu.hide();
- this.currentFloatingMenu = null; // Clear reference
- });
+ floatingMenu.show(editorContent, controls);
}
/**