Преглед на файлове

Image resize to wysiwyg

svalavuo преди 5 дни
родител
ревизия
9f7beb21b9
променени са 2 файла, в които са добавени 392 реда и са изтрити 0 реда
  1. 172 0
      css/wysiwyg.css
  2. 220 0
      js/wysiwyg.js

+ 172 - 0
css/wysiwyg.css

@@ -409,3 +409,175 @@
         min-height: 300px;
     }
 }
+
+/* Image Resizing */
+.wysiwyg-content img {
+    max-width: 100%;
+    height: auto;
+    display: inline-block;
+    cursor: move;
+}
+
+.wysiwyg-content img.resizing {
+    outline: 2px solid #007bff;
+    outline-offset: 2px;
+}
+
+.resize-container {
+    position: relative;
+    display: inline-block;
+    cursor: move;
+}
+
+.resize-container.resizing {
+    outline: 2px solid #007bff;
+    outline-offset: 2px;
+}
+
+.resize-handle {
+    position: absolute;
+    background: #007bff;
+    border: 1px solid #fff;
+    border-radius: 50%;
+    width: 8px;
+    height: 8px;
+    z-index: 1000;
+}
+
+.resize-handle:hover {
+    background: #0056b3;
+    transform: scale(1.2);
+}
+
+.resize-handle.nw {
+    top: -4px;
+    left: -4px;
+    cursor: nw-resize;
+}
+
+.resize-handle.ne {
+    top: -4px;
+    right: -4px;
+    cursor: ne-resize;
+}
+
+.resize-handle.sw {
+    bottom: -4px;
+    left: -4px;
+    cursor: sw-resize;
+}
+
+.resize-handle.se {
+    bottom: -4px;
+    right: -4px;
+    cursor: se-resize;
+}
+
+.resize-handle.n {
+    top: -4px;
+    left: 50%;
+    transform: translateX(-50%);
+    cursor: n-resize;
+}
+
+.resize-handle.s {
+    bottom: -4px;
+    left: 50%;
+    transform: translateX(-50%);
+    cursor: s-resize;
+}
+
+.resize-handle.w {
+    top: 50%;
+    left: -4px;
+    transform: translateY(-50%);
+    cursor: w-resize;
+}
+
+.resize-handle.e {
+    top: 50%;
+    right: -4px;
+    transform: translateY(-50%);
+    cursor: e-resize;
+}
+
+.resize-handle.nw:hover,
+.resize-handle.ne:hover,
+.resize-handle.sw:hover,
+.resize-handle.se:hover {
+    transform: scale(1.2);
+}
+
+.resize-handle.n:hover,
+.resize-handle.s:hover {
+    transform: translateX(-50%) scale(1.2);
+}
+
+.resize-handle.w:hover,
+.resize-handle.e:hover {
+    transform: translateY(-50%) scale(1.2);
+}
+
+.size-display {
+    position: absolute;
+    bottom: -25px;
+    left: 50%;
+    transform: translateX(-50%);
+    background: rgba(0, 0, 0, 0.8);
+    color: white;
+    padding: 2px 6px;
+    border-radius: 3px;
+    font-size: 11px;
+    font-family: monospace;
+    pointer-events: none;
+    z-index: 1001;
+    white-space: nowrap;
+}
+
+.aspect-ratio-toggle {
+    position: absolute;
+    top: -30px;
+    right: 0;
+    background: #007bff;
+    color: white;
+    border: none;
+    padding: 4px 8px;
+    border-radius: 3px;
+    font-size: 11px;
+    cursor: pointer;
+    z-index: 1001;
+}
+
+.aspect-ratio-toggle:hover {
+    background: #0056b3;
+}
+
+.aspect-ratio-toggle.locked {
+    background: #28a745;
+}
+
+.aspect-ratio-toggle.locked:hover {
+    background: #218838;
+}
+
+@media (max-width: 768px) {
+    .wysiwyg-toolbar {
+        padding: 6px;
+        gap: 2px;
+    }
+    
+    .wysiwyg-btn {
+        padding: 4px 6px;
+        font-size: 11px;
+    }
+    
+    .resize-handle {
+        width: 10px;
+        height: 10px;
+    }
+    
+    .size-display {
+        font-size: 10px;
+        bottom: -20px;
+    }
+}

+ 220 - 0
js/wysiwyg.js

@@ -12,10 +12,14 @@ class WYSIWYGEditor {
         };
         
         this.selectedImages = [];
+        this.resizingImage = null;
+        this.resizeData = null;
+        this.aspectRatioLocked = false;
         
         this.createEditor();
         this.bindEvents();
         this.setupImageUpload();
+        this.setupImageResizing();
     }
     
     createEditor() {
@@ -365,6 +369,222 @@ class WYSIWYGEditor {
         return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
     }
     
+    setupImageResizing() {
+        this.content.addEventListener('click', (e) => {
+            if (e.target.tagName === 'IMG') {
+                this.selectImageForResize(e.target);
+            } else if (e.target.closest('.resize-container')) {
+                this.selectImageForResize(e.target.closest('.resize-container').querySelector('img'));
+            } else {
+                this.deselectImageForResize();
+            }
+        });
+        
+        // Handle resize handle mouse events
+        this.content.addEventListener('mousedown', (e) => {
+            if (e.target.classList.contains('resize-handle')) {
+                e.preventDefault();
+                this.startResize(e);
+            }
+        });
+        
+        // Handle global mouse events for resizing
+        document.addEventListener('mousemove', (e) => {
+            if (this.resizingImage) {
+                this.handleResize(e);
+            }
+        });
+        
+        document.addEventListener('mouseup', () => {
+            if (this.resizingImage) {
+                this.stopResize();
+            }
+        });
+    }
+    
+    selectImageForResize(img) {
+        this.deselectImageForResize();
+        
+        // Wrap image in resize container if not already wrapped
+        if (!img.closest('.resize-container')) {
+            const container = document.createElement('div');
+            container.className = 'resize-container';
+            img.parentNode.insertBefore(container, img);
+            container.appendChild(img);
+        }
+        
+        const container = img.closest('.resize-container');
+        container.classList.add('resizing');
+        
+        // Add resize handles
+        this.addResizeHandles(container);
+        
+        // Add aspect ratio toggle
+        this.addAspectRatioToggle(container);
+        
+        // Add size display
+        this.addSizeDisplay(container);
+        
+        this.updateSizeDisplay(container);
+    }
+    
+    deselectImageForResize() {
+        // Remove all resize containers and handles
+        this.content.querySelectorAll('.resize-container').forEach(container => {
+            const img = container.querySelector('img');
+            if (img) {
+                container.parentNode.insertBefore(img, container);
+            }
+            container.remove();
+        });
+        
+        this.resizingImage = null;
+        this.resizeData = null;
+    }
+    
+    addResizeHandles(container) {
+        const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e'];
+        
+        handles.forEach(position => {
+            const handle = document.createElement('div');
+            handle.className = `resize-handle ${position}`;
+            handle.dataset.position = position;
+            container.appendChild(handle);
+        });
+    }
+    
+    addAspectRatioToggle(container) {
+        const toggle = document.createElement('button');
+        toggle.className = 'aspect-ratio-toggle';
+        toggle.textContent = this.aspectRatioLocked ? 'Locked' : 'Free';
+        toggle.title = 'Toggle aspect ratio lock';
+        
+        toggle.addEventListener('click', (e) => {
+            e.stopPropagation();
+            this.aspectRatioLocked = !this.aspectRatioLocked;
+            toggle.textContent = this.aspectRatioLocked ? 'Locked' : 'Free';
+            toggle.classList.toggle('locked', this.aspectRatioLocked);
+        });
+        
+        container.appendChild(toggle);
+    }
+    
+    addSizeDisplay(container) {
+        const display = document.createElement('div');
+        display.className = 'size-display';
+        container.appendChild(display);
+    }
+    
+    updateSizeDisplay(container) {
+        const img = container.querySelector('img');
+        const display = container.querySelector('.size-display');
+        if (img && display) {
+            display.textContent = `${img.offsetWidth} × ${img.offsetHeight}`;
+        }
+    }
+    
+    startResize(e) {
+        const handle = e.target;
+        const container = handle.closest('.resize-container');
+        const img = container.querySelector('img');
+        
+        this.resizingImage = img;
+        this.resizeData = {
+            container: container,
+            handle: handle,
+            position: handle.dataset.position,
+            startX: e.clientX,
+            startY: e.clientY,
+            startWidth: img.offsetWidth,
+            startHeight: img.offsetHeight,
+            aspectRatio: img.offsetWidth / img.offsetHeight
+        };
+    }
+    
+    handleResize(e) {
+        if (!this.resizeData) return;
+        
+        const deltaX = e.clientX - this.resizeData.startX;
+        const deltaY = e.clientY - this.resizeData.startY;
+        const position = this.resizeData.position;
+        
+        let newWidth = this.resizeData.startWidth;
+        let newHeight = this.resizeData.startHeight;
+        
+        switch (position) {
+            case 'se':
+                newWidth = this.resizeData.startWidth + deltaX;
+                newHeight = this.aspectRatioLocked 
+                    ? newWidth / this.resizeData.aspectRatio 
+                    : this.resizeData.startHeight + deltaY;
+                break;
+            case 'sw':
+                newWidth = this.resizeData.startWidth - deltaX;
+                newHeight = this.aspectRatioLocked 
+                    ? newWidth / this.resizeData.aspectRatio 
+                    : this.resizeData.startHeight + deltaY;
+                break;
+            case 'ne':
+                newWidth = this.resizeData.startWidth + deltaX;
+                newHeight = this.aspectRatioLocked 
+                    ? newWidth / this.resizeData.aspectRatio 
+                    : this.resizeData.startHeight - deltaY;
+                break;
+            case 'nw':
+                newWidth = this.resizeData.startWidth - deltaX;
+                newHeight = this.aspectRatioLocked 
+                    ? newWidth / this.resizeData.aspectRatio 
+                    : this.resizeData.startHeight - deltaY;
+                break;
+            case 'n':
+                newHeight = this.resizeData.startHeight - deltaY;
+                if (this.aspectRatioLocked) {
+                    newWidth = newHeight * this.resizeData.aspectRatio;
+                }
+                break;
+            case 's':
+                newHeight = this.resizeData.startHeight + deltaY;
+                if (this.aspectRatioLocked) {
+                    newWidth = newHeight * this.resizeData.aspectRatio;
+                }
+                break;
+            case 'w':
+                newWidth = this.resizeData.startWidth - deltaX;
+                if (this.aspectRatioLocked) {
+                    newHeight = newWidth / this.resizeData.aspectRatio;
+                }
+                break;
+            case 'e':
+                newWidth = this.resizeData.startWidth + deltaX;
+                if (this.aspectRatioLocked) {
+                    newHeight = newWidth / this.resizeData.aspectRatio;
+                }
+                break;
+        }
+        
+        // Apply minimum size constraints
+        newWidth = Math.max(50, newWidth);
+        newHeight = Math.max(50, newHeight);
+        
+        // Apply new dimensions
+        this.resizingImage.style.width = newWidth + 'px';
+        this.resizingImage.style.height = newHeight + 'px';
+        
+        // Update size display
+        this.updateSizeDisplay(this.resizeData.container);
+    }
+    
+    stopResize() {
+        if (this.resizeData) {
+            // Update textarea content
+            this.textarea.value = this.content.innerHTML;
+            this.updateCharCount();
+        }
+        
+        this.resizingImage = null;
+        this.resizeData = null;
+    }
+    
     formatHeading(tag) {
         const selection = window.getSelection();
         const range = selection.getRangeAt(0);